##// END OF EJS Templates
Added thread field to the post. 'parent' field is deprecated now.
neko259 -
r174:481c6224 default
parent child Browse files
Show More
@@ -0,0 +1,72 b''
1 # -*- coding: utf-8 -*-
2 import datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'Post.thread'
12 db.add_column(u'boards_post', 'thread',
13 self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['boards.Post'], null=True),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Post.thread'
19 db.delete_column(u'boards_post', 'thread_id')
20
21
22 models = {
23 u'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
27 },
28 u'boards.post': {
29 'Meta': {'object_name': 'Post'},
30 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
31 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
32 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
33 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
34 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
35 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
36 'parent': ('django.db.models.fields.BigIntegerField', [], {}),
37 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
38 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
39 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
40 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'re+'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['boards.Post']"}),
41 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['boards.Tag']", 'symmetrical': 'False'}),
42 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
43 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
44 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['boards.Post']", 'null': 'True'}),
45 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
46 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['boards.User']", 'null': 'True'})
47 },
48 u'boards.setting': {
49 'Meta': {'object_name': 'Setting'},
50 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
51 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
52 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['boards.User']"}),
53 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
54 },
55 u'boards.tag': {
56 'Meta': {'object_name': 'Tag'},
57 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
58 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
59 },
60 u'boards.user': {
61 'Meta': {'object_name': 'User'},
62 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
63 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['boards.Post']"}),
64 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
65 'last_access_time': ('django.db.models.fields.DateTimeField', [], {}),
66 'rank': ('django.db.models.fields.IntegerField', [], {}),
67 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
68 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
69 }
70 }
71
72 complete_apps = ['boards'] No newline at end of file
@@ -1,332 +1,341 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.db.models import Count
8 from django.db.models import Count
9 from django.http import Http404
9 from django.http import Http404
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 def create_post(self, title, text, image=None, thread=None,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 parent=parent_id,
41 thread=thread,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if parent_id != NO_PARENT:
48 if thread:
49 parent = self.get(id=parent_id)
49 thread.replies.add(post)
50 parent.replies.add(post)
51
50
52 if tags:
51 if tags:
53 map(post.tags.add, tags)
52 map(post.tags.add, tags)
54
53
55 if parent_id != NO_PARENT:
54 if thread:
56 self._bump_thread(parent_id)
55 thread.bump()
57 else:
56 else:
58 self._delete_old_threads()
57 self._delete_old_threads()
59
58
60 return post
59 return post
61
60
62 def delete_post(self, post):
61 def delete_post(self, post):
63 if post.replies.count() > 0:
62 if post.replies.count() > 0:
64 map(self.delete_post, post.replies)
63 map(self.delete_post, post.replies.all())
65 post.delete()
64 post.delete()
66
65
67 def delete_posts_by_ip(self, ip):
66 def delete_posts_by_ip(self, ip):
68 posts = self.filter(poster_ip=ip)
67 posts = self.filter(poster_ip=ip)
69 map(self.delete_post, posts)
68 map(self.delete_post, posts)
70
69
71 def get_threads(self, tag=None, page=ALL_PAGES,
70 def get_threads(self, tag=None, page=ALL_PAGES,
72 order_by='-last_edit_time'):
71 order_by='-last_edit_time'):
73 if tag:
72 if tag:
74 threads = self.filter(parent=NO_PARENT, tags=tag)
73 threads = self.filter(thread=None, tags=tag)
74
75 # TODO This needs to be uncommented when 'all tags' view won't
76 # use this method to get threads for tag
75
77
76 # TODO Throw error 404 if no threads for tag found?
78 # if threads.count() == 0:
79 # raise Http404
77 else:
80 else:
78 threads = self.filter(parent=NO_PARENT)
81 threads = self.filter(thread=None)
79
82
80 threads = threads.order_by(order_by)
83 threads = threads.order_by(order_by)
81
84
82 if page != ALL_PAGES:
85 if page != ALL_PAGES:
83 thread_count = threads.count()
86 thread_count = threads.count()
84
87
85 if page < self.get_thread_page_count(tag=tag):
88 if page < self.get_thread_page_count(tag=tag):
86 start_thread = page * settings.THREADS_PER_PAGE
89 start_thread = page * settings.THREADS_PER_PAGE
87 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
90 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
88 thread_count)
91 thread_count)
89 threads = threads[start_thread:end_thread]
92 threads = threads[start_thread:end_thread]
90
93
91 return threads
94 return threads
92
95
93 def get_thread(self, opening_post_id):
96 def get_thread(self, opening_post_id):
94 try:
97 try:
95 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
98 opening_post = self.get(id=opening_post_id, thread=None)
96 except Post.DoesNotExist:
99 except Post.DoesNotExist:
97 raise Http404
100 raise Http404
98
101
99 if opening_post.replies:
102 if opening_post.replies:
100 thread = [opening_post]
103 thread = [opening_post]
101 thread.extend(opening_post.replies.all())
104 thread.extend(opening_post.replies.all())
102
105
103 return thread
106 return thread
104
107
105 def exists(self, post_id):
108 def exists(self, post_id):
106 posts = self.filter(id=post_id)
109 posts = self.filter(id=post_id)
107
110
108 return posts.count() > 0
111 return posts.count() > 0
109
112
110 def get_thread_page_count(self, tag=None):
113 def get_thread_page_count(self, tag=None):
111 if tag:
114 if tag:
112 threads = self.filter(parent=NO_PARENT, tags=tag)
115 threads = self.filter(thread=None, tags=tag)
113 else:
116 else:
114 threads = self.filter(parent=NO_PARENT)
117 threads = self.filter(thread=None)
115
118
116 return int(math.ceil(threads.count() / float(
119 return int(math.ceil(threads.count() / float(
117 settings.THREADS_PER_PAGE)))
120 settings.THREADS_PER_PAGE)))
118
121
119 def _delete_old_threads(self):
122 def _delete_old_threads(self):
120 """
123 """
121 Preserves maximum thread count. If there are too many threads,
124 Preserves maximum thread count. If there are too many threads,
122 delete the old ones.
125 delete the old ones.
123 """
126 """
124
127
125 # TODO Move old threads to the archive instead of deleting them.
128 # TODO Move old threads to the archive instead of deleting them.
126 # Maybe make some 'old' field in the model to indicate the thread
129 # Maybe make some 'old' field in the model to indicate the thread
127 # must not be shown and be able for replying.
130 # must not be shown and be able for replying.
128
131
129 threads = self.get_threads()
132 threads = self.get_threads()
130 thread_count = len(threads)
133 thread_count = len(threads)
131
134
132 if thread_count > settings.MAX_THREAD_COUNT:
135 if thread_count > settings.MAX_THREAD_COUNT:
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
136 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
134 old_threads = threads[thread_count - num_threads_to_delete:]
137 old_threads = threads[thread_count - num_threads_to_delete:]
135
138
136 map(self.delete_post, old_threads)
139 map(self.delete_post, old_threads)
137
140
138 def _bump_thread(self, thread_id):
141 def _bump_thread(self, thread_id):
139 thread = self.get(id=thread_id)
142 thread = self.get(id=thread_id)
140
143
141 if thread.can_bump():
144 if thread.can_bump():
142 thread.last_edit_time = timezone.now()
145 thread.last_edit_time = timezone.now()
143 thread.save()
146 thread.save()
144
147
145
148
146 class TagManager(models.Manager):
149 class TagManager(models.Manager):
147 def get_not_empty_tags(self):
150 def get_not_empty_tags(self):
148 all_tags = self.all().order_by('name')
151 all_tags = self.all().order_by('name')
149 tags = []
152 tags = []
150 for tag in all_tags:
153 for tag in all_tags:
151 if not tag.is_empty():
154 if not tag.is_empty():
152 tags.append(tag)
155 tags.append(tag)
153
156
154 return tags
157 return tags
155
158
156 def get_popular_tags(self):
159 def get_popular_tags(self):
157 all_tags = self.get_not_empty_tags()
160 all_tags = self.get_not_empty_tags()
158
161
159 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
162 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
160 reverse=True)
163 reverse=True)
161
164
162 return sorted_tags[:settings.POPULAR_TAGS]
165 return sorted_tags[:settings.POPULAR_TAGS]
163
166
164
167
165 class Tag(models.Model):
168 class Tag(models.Model):
166 """
169 """
167 A tag is a text node assigned to the post. The tag serves as a board
170 A tag is a text node assigned to the post. The tag serves as a board
168 section. There can be multiple tags for each message
171 section. There can be multiple tags for each message
169 """
172 """
170
173
171 objects = TagManager()
174 objects = TagManager()
172
175
173 name = models.CharField(max_length=100)
176 name = models.CharField(max_length=100)
174
177
175 def __unicode__(self):
178 def __unicode__(self):
176 return self.name
179 return self.name
177
180
178 def is_empty(self):
181 def is_empty(self):
179 return self.get_post_count() == 0
182 return self.get_post_count() == 0
180
183
181 def get_post_count(self):
184 def get_post_count(self):
182 posts_with_tag = Post.objects.get_threads(tag=self)
185 posts_with_tag = Post.objects.get_threads(tag=self)
183 return posts_with_tag.count()
186 return posts_with_tag.count()
184
187
185 def get_popularity(self):
188 def get_popularity(self):
186 posts_with_tag = Post.objects.get_threads(tag=self)
189 posts_with_tag = Post.objects.get_threads(tag=self)
187 reply_count = 0
190 reply_count = 0
188 for post in posts_with_tag:
191 for post in posts_with_tag:
189 reply_count += post.get_reply_count()
192 reply_count += post.get_reply_count()
190 reply_count += OPENING_POST_POPULARITY_WEIGHT
193 reply_count += OPENING_POST_POPULARITY_WEIGHT
191
194
192 return reply_count
195 return reply_count
193
196
194
197
195 class Post(models.Model):
198 class Post(models.Model):
196 """A post is a message."""
199 """A post is a message."""
197
200
198 objects = PostManager()
201 objects = PostManager()
199
202
200 def _update_image_filename(self, filename):
203 def _update_image_filename(self, filename):
201 """Get unique image filename"""
204 """Get unique image filename"""
202
205
203 path = IMAGES_DIRECTORY
206 path = IMAGES_DIRECTORY
204 new_name = str(int(time.mktime(time.gmtime())))
207 new_name = str(int(time.mktime(time.gmtime())))
205 new_name += str(int(random() * 1000))
208 new_name += str(int(random() * 1000))
206 new_name += FILE_EXTENSION_DELIMITER
209 new_name += FILE_EXTENSION_DELIMITER
207 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
210 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
208
211
209 return os.path.join(path, new_name)
212 return os.path.join(path, new_name)
210
213
211 title = models.CharField(max_length=TITLE_MAX_LENGTH)
214 title = models.CharField(max_length=TITLE_MAX_LENGTH)
212 pub_time = models.DateTimeField()
215 pub_time = models.DateTimeField()
213 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
216 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
214 escape_html=False)
217 escape_html=False)
215
218
216 image_width = models.IntegerField(default=0)
219 image_width = models.IntegerField(default=0)
217 image_height = models.IntegerField(default=0)
220 image_height = models.IntegerField(default=0)
218
221
219 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
222 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
220 blank=True, sizes=(IMAGE_THUMB_SIZE,),
223 blank=True, sizes=(IMAGE_THUMB_SIZE,),
221 width_field='image_width',
224 width_field='image_width',
222 height_field='image_height')
225 height_field='image_height')
223
226
224 poster_ip = models.GenericIPAddressField()
227 poster_ip = models.GenericIPAddressField()
225 poster_user_agent = models.TextField()
228 poster_user_agent = models.TextField()
226
229
227 # TODO Convert this field to ForeignKey
230 # TODO Convert this field to ForeignKey
228 parent = models.BigIntegerField()
231 parent = models.BigIntegerField(default=NO_PARENT)
229
232
233 thread = models.ForeignKey('Post', null=True, default=None)
230 tags = models.ManyToManyField(Tag)
234 tags = models.ManyToManyField(Tag)
231 last_edit_time = models.DateTimeField()
235 last_edit_time = models.DateTimeField()
232 user = models.ForeignKey('User', null=True, default=None)
236 user = models.ForeignKey('User', null=True, default=None)
233
237
234 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
238 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
235 blank=True, related_name='re+')
239 blank=True, related_name='re+')
236
240
237 def __unicode__(self):
241 def __unicode__(self):
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
242 return '#' + str(self.id) + ' ' + self.title + ' (' + \
239 self.text.raw[:50] + ')'
243 self.text.raw[:50] + ')'
240
244
241 def get_title(self):
245 def get_title(self):
242 title = self.title
246 title = self.title
243 if len(title) == 0:
247 if len(title) == 0:
244 title = self.text.raw[:20]
248 title = self.text.raw[:20]
245
249
246 return title
250 return title
247
251
248 def _get_replies(self):
252 def _get_replies(self):
249 return self.replies
253 return self.replies
250
254
251 def get_reply_count(self):
255 def get_reply_count(self):
252 return self.replies.count()
256 return self.replies.count()
253
257
254 def get_images_count(self):
258 def get_images_count(self):
255 images_count = 1 if self.image else 0
259 images_count = 1 if self.image else 0
256
260
257 for reply in self.replies:
261 for reply in self.replies:
258 if reply.image:
262 if reply.image:
259 images_count += 1
263 images_count += 1
260
264
261 return images_count
265 return images_count
262
266
263 def can_bump(self):
267 def can_bump(self):
264 """Check if the thread can be bumped by replying"""
268 """Check if the thread can be bumped by replying"""
265
269
266 post_count = self.get_reply_count() + 1
270 post_count = self.get_reply_count() + 1
267
271
268 return post_count <= settings.MAX_POSTS_PER_THREAD
272 return post_count <= settings.MAX_POSTS_PER_THREAD
269
273
270 def get_last_replies(self):
274 def bump(self):
275 if self.can_bump():
276 self.last_edit_time = timezone.now()
277 self.save()
278
279 def get_last_replies(self):
271 if settings.LAST_REPLIES_COUNT > 0:
280 if settings.LAST_REPLIES_COUNT > 0:
272 reply_count = self.get_reply_count()
281 reply_count = self.get_reply_count()
273
282
274 if reply_count > 0:
283 if reply_count > 0:
275 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
284 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
276 reply_count)
285 reply_count)
277 last_replies = self.replies.all()[reply_count -
286 last_replies = self.replies.all()[reply_count -
278 reply_count_to_show:]
287 reply_count_to_show:]
279
288
280 return last_replies
289 return last_replies
281
290
282
291
283 class User(models.Model):
292 class User(models.Model):
284
293
285 user_id = models.CharField(max_length=50)
294 user_id = models.CharField(max_length=50)
286 rank = models.IntegerField()
295 rank = models.IntegerField()
287
296
288 registration_time = models.DateTimeField()
297 registration_time = models.DateTimeField()
289 last_access_time = models.DateTimeField()
298 last_access_time = models.DateTimeField()
290
299
291 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
300 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
292 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
301 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
293 blank=True)
302 blank=True)
294
303
295 def save_setting(self, name, value):
304 def save_setting(self, name, value):
296 setting, created = Setting.objects.get_or_create(name=name, user=self)
305 setting, created = Setting.objects.get_or_create(name=name, user=self)
297 setting.value = value
306 setting.value = value
298 setting.save()
307 setting.save()
299
308
300 return setting
309 return setting
301
310
302 def get_setting(self, name):
311 def get_setting(self, name):
303 if Setting.objects.filter(name=name, user=self).exists():
312 if Setting.objects.filter(name=name, user=self).exists():
304 setting = Setting.objects.get(name=name, user=self)
313 setting = Setting.objects.get(name=name, user=self)
305 setting_value = setting.value
314 setting_value = setting.value
306 else:
315 else:
307 setting_value = None
316 setting_value = None
308
317
309 return setting_value
318 return setting_value
310
319
311 def is_moderator(self):
320 def is_moderator(self):
312 return RANK_MODERATOR >= self.rank
321 return RANK_MODERATOR >= self.rank
313
322
314 def get_sorted_fav_tags(self):
323 def get_sorted_fav_tags(self):
315 return self.fav_tags.order_by('name')
324 return self.fav_tags.order_by('name')
316
325
317 def __unicode__(self):
326 def __unicode__(self):
318 return self.user_id + '(' + str(self.rank) + ')'
327 return self.user_id + '(' + str(self.rank) + ')'
319
328
320
329
321 class Setting(models.Model):
330 class Setting(models.Model):
322
331
323 name = models.CharField(max_length=50)
332 name = models.CharField(max_length=50)
324 value = models.CharField(max_length=50)
333 value = models.CharField(max_length=50)
325 user = models.ForeignKey(User)
334 user = models.ForeignKey(User)
326
335
327
336
328 class Ban(models.Model):
337 class Ban(models.Model):
329 ip = models.GenericIPAddressField()
338 ip = models.GenericIPAddressField()
330
339
331 def __unicode__(self):
340 def __unicode__(self):
332 return self.ip
341 return self.ip
@@ -1,48 +1,50 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3
3
4 <!DOCTYPE html>
4 <!DOCTYPE html>
5 <html>
5 <html>
6 <head>
6 <head>
7 <link rel="stylesheet" type="text/css"
7 <link rel="stylesheet" type="text/css"
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
9 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css"
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
12 {% trans 'Feed' %}"/>
12 {% trans 'Feed' %}"/>
13
13
14 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
15 href="{{ STATIC_URL }}favicon.png">
15 href="{{ STATIC_URL }}favicon.png">
16
16
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta charset="utf-8"/>
18 <meta charset="utf-8"/>
19 {% block head %}{% endblock %}
19 {% block head %}{% endblock %}
20 </head>
20 </head>
21 <body>
21 <body>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
27
27
28 <div class="navigation_panel">
28 <div class="navigation_panel">
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
30 {% for tag in tags %}
30 {% for tag in tags %}
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
31 {% if not tag.is_empty %}
32 >{{ tag.name }}</a>
32 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
33 >{{ tag.name }}</a>
34 {% endif %}
33 {% endfor %}
35 {% endfor %}
34 <a class="tag" href="{% url 'tags' %}" alt="{% trans 'Tag management' %}"
36 <a class="tag" href="{% url 'tags' %}" alt="{% trans 'Tag management' %}"
35 >[...]</a>
37 >[...]</a>
36 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
38 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
37 </div>
39 </div>
38
40
39 {% block content %}{% endblock %}
41 {% block content %}{% endblock %}
40
42
41 <div class="navigation_panel">
43 <div class="navigation_panel">
42 {% block metapanel %}{% endblock %}
44 {% block metapanel %}{% endblock %}
43 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
45 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
44 <a class="link" href="#top">{% trans 'Up' %}</a>
46 <a class="link" href="#top">{% trans 'Up' %}</a>
45 </div>
47 </div>
46
48
47 </body>
49 </body>
48 </html>
50 </html>
@@ -1,181 +1,172 b''
1 # coding=utf-8
1 # coding=utf-8
2 from django.utils.unittest import TestCase
2 from django.utils.unittest import TestCase
3 from django.test.client import Client
3 from django.test.client import Client
4
4
5 import boards
5 import boards
6
6
7 from boards.models import Post, Tag
7 from boards.models import Post, Tag
8 from neboard import settings
8 from neboard import settings
9
9
10 TEST_TEXT = 'test text'
10 TEST_TEXT = 'test text'
11
11
12 NEW_THREAD_PAGE = '/'
12 NEW_THREAD_PAGE = '/'
13 THREAD_PAGE_ONE = '/thread/1/'
13 THREAD_PAGE_ONE = '/thread/1/'
14 THREAD_PAGE = '/thread/'
14 THREAD_PAGE = '/thread/'
15 TAG_PAGE = '/tag/'
15 TAG_PAGE = '/tag/'
16 HTTP_CODE_REDIRECT = 302
16 HTTP_CODE_REDIRECT = 302
17 HTTP_CODE_OK = 200
17 HTTP_CODE_OK = 200
18 HTTP_CODE_NOT_FOUND = 404
18 HTTP_CODE_NOT_FOUND = 404
19
19
20
20
21 class BoardTests(TestCase):
21 class BoardTests(TestCase):
22 def _create_post(self):
22 def _create_post(self):
23 return Post.objects.create_post(title='title',
23 return Post.objects.create_post(title='title',
24 text='text')
24 text='text')
25
25
26 def test_post_add(self):
26 def test_post_add(self):
27 post = self._create_post()
27 post = self._create_post()
28
28
29 self.assertIsNotNone(post)
29 self.assertIsNotNone(post)
30 self.assertEqual(boards.models.NO_PARENT, post.parent)
30 self.assertEqual(boards.models.NO_PARENT, post.parent)
31
31
32 def test_delete_post(self):
32 def test_delete_post(self):
33 post = self._create_post()
33 post = self._create_post()
34 post_id = post.id
34 post_id = post.id
35
35
36 Post.objects.delete_post(post)
36 Post.objects.delete_post(post)
37
37
38 self.assertFalse(Post.objects.exists(post_id))
38 self.assertFalse(Post.objects.exists(post_id))
39
39
40 def test_delete_posts_by_ip(self):
40 def test_delete_posts_by_ip(self):
41 post = self._create_post()
41 post = self._create_post()
42 post_id = post.id
42 post_id = post.id
43
43
44 Post.objects.delete_posts_by_ip('0.0.0.0')
44 Post.objects.delete_posts_by_ip('0.0.0.0')
45
45
46 self.assertFalse(Post.objects.exists(post_id))
46 self.assertFalse(Post.objects.exists(post_id))
47
47
48 # Authentication tests
48 # Authentication tests
49
49
50 def _create_test_user(self):
51 admin = Admin(name='test_username12313584353165',
52 password='test_userpassword135135512')
53
54 admin.save()
55 return admin
56
57 def test_get_thread(self):
50 def test_get_thread(self):
58 opening_post = self._create_post()
51 opening_post = self._create_post()
59 op_id = opening_post.id
60
52
61 for i in range(0, 2):
53 for i in range(0, 2):
62 Post.objects.create_post('title', 'text',
54 Post.objects.create_post('title', 'text',thread=opening_post)
63 parent_id=op_id)
64
55
65 thread = Post.objects.get_thread(op_id)
56 thread = Post.objects.get_thread(opening_post.id)
66
57
67 self.assertEqual(3, len(thread))
58 self.assertEqual(3, len(thread))
68
59
69 def test_create_post_with_tag(self):
60 def test_create_post_with_tag(self):
70 tag = Tag.objects.create(name='test_tag')
61 tag = Tag.objects.create(name='test_tag')
71 post = Post.objects.create_post(title='title', text='text', tags=[tag])
62 post = Post.objects.create_post(title='title', text='text', tags=[tag])
72 self.assertIsNotNone(post)
63 self.assertIsNotNone(post)
73
64
74 def test_thread_max_count(self):
65 def test_thread_max_count(self):
75 for i in range(settings.MAX_THREAD_COUNT + 1):
66 for i in range(settings.MAX_THREAD_COUNT + 1):
76 self._create_post()
67 self._create_post()
77
68
78 self.assertEqual(settings.MAX_THREAD_COUNT,
69 self.assertEqual(settings.MAX_THREAD_COUNT,
79 len(Post.objects.get_threads()))
70 len(Post.objects.get_threads()))
80
71
81 def test_pages(self):
72 def test_pages(self):
82 """Test that the thread list is properly split into pages"""
73 """Test that the thread list is properly split into pages"""
83
74
84 for i in range(settings.MAX_THREAD_COUNT):
75 for i in range(settings.MAX_THREAD_COUNT):
85 self._create_post()
76 self._create_post()
86
77
87 all_threads = Post.objects.get_threads()
78 all_threads = Post.objects.get_threads()
88
79
89 posts_in_second_page = Post.objects.get_threads(page=1)
80 posts_in_second_page = Post.objects.get_threads(page=1)
90 first_post = posts_in_second_page[0]
81 first_post = posts_in_second_page[0]
91
82
92 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
83 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
93 first_post.id)
84 first_post.id)
94
85
95 def test_post_validation(self):
86 def test_post_validation(self):
96 """Test the validation of the post form"""
87 """Test the validation of the post form"""
97
88
98 # Disable captcha for the test
89 # Disable captcha for the test
99 captcha_enabled = settings.ENABLE_CAPTCHA
90 captcha_enabled = settings.ENABLE_CAPTCHA
100 settings.ENABLE_CAPTCHA = False
91 settings.ENABLE_CAPTCHA = False
101
92
102 Post.objects.all().delete()
93 Post.objects.all().delete()
103
94
104 client = Client()
95 client = Client()
105
96
106 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
97 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
107 invalid_tags = u'$%_356 ---'
98 invalid_tags = u'$%_356 ---'
108
99
109 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
100 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
110 'text': TEST_TEXT,
101 'text': TEST_TEXT,
111 'tags': valid_tags})
102 'tags': valid_tags})
112 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
103 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
113 msg='Posting new message failed: got code ' +
104 msg='Posting new message failed: got code ' +
114 str(response.status_code))
105 str(response.status_code))
115
106
116 self.assertEqual(1, Post.objects.count(),
107 self.assertEqual(1, Post.objects.count(),
117 msg='No posts were created')
108 msg='No posts were created')
118
109
119 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
110 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
120 'tags': invalid_tags})
111 'tags': invalid_tags})
121 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
112 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
122 'where it should fail')
113 'where it should fail')
123
114
124 # TODO Some workaround and test for the "waiting" validation should
115 # TODO Some workaround and test for the "waiting" validation should
125 # exist here
116 # exist here
126 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
117 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
127 'tags': valid_tags})
118 'tags': valid_tags})
128 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
119 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
129 msg=u'Posting new message failed: got code ' +
120 msg=u'Posting new message failed: got code ' +
130 str(response.status_code))
121 str(response.status_code))
131
122
132 self.assertEqual(2, Post.objects.count(),
123 self.assertEqual(2, Post.objects.count(),
133 msg=u'No posts were created')
124 msg=u'No posts were created')
134
125
135 # Restore captcha setting
126 # Restore captcha setting
136 settings.ENABLE_CAPTCHA = captcha_enabled
127 settings.ENABLE_CAPTCHA = captcha_enabled
137
128
138 # TODO This test fails for now. We must check for 404.html instead of
129 # TODO This test fails for now. We must check for 404.html instead of
139 # code 404
130 # code 404
140 def test_404(self):
131 def test_404(self):
141 """Test receiving error 404 when opening a non-existent page"""
132 """Test receiving error 404 when opening a non-existent page"""
142
133
143 Post.objects.all().delete()
134 Post.objects.all().delete()
144 Tag.objects.all().delete()
135 Tag.objects.all().delete()
145
136
146 tag_name = u'test_tag'
137 tag_name = u'test_tag'
147 tags, = [Tag.objects.get_or_create(name=tag_name)]
138 tags, = [Tag.objects.get_or_create(name=tag_name)]
148 client = Client()
139 client = Client()
149
140
150 Post.objects.create_post('title', TEST_TEXT, tags=tags)
141 Post.objects.create_post('title', TEST_TEXT, tags=tags)
151
142
152 existing_post_id = Post.objects.all()[0].id
143 existing_post_id = Post.objects.all()[0].id
153 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
144 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
154 '/')
145 '/')
155 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
146 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
156 u'Cannot open existing thread')
147 u'Cannot open existing thread')
157
148
158 response_not_existing = client.get(THREAD_PAGE + str(
149 response_not_existing = client.get(THREAD_PAGE + str(
159 existing_post_id + 1) + '/')
150 existing_post_id + 1) + '/')
160 response_not_existing.get_full_path()
151 response_not_existing.get_full_path()
161 self.assertEqual(HTTP_CODE_NOT_FOUND,
152 self.assertEqual(HTTP_CODE_NOT_FOUND,
162 response_not_existing.status_code,
153 response_not_existing.status_code,
163 u'Not existing thread is opened')
154 u'Not existing thread is opened')
164
155
165 response_existing = client.get(TAG_PAGE + tag_name + '/')
156 response_existing = client.get(TAG_PAGE + tag_name + '/')
166 self.assertEqual(HTTP_CODE_OK,
157 self.assertEqual(HTTP_CODE_OK,
167 response_existing.status_code,
158 response_existing.status_code,
168 u'Cannot open existing tag')
159 u'Cannot open existing tag')
169
160
170 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
161 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
171 self.assertEqual(HTTP_CODE_NOT_FOUND,
162 self.assertEqual(HTTP_CODE_NOT_FOUND,
172 response_not_existing.status_code,
163 response_not_existing.status_code,
173 u'Not existing tag is opened')
164 u'Not existing tag is opened')
174
165
175 reply_id = Post.objects.create_post('', TEST_TEXT,
166 reply_id = Post.objects.create_post('', TEST_TEXT,
176 parent_id=existing_post_id)
167 parent_id=existing_post_id)
177 response_not_existing = client.get(THREAD_PAGE + str(
168 response_not_existing = client.get(THREAD_PAGE + str(
178 reply_id) + '/')
169 reply_id) + '/')
179 self.assertEqual(HTTP_CODE_NOT_FOUND,
170 self.assertEqual(HTTP_CODE_NOT_FOUND,
180 response_not_existing.status_code,
171 response_not_existing.status_code,
181 u'Not existing thread is opened')
172 u'Not existing thread is opened')
@@ -1,359 +1,358 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.http import HttpResponseRedirect
3 from django.http import HttpResponseRedirect
4 from django.template import RequestContext
4 from django.template import RequestContext
5 from django.shortcuts import render, redirect, get_object_or_404
5 from django.shortcuts import render, redirect, get_object_or_404
6 from django.utils import timezone
6 from django.utils import timezone
7
7
8 from boards import forms
8 from boards import forms
9 import boards
9 import boards
10 from boards import utils
10 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
13
13
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
15 from boards import authors
15 from boards import authors
16 import neboard
16 import neboard
17
17
18
18
19 def index(request, page=0):
19 def index(request, page=0):
20 context = _init_default_context(request)
20 context = _init_default_context(request)
21
21
22 if utils.need_include_captcha(request):
22 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
23 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
24 kwargs = {'request': request}
25 else:
25 else:
26 threadFormClass = ThreadForm
26 threadFormClass = ThreadForm
27 kwargs = {}
27 kwargs = {}
28
28
29 if request.method == 'POST':
29 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
30 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
31 error_class=PlainErrorList, **kwargs)
32 form.session = request.session
32 form.session = request.session
33
33
34 if form.is_valid():
34 if form.is_valid():
35 return _new_post(request, form)
35 return _new_post(request, form)
36 else:
36 else:
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
38
38
39 threads = []
39 threads = []
40 for thread in Post.objects.get_threads(page=int(page)):
40 for thread in Post.objects.get_threads(page=int(page)):
41 threads.append({'thread': thread,
41 threads.append({'thread': thread,
42 'bumpable': thread.can_bump()})
42 'bumpable': thread.can_bump()})
43
43
44 context['threads'] = None if len(threads) == 0 else threads
44 context['threads'] = None if len(threads) == 0 else threads
45 context['form'] = form
45 context['form'] = form
46 context['pages'] = range(Post.objects.get_thread_page_count())
46 context['pages'] = range(Post.objects.get_thread_page_count())
47
47
48 return render(request, 'boards/posting_general.html',
48 return render(request, 'boards/posting_general.html',
49 context)
49 context)
50
50
51
51
52 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
52 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
53 """Add a new post (in thread or as a reply)."""
53 """Add a new post (in thread or as a reply)."""
54
54
55 ip = _get_client_ip(request)
55 ip = _get_client_ip(request)
56 is_banned = Ban.objects.filter(ip=ip).count() > 0
56 is_banned = Ban.objects.filter(ip=ip).count() > 0
57
57
58 if is_banned:
58 if is_banned:
59 return redirect(you_are_banned)
59 return redirect(you_are_banned)
60
60
61 data = form.cleaned_data
61 data = form.cleaned_data
62
62
63 title = data['title']
63 title = data['title']
64 text = data['text']
64 text = data['text']
65
65
66 if 'image' in data.keys():
66 if 'image' in data.keys():
67 image = data['image']
67 image = data['image']
68 else:
68 else:
69 image = None
69 image = None
70
70
71 tags = []
71 tags = []
72
72
73 new_thread = thread_id == boards.models.NO_PARENT
73 new_thread = thread_id == boards.models.NO_PARENT
74 if new_thread:
74 if new_thread:
75 tag_strings = data['tags']
75 tag_strings = data['tags']
76
76
77 if tag_strings:
77 if tag_strings:
78 tag_strings = tag_strings.split(' ')
78 tag_strings = tag_strings.split(' ')
79 for tag_name in tag_strings:
79 for tag_name in tag_strings:
80 tag_name = tag_name.strip()
80 tag_name = tag_name.strip()
81 if len(tag_name) > 0:
81 if len(tag_name) > 0:
82 tag, created = Tag.objects.get_or_create(name=tag_name)
82 tag, created = Tag.objects.get_or_create(name=tag_name)
83 tags.append(tag)
83 tags.append(tag)
84
84
85 # TODO Add a possibility to define a link image instead of an image file.
85 # TODO Add a possibility to define a link image instead of an image file.
86 # If a link is given, download the image automatically.
86 # If a link is given, download the image automatically.
87
87
88 op = None if thread_id == boards.models.NO_PARENT else \
89 get_object_or_404(Post, id=thread_id)
88 post = Post.objects.create_post(title=title, text=text, ip=ip,
90 post = Post.objects.create_post(title=title, text=text, ip=ip,
89 parent_id=thread_id, image=image,
91 thread=op, image=image,
90 tags=tags)
92 tags=tags)
91
93
92 thread_to_show = (post.id if new_thread else thread_id)
94 thread_to_show = (post.id if new_thread else thread_id)
93
95
94 if new_thread:
96 if new_thread:
95 return redirect(thread, post_id=thread_to_show)
97 return redirect(thread, post_id=thread_to_show)
96 else:
98 else:
97 return redirect(reverse(thread,
99 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
98 kwargs={'post_id': thread_to_show}) + '#'
100 '#' + str(post.id))
99 + str(post.id))
100
101
101
102
102 def tag(request, tag_name, page=0):
103 def tag(request, tag_name, page=0):
103 """Get all tag threads (posts without a parent)."""
104 """Get all tag threads (posts without a parent)."""
104
105
105 tag = get_object_or_404(Tag, name=tag_name)
106 tag = get_object_or_404(Tag, name=tag_name)
106 threads = []
107 threads = []
107 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
108 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
108 threads.append({'thread': thread,
109 threads.append({'thread': thread,
109 'bumpable': thread.can_bump()})
110 'bumpable': thread.can_bump()})
110
111
111 if request.method == 'POST':
112 if request.method == 'POST':
112 form = ThreadForm(request.POST, request.FILES,
113 form = ThreadForm(request.POST, request.FILES,
113 error_class=PlainErrorList)
114 error_class=PlainErrorList)
114 if form.is_valid():
115 if form.is_valid():
115 return _new_post(request, form)
116 return _new_post(request, form)
116 else:
117 else:
117 form = forms.ThreadForm(initial={'tags': tag_name},
118 form = forms.ThreadForm(initial={'tags': tag_name},
118 error_class=PlainErrorList)
119 error_class=PlainErrorList)
119
120
120 context = _init_default_context(request)
121 context = _init_default_context(request)
121 context['threads'] = None if len(threads) == 0 else threads
122 context['threads'] = None if len(threads) == 0 else threads
122 context['tag'] = tag_name
123 context['tag'] = tag_name
123 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
124 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
124
125
125 context['form'] = form
126 context['form'] = form
126
127
127 return render(request, 'boards/posting_general.html',
128 return render(request, 'boards/posting_general.html',
128 context)
129 context)
129
130
130
131
131 def thread(request, post_id):
132 def thread(request, post_id):
132 """Get all thread posts"""
133 """Get all thread posts"""
133
134
134 if utils.need_include_captcha(request):
135 if utils.need_include_captcha(request):
135 postFormClass = PostCaptchaForm
136 postFormClass = PostCaptchaForm
136 kwargs = {'request': request}
137 kwargs = {'request': request}
137 else:
138 else:
138 postFormClass = PostForm
139 postFormClass = PostForm
139 kwargs = {}
140 kwargs = {}
140
141
141 if request.method == 'POST':
142 if request.method == 'POST':
142 form = postFormClass(request.POST, request.FILES,
143 form = postFormClass(request.POST, request.FILES,
143 error_class=PlainErrorList, **kwargs)
144 error_class=PlainErrorList, **kwargs)
144 form.session = request.session
145 form.session = request.session
145
146
146 if form.is_valid():
147 if form.is_valid():
147 return _new_post(request, form, post_id)
148 return _new_post(request, form, post_id)
148 else:
149 else:
149 form = postFormClass(error_class=PlainErrorList, **kwargs)
150 form = postFormClass(error_class=PlainErrorList, **kwargs)
150
151
151 posts = Post.objects.get_thread(post_id)
152 posts = Post.objects.get_thread(post_id)
152
153
153 context = _init_default_context(request)
154 context = _init_default_context(request)
154
155
155 context['posts'] = posts
156 context['posts'] = posts
156 context['form'] = form
157 context['form'] = form
157 context['bumpable'] = posts[0].can_bump()
158 context['bumpable'] = posts[0].can_bump()
158
159
159 return render(request, 'boards/thread.html', context)
160 return render(request, 'boards/thread.html', context)
160
161
161
162
162 def login(request):
163 def login(request):
163 """Log in with user id"""
164 """Log in with user id"""
164
165
165 context = _init_default_context(request)
166 context = _init_default_context(request)
166
167
167 if request.method == 'POST':
168 if request.method == 'POST':
168 form = LoginForm(request.POST, request.FILES,
169 form = LoginForm(request.POST, request.FILES,
169 error_class=PlainErrorList)
170 error_class=PlainErrorList)
170 if form.is_valid():
171 if form.is_valid():
171 user = User.objects.get(user_id=form.cleaned_data['user_id'])
172 user = User.objects.get(user_id=form.cleaned_data['user_id'])
172 request.session['user_id'] = user.id
173 request.session['user_id'] = user.id
173 return redirect(index)
174 return redirect(index)
174
175
175 else:
176 else:
176 form = LoginForm()
177 form = LoginForm()
177
178
178 context['form'] = form
179 context['form'] = form
179
180
180 return render(request, 'boards/login.html', context)
181 return render(request, 'boards/login.html', context)
181
182
182
183
183 def settings(request):
184 def settings(request):
184 """User's settings"""
185 """User's settings"""
185
186
186 context = _init_default_context(request)
187 context = _init_default_context(request)
187
188
188 if request.method == 'POST':
189 if request.method == 'POST':
189 form = SettingsForm(request.POST)
190 form = SettingsForm(request.POST)
190 if form.is_valid():
191 if form.is_valid():
191 selected_theme = form.cleaned_data['theme']
192 selected_theme = form.cleaned_data['theme']
192
193
193 user = _get_user(request)
194 user = _get_user(request)
194 user.save_setting('theme', selected_theme)
195 user.save_setting('theme', selected_theme)
195
196
196 return redirect(settings)
197 return redirect(settings)
197 else:
198 else:
198 selected_theme = _get_theme(request)
199 selected_theme = _get_theme(request)
199 form = SettingsForm(initial={'theme': selected_theme})
200 form = SettingsForm(initial={'theme': selected_theme})
200 context['form'] = form
201 context['form'] = form
201
202
202 return render(request, 'boards/settings.html', context)
203 return render(request, 'boards/settings.html', context)
203
204
204
205
205 def all_tags(request):
206 def all_tags(request):
206 """All tags list"""
207 """All tags list"""
207
208
208 context = _init_default_context(request)
209 context = _init_default_context(request)
209 context['all_tags'] = Tag.objects.get_not_empty_tags()
210 context['all_tags'] = Tag.objects.get_not_empty_tags()
210
211
211 return render(request, 'boards/tags.html', context)
212 return render(request, 'boards/tags.html', context)
212
213
213
214
214 def jump_to_post(request, post_id):
215 def jump_to_post(request, post_id):
215 """Determine thread in which the requested post is and open it's page"""
216 """Determine thread in which the requested post is and open it's page"""
216
217
217 post = get_object_or_404(Post, id=post_id)
218 post = get_object_or_404(Post, id=post_id)
218
219
219 if boards.models.NO_PARENT == post.parent:
220 if not post.thread:
220 return redirect(thread, post_id=post.id)
221 return redirect(thread, post_id=post.id)
221 else:
222 else:
222 # TODO Change this code to not use 'parent' field anymore
223 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
223 parent_thread = get_object_or_404(Post, id=post.parent)
224 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
225 + '#' + str(post.id))
224 + '#' + str(post.id))
226
225
227
226
228 def authors(request):
227 def authors(request):
229 context = _init_default_context(request)
228 context = _init_default_context(request)
230 context['authors'] = boards.authors.authors
229 context['authors'] = boards.authors.authors
231
230
232 return render(request, 'boards/authors.html', context)
231 return render(request, 'boards/authors.html', context)
233
232
234
233
235 def delete(request, post_id):
234 def delete(request, post_id):
236 user = _get_user(request)
235 user = _get_user(request)
237 post = get_object_or_404(Post, id=post_id)
236 post = get_object_or_404(Post, id=post_id)
238
237
239 if user.is_moderator():
238 if user.is_moderator():
240 # TODO Show confirmation page before deletion
239 # TODO Show confirmation page before deletion
241 Post.objects.delete_post(post)
240 Post.objects.delete_post(post)
242
241
243 if NO_PARENT == post.parent:
242 if not post.thread:
244 return _redirect_to_next(request)
243 return _redirect_to_next(request)
245 else:
244 else:
246 return redirect(thread, post_id=post.parent)
245 return redirect(thread, post_id=post.thread.id)
247
246
248
247
249 def ban(request, post_id):
248 def ban(request, post_id):
250 user = _get_user(request)
249 user = _get_user(request)
251 post = get_object_or_404(Post, id=post_id)
250 post = get_object_or_404(Post, id=post_id)
252
251
253 if user.is_moderator():
252 if user.is_moderator():
254 # TODO Show confirmation page before ban
253 # TODO Show confirmation page before ban
255 Ban.objects.get_or_create(ip=post.poster_ip)
254 Ban.objects.get_or_create(ip=post.poster_ip)
256
255
257 return _redirect_to_next(request)
256 return _redirect_to_next(request)
258
257
259
258
260 def you_are_banned(request):
259 def you_are_banned(request):
261 context = _init_default_context(request)
260 context = _init_default_context(request)
262 return render(request, 'boards/staticpages/banned.html', context)
261 return render(request, 'boards/staticpages/banned.html', context)
263
262
264
263
265 def page_404(request):
264 def page_404(request):
266 context = _init_default_context(request)
265 context = _init_default_context(request)
267 return render(request, 'boards/404.html', context)
266 return render(request, 'boards/404.html', context)
268
267
269
268
270 def tag_subscribe(request, tag_name):
269 def tag_subscribe(request, tag_name):
271 user = _get_user(request)
270 user = _get_user(request)
272 tag = get_object_or_404(Tag, name=tag_name)
271 tag = get_object_or_404(Tag, name=tag_name)
273
272
274 if not tag in user.fav_tags.all():
273 if not tag in user.fav_tags.all():
275 user.fav_tags.add(tag)
274 user.fav_tags.add(tag)
276
275
277 return redirect(all_tags)
276 return redirect(all_tags)
278
277
279
278
280 def tag_unsubscribe(request, tag_name):
279 def tag_unsubscribe(request, tag_name):
281 user = _get_user(request)
280 user = _get_user(request)
282 tag = get_object_or_404(Tag, name=tag_name)
281 tag = get_object_or_404(Tag, name=tag_name)
283
282
284 if tag in user.fav_tags.all():
283 if tag in user.fav_tags.all():
285 user.fav_tags.remove(tag)
284 user.fav_tags.remove(tag)
286
285
287 return redirect(all_tags)
286 return redirect(all_tags)
288
287
289
288
290 def static_page(request, name):
289 def static_page(request, name):
291 context = _init_default_context(request)
290 context = _init_default_context(request)
292 return render(request, 'boards/staticpages/' + name + '.html', context)
291 return render(request, 'boards/staticpages/' + name + '.html', context)
293
292
294
293
295 def _get_theme(request, user=None):
294 def _get_theme(request, user=None):
296 """Get user's CSS theme"""
295 """Get user's CSS theme"""
297
296
298 if not user:
297 if not user:
299 user = _get_user(request)
298 user = _get_user(request)
300 theme = user.get_setting('theme')
299 theme = user.get_setting('theme')
301 if not theme:
300 if not theme:
302 theme = neboard.settings.DEFAULT_THEME
301 theme = neboard.settings.DEFAULT_THEME
303
302
304 return theme
303 return theme
305
304
306
305
307 def _get_client_ip(request):
306 def _get_client_ip(request):
308 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
307 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
309 if x_forwarded_for:
308 if x_forwarded_for:
310 ip = x_forwarded_for.split(',')[-1].strip()
309 ip = x_forwarded_for.split(',')[-1].strip()
311 else:
310 else:
312 ip = request.META.get('REMOTE_ADDR')
311 ip = request.META.get('REMOTE_ADDR')
313 return ip
312 return ip
314
313
315
314
316 def _init_default_context(request):
315 def _init_default_context(request):
317 """Create context with default values that are used in most views"""
316 """Create context with default values that are used in most views"""
318
317
319 context = RequestContext(request)
318 context = RequestContext(request)
320
319
321 user = _get_user(request)
320 user = _get_user(request)
322 context['user'] = user
321 context['user'] = user
323 context['tags'] = user.get_sorted_fav_tags()
322 context['tags'] = user.get_sorted_fav_tags()
324 context['theme'] = _get_theme(request, user)
323 context['theme'] = _get_theme(request, user)
325
324
326 return context
325 return context
327
326
328
327
329 def _get_user(request):
328 def _get_user(request):
330 """Get current user from the session"""
329 """Get current user from the session"""
331
330
332 session = request.session
331 session = request.session
333 if not 'user_id' in session:
332 if not 'user_id' in session:
334 request.session.save()
333 request.session.save()
335
334
336 md5 = hashlib.md5()
335 md5 = hashlib.md5()
337 md5.update(session.session_key)
336 md5.update(session.session_key)
338 new_id = md5.hexdigest()
337 new_id = md5.hexdigest()
339
338
340 time_now = timezone.now()
339 time_now = timezone.now()
341 user = User.objects.create(user_id=new_id, rank=RANK_USER,
340 user = User.objects.create(user_id=new_id, rank=RANK_USER,
342 registration_time=time_now,
341 registration_time=time_now,
343 last_access_time=time_now)
342 last_access_time=time_now)
344
343
345 session['user_id'] = user.id
344 session['user_id'] = user.id
346 else:
345 else:
347 user = User.objects.get(id=session['user_id'])
346 user = User.objects.get(id=session['user_id'])
348 user.last_access_time = timezone.now()
347 user.last_access_time = timezone.now()
349 user.save()
348 user.save()
350
349
351 return user
350 return user
352
351
353
352
354 def _redirect_to_next(request):
353 def _redirect_to_next(request):
355 if 'next' in request.GET:
354 if 'next' in request.GET:
356 next_page = request.GET['next']
355 next_page = request.GET['next']
357 return HttpResponseRedirect(next_page)
356 return HttpResponseRedirect(next_page)
358 else:
357 else:
359 return redirect(index)
358 return redirect(index)
General Comments 0
You need to be logged in to leave comments. Login now