##// END OF EJS Templates
Loading updated posts in thread autoupdate
neko259 -
r364:e8323022 thread_autoupdate
parent child Browse files
Show More
@@ -1,434 +1,437 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import time
3 import time
4 import math
4 import math
5 from django.core.cache import cache
5 from django.core.cache import cache
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 from boards import settings as board_settings
12 from boards import settings as board_settings
13
13
14 from neboard import settings
14 from neboard import settings
15 import thumbs
15 import thumbs
16
16
17 import re
17 import re
18
18
19 BAN_REASON_MAX_LENGTH = 200
19 BAN_REASON_MAX_LENGTH = 200
20
20
21 BAN_REASON_AUTO = 'Auto'
21 BAN_REASON_AUTO = 'Auto'
22
22
23 IMAGE_THUMB_SIZE = (200, 150)
23 IMAGE_THUMB_SIZE = (200, 150)
24
24
25 TITLE_MAX_LENGTH = 50
25 TITLE_MAX_LENGTH = 50
26
26
27 DEFAULT_MARKUP_TYPE = 'markdown'
27 DEFAULT_MARKUP_TYPE = 'markdown'
28
28
29 NO_PARENT = -1
29 NO_PARENT = -1
30 NO_IP = '0.0.0.0'
30 NO_IP = '0.0.0.0'
31 UNKNOWN_UA = ''
31 UNKNOWN_UA = ''
32 ALL_PAGES = -1
32 ALL_PAGES = -1
33 OPENING_POST_POPULARITY_WEIGHT = 2
33 OPENING_POST_POPULARITY_WEIGHT = 2
34 IMAGES_DIRECTORY = 'images/'
34 IMAGES_DIRECTORY = 'images/'
35 FILE_EXTENSION_DELIMITER = '.'
35 FILE_EXTENSION_DELIMITER = '.'
36
36
37 RANK_ADMIN = 0
37 RANK_ADMIN = 0
38 RANK_MODERATOR = 10
38 RANK_MODERATOR = 10
39 RANK_USER = 100
39 RANK_USER = 100
40
40
41 SETTING_MODERATE = "moderate"
41 SETTING_MODERATE = "moderate"
42
42
43 REGEX_REPLY = re.compile('>>(\d+)')
43 REGEX_REPLY = re.compile('>>(\d+)')
44
44
45
45
46 class PostManager(models.Manager):
46 class PostManager(models.Manager):
47
47
48 def create_post(self, title, text, image=None, thread=None,
48 def create_post(self, title, text, image=None, thread=None,
49 ip=NO_IP, tags=None, user=None):
49 ip=NO_IP, tags=None, user=None):
50 post = self.create(title=title,
50 post = self.create(title=title,
51 text=text,
51 text=text,
52 pub_time=timezone.now(),
52 pub_time=timezone.now(),
53 thread=thread,
53 thread=thread,
54 image=image,
54 image=image,
55 poster_ip=ip,
55 poster_ip=ip,
56 poster_user_agent=UNKNOWN_UA,
56 poster_user_agent=UNKNOWN_UA,
57 last_edit_time=timezone.now(),
57 last_edit_time=timezone.now(),
58 bump_time=timezone.now(),
58 bump_time=timezone.now(),
59 user=user)
59 user=user)
60
60
61 if tags:
61 if tags:
62 map(post.tags.add, tags)
62 map(post.tags.add, tags)
63 for tag in tags:
63 for tag in tags:
64 tag.threads.add(post)
64 tag.threads.add(post)
65
65
66 if thread:
66 if thread:
67 thread.replies.add(post)
67 thread.replies.add(post)
68 thread.bump()
68 thread.bump()
69 thread.last_edit_time = timezone.now()
69 thread.last_edit_time = timezone.now()
70 thread.save()
70 thread.save()
71
71
72 #cache_key = thread.get_cache_key()
72 #cache_key = thread.get_cache_key()
73 #cache.delete(cache_key)
73 #cache.delete(cache_key)
74
74
75 else:
75 else:
76 self._delete_old_threads()
76 self._delete_old_threads()
77
77
78 self.connect_replies(post)
78 self.connect_replies(post)
79
79
80 return post
80 return post
81
81
82 def delete_post(self, post):
82 def delete_post(self, post):
83 if post.replies.count() > 0:
83 if post.replies.count() > 0:
84 map(self.delete_post, post.replies.all())
84 map(self.delete_post, post.replies.all())
85
85
86 # Update thread's last edit time (used as cache key)
86 # Update thread's last edit time (used as cache key)
87 thread = post.thread
87 thread = post.thread
88 if thread:
88 if thread:
89 thread.last_edit_time = timezone.now()
89 thread.last_edit_time = timezone.now()
90 thread.save()
90 thread.save()
91
91
92 #cache_key = thread.get_cache_key()
92 #cache_key = thread.get_cache_key()
93 #cache.delete(cache_key)
93 #cache.delete(cache_key)
94
94
95 post.delete()
95 post.delete()
96
96
97 def delete_posts_by_ip(self, ip):
97 def delete_posts_by_ip(self, ip):
98 posts = self.filter(poster_ip=ip)
98 posts = self.filter(poster_ip=ip)
99 map(self.delete_post, posts)
99 map(self.delete_post, posts)
100
100
101 def get_threads(self, tag=None, page=ALL_PAGES,
101 def get_threads(self, tag=None, page=ALL_PAGES,
102 order_by='-bump_time'):
102 order_by='-bump_time'):
103 if tag:
103 if tag:
104 threads = tag.threads
104 threads = tag.threads
105
105
106 if threads.count() == 0:
106 if threads.count() == 0:
107 raise Http404
107 raise Http404
108 else:
108 else:
109 threads = self.filter(thread=None)
109 threads = self.filter(thread=None)
110
110
111 threads = threads.order_by(order_by)
111 threads = threads.order_by(order_by)
112
112
113 if page != ALL_PAGES:
113 if page != ALL_PAGES:
114 thread_count = threads.count()
114 thread_count = threads.count()
115
115
116 if page < self._get_page_count(thread_count):
116 if page < self._get_page_count(thread_count):
117 start_thread = page * settings.THREADS_PER_PAGE
117 start_thread = page * settings.THREADS_PER_PAGE
118 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
118 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
119 thread_count)
119 thread_count)
120 threads = threads[start_thread:end_thread]
120 threads = threads[start_thread:end_thread]
121
121
122 return threads
122 return threads
123
123
124 def get_thread(self, opening_post_id):
124 def get_thread(self, opening_post_id):
125 try:
125 try:
126 opening_post = self.get(id=opening_post_id, thread=None)
126 opening_post = self.get(id=opening_post_id, thread=None)
127 except Post.DoesNotExist:
127 except Post.DoesNotExist:
128 raise Http404
128 raise Http404
129
129
130 #cache_key = opening_post.get_cache_key()
130 #cache_key = opening_post.get_cache_key()
131 #thread = cache.get(cache_key)
131 #thread = cache.get(cache_key)
132 #if thread:
132 #if thread:
133 # return thread
133 # return thread
134
134
135 if opening_post.replies:
135 if opening_post.replies:
136 thread = [opening_post]
136 thread = [opening_post]
137 thread.extend(opening_post.replies.all().order_by('pub_time'))
137 thread.extend(opening_post.replies.all().order_by('pub_time'))
138
138
139 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
139 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
140
140
141 return thread
141 return thread
142
142
143 def exists(self, post_id):
143 def exists(self, post_id):
144 posts = self.filter(id=post_id)
144 posts = self.filter(id=post_id)
145
145
146 return posts.count() > 0
146 return posts.count() > 0
147
147
148 def get_thread_page_count(self, tag=None):
148 def get_thread_page_count(self, tag=None):
149 if tag:
149 if tag:
150 threads = self.filter(thread=None, tags=tag)
150 threads = self.filter(thread=None, tags=tag)
151 else:
151 else:
152 threads = self.filter(thread=None)
152 threads = self.filter(thread=None)
153
153
154 return self._get_page_count(threads.count())
154 return self._get_page_count(threads.count())
155
155
156 def _delete_old_threads(self):
156 def _delete_old_threads(self):
157 """
157 """
158 Preserves maximum thread count. If there are too many threads,
158 Preserves maximum thread count. If there are too many threads,
159 delete the old ones.
159 delete the old ones.
160 """
160 """
161
161
162 # TODO Move old threads to the archive instead of deleting them.
162 # TODO Move old threads to the archive instead of deleting them.
163 # Maybe make some 'old' field in the model to indicate the thread
163 # Maybe make some 'old' field in the model to indicate the thread
164 # must not be shown and be able for replying.
164 # must not be shown and be able for replying.
165
165
166 threads = self.get_threads()
166 threads = self.get_threads()
167 thread_count = threads.count()
167 thread_count = threads.count()
168
168
169 if thread_count > settings.MAX_THREAD_COUNT:
169 if thread_count > settings.MAX_THREAD_COUNT:
170 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
170 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
171 old_threads = threads[thread_count - num_threads_to_delete:]
171 old_threads = threads[thread_count - num_threads_to_delete:]
172
172
173 map(self.delete_post, old_threads)
173 map(self.delete_post, old_threads)
174
174
175 def connect_replies(self, post):
175 def connect_replies(self, post):
176 """Connect replies to a post to show them as a refmap"""
176 """Connect replies to a post to show them as a refmap"""
177
177
178 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
178 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
179 id = reply_number.group(1)
179 id = reply_number.group(1)
180 ref_post = self.filter(id=id)
180 ref_post = self.filter(id=id)
181 if ref_post.count() > 0:
181 if ref_post.count() > 0:
182 ref_post[0].referenced_posts.add(post)
182 referenced_post = ref_post[0]
183 referenced_post.referenced_posts.add(post)
184 referenced_post.last_edit_time = timezone.now()
185 referenced_post.save()
183
186
184 def _get_page_count(self, thread_count):
187 def _get_page_count(self, thread_count):
185 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
188 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
186
189
187
190
188 class TagManager(models.Manager):
191 class TagManager(models.Manager):
189
192
190 def get_not_empty_tags(self):
193 def get_not_empty_tags(self):
191 tags = self.annotate(Count('threads')) \
194 tags = self.annotate(Count('threads')) \
192 .filter(threads__count__gt=0).order_by('name')
195 .filter(threads__count__gt=0).order_by('name')
193
196
194 return tags
197 return tags
195
198
196
199
197 class Tag(models.Model):
200 class Tag(models.Model):
198 """
201 """
199 A tag is a text node assigned to the post. The tag serves as a board
202 A tag is a text node assigned to the post. The tag serves as a board
200 section. There can be multiple tags for each message
203 section. There can be multiple tags for each message
201 """
204 """
202
205
203 objects = TagManager()
206 objects = TagManager()
204
207
205 name = models.CharField(max_length=100)
208 name = models.CharField(max_length=100)
206 threads = models.ManyToManyField('Post', null=True,
209 threads = models.ManyToManyField('Post', null=True,
207 blank=True, related_name='tag+')
210 blank=True, related_name='tag+')
208 linked = models.ForeignKey('Tag', null=True, blank=True)
211 linked = models.ForeignKey('Tag', null=True, blank=True)
209
212
210 def __unicode__(self):
213 def __unicode__(self):
211 return self.name
214 return self.name
212
215
213 def is_empty(self):
216 def is_empty(self):
214 return self.get_post_count() == 0
217 return self.get_post_count() == 0
215
218
216 def get_post_count(self):
219 def get_post_count(self):
217 return self.threads.count()
220 return self.threads.count()
218
221
219 def get_popularity(self):
222 def get_popularity(self):
220 posts_with_tag = Post.objects.get_threads(tag=self)
223 posts_with_tag = Post.objects.get_threads(tag=self)
221 reply_count = 0
224 reply_count = 0
222 for post in posts_with_tag:
225 for post in posts_with_tag:
223 reply_count += post.get_reply_count()
226 reply_count += post.get_reply_count()
224 reply_count += OPENING_POST_POPULARITY_WEIGHT
227 reply_count += OPENING_POST_POPULARITY_WEIGHT
225
228
226 return reply_count
229 return reply_count
227
230
228 def get_linked_tags(self):
231 def get_linked_tags(self):
229 tag_list = []
232 tag_list = []
230 self.get_linked_tags_list(tag_list)
233 self.get_linked_tags_list(tag_list)
231
234
232 return tag_list
235 return tag_list
233
236
234 def get_linked_tags_list(self, tag_list=[]):
237 def get_linked_tags_list(self, tag_list=[]):
235 """
238 """
236 Returns the list of tags linked to current. The list can be got
239 Returns the list of tags linked to current. The list can be got
237 through returned value or tag_list parameter
240 through returned value or tag_list parameter
238 """
241 """
239
242
240 linked_tag = self.linked
243 linked_tag = self.linked
241
244
242 if linked_tag and not (linked_tag in tag_list):
245 if linked_tag and not (linked_tag in tag_list):
243 tag_list.append(linked_tag)
246 tag_list.append(linked_tag)
244
247
245 linked_tag.get_linked_tags_list(tag_list)
248 linked_tag.get_linked_tags_list(tag_list)
246
249
247
250
248 class Post(models.Model):
251 class Post(models.Model):
249 """A post is a message."""
252 """A post is a message."""
250
253
251 objects = PostManager()
254 objects = PostManager()
252
255
253 def _update_image_filename(self, filename):
256 def _update_image_filename(self, filename):
254 """Get unique image filename"""
257 """Get unique image filename"""
255
258
256 path = IMAGES_DIRECTORY
259 path = IMAGES_DIRECTORY
257 new_name = str(int(time.mktime(time.gmtime())))
260 new_name = str(int(time.mktime(time.gmtime())))
258 new_name += str(int(random() * 1000))
261 new_name += str(int(random() * 1000))
259 new_name += FILE_EXTENSION_DELIMITER
262 new_name += FILE_EXTENSION_DELIMITER
260 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
263 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
261
264
262 return os.path.join(path, new_name)
265 return os.path.join(path, new_name)
263
266
264 title = models.CharField(max_length=TITLE_MAX_LENGTH)
267 title = models.CharField(max_length=TITLE_MAX_LENGTH)
265 pub_time = models.DateTimeField()
268 pub_time = models.DateTimeField()
266 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
269 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
267 escape_html=False)
270 escape_html=False)
268
271
269 image_width = models.IntegerField(default=0)
272 image_width = models.IntegerField(default=0)
270 image_height = models.IntegerField(default=0)
273 image_height = models.IntegerField(default=0)
271
274
272 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
275 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
273 blank=True, sizes=(IMAGE_THUMB_SIZE,),
276 blank=True, sizes=(IMAGE_THUMB_SIZE,),
274 width_field='image_width',
277 width_field='image_width',
275 height_field='image_height')
278 height_field='image_height')
276
279
277 poster_ip = models.GenericIPAddressField()
280 poster_ip = models.GenericIPAddressField()
278 poster_user_agent = models.TextField()
281 poster_user_agent = models.TextField()
279
282
280 thread = models.ForeignKey('Post', null=True, default=None)
283 thread = models.ForeignKey('Post', null=True, default=None)
281 tags = models.ManyToManyField(Tag)
284 tags = models.ManyToManyField(Tag)
282 last_edit_time = models.DateTimeField()
285 last_edit_time = models.DateTimeField()
283 bump_time = models.DateTimeField()
286 bump_time = models.DateTimeField()
284 user = models.ForeignKey('User', null=True, default=None)
287 user = models.ForeignKey('User', null=True, default=None)
285
288
286 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
289 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
287 blank=True, related_name='re+')
290 blank=True, related_name='re+')
288 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
291 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
289 null=True,
292 null=True,
290 blank=True, related_name='rfp+')
293 blank=True, related_name='rfp+')
291
294
292 def __unicode__(self):
295 def __unicode__(self):
293 return '#' + str(self.id) + ' ' + self.title + ' (' + \
296 return '#' + str(self.id) + ' ' + self.title + ' (' + \
294 self.text.raw[:50] + ')'
297 self.text.raw[:50] + ')'
295
298
296 def get_title(self):
299 def get_title(self):
297 title = self.title
300 title = self.title
298 if len(title) == 0:
301 if len(title) == 0:
299 title = self.text.raw[:20]
302 title = self.text.raw[:20]
300
303
301 return title
304 return title
302
305
303 def get_reply_count(self):
306 def get_reply_count(self):
304 return self.replies.count()
307 return self.replies.count()
305
308
306 def get_images_count(self):
309 def get_images_count(self):
307 images_count = 1 if self.image else 0
310 images_count = 1 if self.image else 0
308 images_count += self.replies.filter(image_width__gt=0).count()
311 images_count += self.replies.filter(image_width__gt=0).count()
309
312
310 return images_count
313 return images_count
311
314
312 def can_bump(self):
315 def can_bump(self):
313 """Check if the thread can be bumped by replying"""
316 """Check if the thread can be bumped by replying"""
314
317
315 post_count = self.get_reply_count() + 1
318 post_count = self.get_reply_count() + 1
316
319
317 return post_count <= settings.MAX_POSTS_PER_THREAD
320 return post_count <= settings.MAX_POSTS_PER_THREAD
318
321
319 def bump(self):
322 def bump(self):
320 """Bump (move to up) thread"""
323 """Bump (move to up) thread"""
321
324
322 if self.can_bump():
325 if self.can_bump():
323 self.bump_time = timezone.now()
326 self.bump_time = timezone.now()
324
327
325 def get_last_replies(self):
328 def get_last_replies(self):
326 if settings.LAST_REPLIES_COUNT > 0:
329 if settings.LAST_REPLIES_COUNT > 0:
327 reply_count = self.get_reply_count()
330 reply_count = self.get_reply_count()
328
331
329 if reply_count > 0:
332 if reply_count > 0:
330 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
333 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
331 reply_count)
334 reply_count)
332 last_replies = self.replies.all().order_by('pub_time')[
335 last_replies = self.replies.all().order_by('pub_time')[
333 reply_count - reply_count_to_show:]
336 reply_count - reply_count_to_show:]
334
337
335 return last_replies
338 return last_replies
336
339
337 def get_tags(self):
340 def get_tags(self):
338 """Get a sorted tag list"""
341 """Get a sorted tag list"""
339
342
340 return self.tags.order_by('name')
343 return self.tags.order_by('name')
341
344
342 def get_cache_key(self):
345 def get_cache_key(self):
343 return str(self.id) + str(self.last_edit_time.microsecond)
346 return str(self.id) + str(self.last_edit_time.microsecond)
344
347
345 def get_sorted_referenced_posts(self):
348 def get_sorted_referenced_posts(self):
346 return self.referenced_posts.order_by('id')
349 return self.referenced_posts.order_by('id')
347
350
348 def is_referenced(self):
351 def is_referenced(self):
349 return self.referenced_posts.count() > 0
352 return self.referenced_posts.count() > 0
350
353
351
354
352 class User(models.Model):
355 class User(models.Model):
353
356
354 user_id = models.CharField(max_length=50)
357 user_id = models.CharField(max_length=50)
355 rank = models.IntegerField()
358 rank = models.IntegerField()
356
359
357 registration_time = models.DateTimeField()
360 registration_time = models.DateTimeField()
358
361
359 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
362 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
360 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
363 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
361 blank=True)
364 blank=True)
362
365
363 def save_setting(self, name, value):
366 def save_setting(self, name, value):
364 setting, created = Setting.objects.get_or_create(name=name, user=self)
367 setting, created = Setting.objects.get_or_create(name=name, user=self)
365 setting.value = str(value)
368 setting.value = str(value)
366 setting.save()
369 setting.save()
367
370
368 return setting
371 return setting
369
372
370 def get_setting(self, name):
373 def get_setting(self, name):
371 if Setting.objects.filter(name=name, user=self).exists():
374 if Setting.objects.filter(name=name, user=self).exists():
372 setting = Setting.objects.get(name=name, user=self)
375 setting = Setting.objects.get(name=name, user=self)
373 setting_value = setting.value
376 setting_value = setting.value
374 else:
377 else:
375 setting_value = None
378 setting_value = None
376
379
377 return setting_value
380 return setting_value
378
381
379 def is_moderator(self):
382 def is_moderator(self):
380 return RANK_MODERATOR >= self.rank
383 return RANK_MODERATOR >= self.rank
381
384
382 def get_sorted_fav_tags(self):
385 def get_sorted_fav_tags(self):
383 cache_key = self._get_tag_cache_key()
386 cache_key = self._get_tag_cache_key()
384 fav_tags = cache.get(cache_key)
387 fav_tags = cache.get(cache_key)
385 if fav_tags:
388 if fav_tags:
386 return fav_tags
389 return fav_tags
387
390
388 tags = self.fav_tags.annotate(Count('threads'))\
391 tags = self.fav_tags.annotate(Count('threads'))\
389 .filter(threads__count__gt=0).order_by('name')
392 .filter(threads__count__gt=0).order_by('name')
390
393
391 if tags:
394 if tags:
392 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
395 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
393
396
394 return tags
397 return tags
395
398
396 def get_post_count(self):
399 def get_post_count(self):
397 return Post.objects.filter(user=self).count()
400 return Post.objects.filter(user=self).count()
398
401
399 def __unicode__(self):
402 def __unicode__(self):
400 return self.user_id + '(' + str(self.rank) + ')'
403 return self.user_id + '(' + str(self.rank) + ')'
401
404
402 def get_last_access_time(self):
405 def get_last_access_time(self):
403 posts = Post.objects.filter(user=self)
406 posts = Post.objects.filter(user=self)
404 if posts.count() > 0:
407 if posts.count() > 0:
405 return posts.latest('pub_time').pub_time
408 return posts.latest('pub_time').pub_time
406
409
407 def add_tag(self, tag):
410 def add_tag(self, tag):
408 self.fav_tags.add(tag)
411 self.fav_tags.add(tag)
409 cache.delete(self._get_tag_cache_key())
412 cache.delete(self._get_tag_cache_key())
410
413
411 def remove_tag(self, tag):
414 def remove_tag(self, tag):
412 self.fav_tags.remove(tag)
415 self.fav_tags.remove(tag)
413 cache.delete(self._get_tag_cache_key())
416 cache.delete(self._get_tag_cache_key())
414
417
415 def _get_tag_cache_key(self):
418 def _get_tag_cache_key(self):
416 return self.user_id + '_tags'
419 return self.user_id + '_tags'
417
420
418
421
419 class Setting(models.Model):
422 class Setting(models.Model):
420
423
421 name = models.CharField(max_length=50)
424 name = models.CharField(max_length=50)
422 value = models.CharField(max_length=50)
425 value = models.CharField(max_length=50)
423 user = models.ForeignKey(User)
426 user = models.ForeignKey(User)
424
427
425
428
426 class Ban(models.Model):
429 class Ban(models.Model):
427
430
428 ip = models.GenericIPAddressField()
431 ip = models.GenericIPAddressField()
429 reason = models.CharField(default=BAN_REASON_AUTO,
432 reason = models.CharField(default=BAN_REASON_AUTO,
430 max_length=BAN_REASON_MAX_LENGTH)
433 max_length=BAN_REASON_MAX_LENGTH)
431 can_read = models.BooleanField(default=True)
434 can_read = models.BooleanField(default=True)
432
435
433 def __unicode__(self):
436 def __unicode__(self):
434 return self.ip
437 return self.ip
@@ -1,63 +1,77 b''
1 var THREAD_UPDATE_DELAY = 10000;
1 var THREAD_UPDATE_DELAY = 10000;
2
2
3 var loading = false;
3 var loading = false;
4 var lastUpdateTime = null;
4 var lastUpdateTime = null;
5
5
6 function blink(node) {
6 function blink(node) {
7 var blinkCount = 2;
7 var blinkCount = 2;
8 var blinkDelay = 250;
8 var blinkDelay = 250;
9
9
10 var nodeToAnimate = node;
10 var nodeToAnimate = node;
11 for (var i = 0; i < blinkCount; i++) {
11 for (var i = 0; i < blinkCount; i++) {
12 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
12 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
13 }
13 }
14 }
14 }
15
15
16 function updateThread() {
16 function updateThread() {
17 if (loading) {
17 if (loading) {
18 return;
18 return;
19 }
19 }
20
20
21 loading = true;
21 loading = true;
22
22
23 var threadPosts = $('div.thread').children('.post');
23 var threadPosts = $('div.thread').children('.post');
24
24
25 var lastPost = threadPosts.last();
25 var lastPost = threadPosts.last();
26 var threadId = threadPosts.first().attr('id');
26 var threadId = threadPosts.first().attr('id');
27
27
28 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
28 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
29 $.getJSON(diffUrl)
29 $.getJSON(diffUrl)
30 .success(function(data) {
30 .success(function(data) {
31 var addedPosts = data.added;
31 var addedPosts = data.added;
32
32
33 for (var i = 0; i < addedPosts.length; i++) {
33 for (var i = 0; i < addedPosts.length; i++) {
34 var postText = addedPosts[i];
34 var postText = addedPosts[i];
35
35
36 var post = $(postText);
36 var post = $(postText);
37 post.appendTo(lastPost.parent());
37 post.appendTo(lastPost.parent());
38 addRefLinkPreview(post[0]);
38 addRefLinkPreview(post[0]);
39
39
40 lastPost = post;
40 lastPost = post;
41 blink(post);
41 blink(post);
42
43 }
42 }
44
43
44 var updatedPosts = data.updated;
45 for (var i = 0; i < updatedPosts.length; i++) {
46 var postText = updatedPosts[i];
47
48 var post = $(postText);
49 var postId = post.attr('id');
50
51 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
52
53 oldPost.replaceWith(post);
54 addRefLinkPreview(post[0]);
55
56 blink(post);
57 }
58
45 // TODO Process updated and deleted posts
59 // TODO Process updated and deleted posts
46
60
47 lastUpdateTime = data.last_update;
61 lastUpdateTime = data.last_update;
48 loading = false;
62 loading = false;
49 })
63 })
50 .error(function(data) {
64 .error(function(data) {
51 // TODO Show error message that server is unavailable?
65 // TODO Show error message that server is unavailable?
52
66
53 loading = false;
67 loading = false;
54 });
68 });
55 }
69 }
56
70
57 function initAutoupdate() {
71 function initAutoupdate() {
58 loading = false;
72 loading = false;
59
73
60 lastUpdateTime = $('.metapanel').attr('data-last-update');
74 lastUpdateTime = $('.metapanel').attr('data-last-update');
61
75
62 setInterval(updateThread, THREAD_UPDATE_DELAY);
76 setInterval(updateThread, THREAD_UPDATE_DELAY);
63 }
77 }
@@ -1,544 +1,549 b''
1 import hashlib
1 import hashlib
2 import json
2 import json
3 import string
3 import string
4 import time
4 import time
5
5
6 from datetime import datetime
6 from datetime import datetime
7
7
8 from django.core import serializers
8 from django.core import serializers
9 from django.core.urlresolvers import reverse
9 from django.core.urlresolvers import reverse
10 from django.http import HttpResponseRedirect
10 from django.http import HttpResponseRedirect
11 from django.http.response import HttpResponse
11 from django.http.response import HttpResponse
12 from django.template import RequestContext
12 from django.template import RequestContext
13 from django.shortcuts import render, redirect, get_object_or_404
13 from django.shortcuts import render, redirect, get_object_or_404
14 from django.utils import timezone
14 from django.utils import timezone
15 from django.db import transaction
15 from django.db import transaction
16
16
17 from boards import forms
17 from boards import forms
18 import boards
18 import boards
19 from boards import utils
19 from boards import utils
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
22
22
23 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
23 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
24 REGEX_REPLY
24 REGEX_REPLY
25 from boards import authors
25 from boards import authors
26 from boards.utils import get_client_ip
26 from boards.utils import get_client_ip
27 import neboard
27 import neboard
28 import re
28 import re
29
29
30 BAN_REASON_SPAM = 'Autoban: spam bot'
30 BAN_REASON_SPAM = 'Autoban: spam bot'
31
31
32
32
33 def index(request, page=0):
33 def index(request, page=0):
34 context = _init_default_context(request)
34 context = _init_default_context(request)
35
35
36 if utils.need_include_captcha(request):
36 if utils.need_include_captcha(request):
37 threadFormClass = ThreadCaptchaForm
37 threadFormClass = ThreadCaptchaForm
38 kwargs = {'request': request}
38 kwargs = {'request': request}
39 else:
39 else:
40 threadFormClass = ThreadForm
40 threadFormClass = ThreadForm
41 kwargs = {}
41 kwargs = {}
42
42
43 if request.method == 'POST':
43 if request.method == 'POST':
44 form = threadFormClass(request.POST, request.FILES,
44 form = threadFormClass(request.POST, request.FILES,
45 error_class=PlainErrorList, **kwargs)
45 error_class=PlainErrorList, **kwargs)
46 form.session = request.session
46 form.session = request.session
47
47
48 if form.is_valid():
48 if form.is_valid():
49 return _new_post(request, form)
49 return _new_post(request, form)
50 if form.need_to_ban:
50 if form.need_to_ban:
51 # Ban user because he is suspected to be a bot
51 # Ban user because he is suspected to be a bot
52 _ban_current_user(request)
52 _ban_current_user(request)
53 else:
53 else:
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
55
55
56 threads = []
56 threads = []
57 for thread in Post.objects.get_threads(page=int(page)):
57 for thread in Post.objects.get_threads(page=int(page)):
58 threads.append({
58 threads.append({
59 'thread': thread,
59 'thread': thread,
60 'bumpable': thread.can_bump(),
60 'bumpable': thread.can_bump(),
61 'last_replies': thread.get_last_replies(),
61 'last_replies': thread.get_last_replies(),
62 })
62 })
63
63
64 # TODO Make this generic for tag and threads list pages
64 # TODO Make this generic for tag and threads list pages
65 context['threads'] = None if len(threads) == 0 else threads
65 context['threads'] = None if len(threads) == 0 else threads
66 context['form'] = form
66 context['form'] = form
67
67
68 page_count = Post.objects.get_thread_page_count()
68 page_count = Post.objects.get_thread_page_count()
69 context['pages'] = range(page_count)
69 context['pages'] = range(page_count)
70 page = int(page)
70 page = int(page)
71 if page < page_count - 1:
71 if page < page_count - 1:
72 context['next_page'] = str(page + 1)
72 context['next_page'] = str(page + 1)
73 if page > 0:
73 if page > 0:
74 context['prev_page'] = str(page - 1)
74 context['prev_page'] = str(page - 1)
75
75
76 return render(request, 'boards/posting_general.html',
76 return render(request, 'boards/posting_general.html',
77 context)
77 context)
78
78
79
79
80 @transaction.commit_on_success
80 @transaction.commit_on_success
81 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
81 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
82 """Add a new post (in thread or as a reply)."""
82 """Add a new post (in thread or as a reply)."""
83
83
84 ip = get_client_ip(request)
84 ip = get_client_ip(request)
85 is_banned = Ban.objects.filter(ip=ip).exists()
85 is_banned = Ban.objects.filter(ip=ip).exists()
86
86
87 if is_banned:
87 if is_banned:
88 return redirect(you_are_banned)
88 return redirect(you_are_banned)
89
89
90 data = form.cleaned_data
90 data = form.cleaned_data
91
91
92 title = data['title']
92 title = data['title']
93 text = data['text']
93 text = data['text']
94
94
95 text = _remove_invalid_links(text)
95 text = _remove_invalid_links(text)
96
96
97 if 'image' in data.keys():
97 if 'image' in data.keys():
98 image = data['image']
98 image = data['image']
99 else:
99 else:
100 image = None
100 image = None
101
101
102 tags = []
102 tags = []
103
103
104 new_thread = thread_id == boards.models.NO_PARENT
104 new_thread = thread_id == boards.models.NO_PARENT
105 if new_thread:
105 if new_thread:
106 tag_strings = data['tags']
106 tag_strings = data['tags']
107
107
108 if tag_strings:
108 if tag_strings:
109 tag_strings = tag_strings.split(' ')
109 tag_strings = tag_strings.split(' ')
110 for tag_name in tag_strings:
110 for tag_name in tag_strings:
111 tag_name = string.lower(tag_name.strip())
111 tag_name = string.lower(tag_name.strip())
112 if len(tag_name) > 0:
112 if len(tag_name) > 0:
113 tag, created = Tag.objects.get_or_create(name=tag_name)
113 tag, created = Tag.objects.get_or_create(name=tag_name)
114 tags.append(tag)
114 tags.append(tag)
115
115
116 linked_tags = tag.get_linked_tags()
116 linked_tags = tag.get_linked_tags()
117 if len(linked_tags) > 0:
117 if len(linked_tags) > 0:
118 tags.extend(linked_tags)
118 tags.extend(linked_tags)
119
119
120 op = None if thread_id == boards.models.NO_PARENT else \
120 op = None if thread_id == boards.models.NO_PARENT else \
121 get_object_or_404(Post, id=thread_id)
121 get_object_or_404(Post, id=thread_id)
122 post = Post.objects.create_post(title=title, text=text, ip=ip,
122 post = Post.objects.create_post(title=title, text=text, ip=ip,
123 thread=op, image=image,
123 thread=op, image=image,
124 tags=tags, user=_get_user(request))
124 tags=tags, user=_get_user(request))
125
125
126 thread_to_show = (post.id if new_thread else thread_id)
126 thread_to_show = (post.id if new_thread else thread_id)
127
127
128 if new_thread:
128 if new_thread:
129 return redirect(thread, post_id=thread_to_show)
129 return redirect(thread, post_id=thread_to_show)
130 else:
130 else:
131 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
131 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
132 '#' + str(post.id))
132 '#' + str(post.id))
133
133
134
134
135 def tag(request, tag_name, page=0):
135 def tag(request, tag_name, page=0):
136 """
136 """
137 Get all tag threads. Threads are split in pages, so some page is
137 Get all tag threads. Threads are split in pages, so some page is
138 requested. Default page is 0.
138 requested. Default page is 0.
139 """
139 """
140
140
141 tag = get_object_or_404(Tag, name=tag_name)
141 tag = get_object_or_404(Tag, name=tag_name)
142 threads = []
142 threads = []
143 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
143 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
144 threads.append({
144 threads.append({
145 'thread': thread,
145 'thread': thread,
146 'bumpable': thread.can_bump(),
146 'bumpable': thread.can_bump(),
147 'last_replies': thread.get_last_replies(),
147 'last_replies': thread.get_last_replies(),
148 })
148 })
149
149
150 if request.method == 'POST':
150 if request.method == 'POST':
151 form = ThreadForm(request.POST, request.FILES,
151 form = ThreadForm(request.POST, request.FILES,
152 error_class=PlainErrorList)
152 error_class=PlainErrorList)
153 form.session = request.session
153 form.session = request.session
154
154
155 if form.is_valid():
155 if form.is_valid():
156 return _new_post(request, form)
156 return _new_post(request, form)
157 if form.need_to_ban:
157 if form.need_to_ban:
158 # Ban user because he is suspected to be a bot
158 # Ban user because he is suspected to be a bot
159 _ban_current_user(request)
159 _ban_current_user(request)
160 else:
160 else:
161 form = forms.ThreadForm(initial={'tags': tag_name},
161 form = forms.ThreadForm(initial={'tags': tag_name},
162 error_class=PlainErrorList)
162 error_class=PlainErrorList)
163
163
164 context = _init_default_context(request)
164 context = _init_default_context(request)
165 context['threads'] = None if len(threads) == 0 else threads
165 context['threads'] = None if len(threads) == 0 else threads
166 context['tag'] = tag
166 context['tag'] = tag
167
167
168 page_count = Post.objects.get_thread_page_count(tag=tag)
168 page_count = Post.objects.get_thread_page_count(tag=tag)
169 context['pages'] = range(page_count)
169 context['pages'] = range(page_count)
170 page = int(page)
170 page = int(page)
171 if page < page_count - 1:
171 if page < page_count - 1:
172 context['next_page'] = str(page + 1)
172 context['next_page'] = str(page + 1)
173 if page > 0:
173 if page > 0:
174 context['prev_page'] = str(page - 1)
174 context['prev_page'] = str(page - 1)
175
175
176 context['form'] = form
176 context['form'] = form
177
177
178 return render(request, 'boards/posting_general.html',
178 return render(request, 'boards/posting_general.html',
179 context)
179 context)
180
180
181
181
182 def thread(request, post_id):
182 def thread(request, post_id):
183 """Get all thread posts"""
183 """Get all thread posts"""
184
184
185 if utils.need_include_captcha(request):
185 if utils.need_include_captcha(request):
186 postFormClass = PostCaptchaForm
186 postFormClass = PostCaptchaForm
187 kwargs = {'request': request}
187 kwargs = {'request': request}
188 else:
188 else:
189 postFormClass = PostForm
189 postFormClass = PostForm
190 kwargs = {}
190 kwargs = {}
191
191
192 if request.method == 'POST':
192 if request.method == 'POST':
193 form = postFormClass(request.POST, request.FILES,
193 form = postFormClass(request.POST, request.FILES,
194 error_class=PlainErrorList, **kwargs)
194 error_class=PlainErrorList, **kwargs)
195 form.session = request.session
195 form.session = request.session
196
196
197 if form.is_valid():
197 if form.is_valid():
198 return _new_post(request, form, post_id)
198 return _new_post(request, form, post_id)
199 if form.need_to_ban:
199 if form.need_to_ban:
200 # Ban user because he is suspected to be a bot
200 # Ban user because he is suspected to be a bot
201 _ban_current_user(request)
201 _ban_current_user(request)
202 else:
202 else:
203 form = postFormClass(error_class=PlainErrorList, **kwargs)
203 form = postFormClass(error_class=PlainErrorList, **kwargs)
204
204
205 posts = Post.objects.get_thread(post_id)
205 posts = Post.objects.get_thread(post_id)
206
206
207 context = _init_default_context(request)
207 context = _init_default_context(request)
208
208
209 context['posts'] = posts
209 context['posts'] = posts
210 context['form'] = form
210 context['form'] = form
211 context['bumpable'] = posts[0].can_bump()
211 context['bumpable'] = posts[0].can_bump()
212 if context['bumpable']:
212 if context['bumpable']:
213 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
213 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
214 posts)
214 posts)
215 context['bumplimit_progress'] = str(
215 context['bumplimit_progress'] = str(
216 float(context['posts_left']) /
216 float(context['posts_left']) /
217 neboard.settings.MAX_POSTS_PER_THREAD * 100)
217 neboard.settings.MAX_POSTS_PER_THREAD * 100)
218 context["last_update"] = int(time.time() * 1000)
218 context["last_update"] = int(time.time() * 1000)
219
219
220 return render(request, 'boards/thread.html', context)
220 return render(request, 'boards/thread.html', context)
221
221
222
222
223 def login(request):
223 def login(request):
224 """Log in with user id"""
224 """Log in with user id"""
225
225
226 context = _init_default_context(request)
226 context = _init_default_context(request)
227
227
228 if request.method == 'POST':
228 if request.method == 'POST':
229 form = LoginForm(request.POST, request.FILES,
229 form = LoginForm(request.POST, request.FILES,
230 error_class=PlainErrorList)
230 error_class=PlainErrorList)
231 form.session = request.session
231 form.session = request.session
232
232
233 if form.is_valid():
233 if form.is_valid():
234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
235 request.session['user_id'] = user.id
235 request.session['user_id'] = user.id
236 return redirect(index)
236 return redirect(index)
237
237
238 else:
238 else:
239 form = LoginForm()
239 form = LoginForm()
240
240
241 context['form'] = form
241 context['form'] = form
242
242
243 return render(request, 'boards/login.html', context)
243 return render(request, 'boards/login.html', context)
244
244
245
245
246 def settings(request):
246 def settings(request):
247 """User's settings"""
247 """User's settings"""
248
248
249 context = _init_default_context(request)
249 context = _init_default_context(request)
250 user = _get_user(request)
250 user = _get_user(request)
251 is_moderator = user.is_moderator()
251 is_moderator = user.is_moderator()
252
252
253 if request.method == 'POST':
253 if request.method == 'POST':
254 with transaction.commit_on_success():
254 with transaction.commit_on_success():
255 if is_moderator:
255 if is_moderator:
256 form = ModeratorSettingsForm(request.POST,
256 form = ModeratorSettingsForm(request.POST,
257 error_class=PlainErrorList)
257 error_class=PlainErrorList)
258 else:
258 else:
259 form = SettingsForm(request.POST, error_class=PlainErrorList)
259 form = SettingsForm(request.POST, error_class=PlainErrorList)
260
260
261 if form.is_valid():
261 if form.is_valid():
262 selected_theme = form.cleaned_data['theme']
262 selected_theme = form.cleaned_data['theme']
263
263
264 user.save_setting('theme', selected_theme)
264 user.save_setting('theme', selected_theme)
265
265
266 if is_moderator:
266 if is_moderator:
267 moderate = form.cleaned_data['moderate']
267 moderate = form.cleaned_data['moderate']
268 user.save_setting(SETTING_MODERATE, moderate)
268 user.save_setting(SETTING_MODERATE, moderate)
269
269
270 return redirect(settings)
270 return redirect(settings)
271 else:
271 else:
272 selected_theme = _get_theme(request)
272 selected_theme = _get_theme(request)
273
273
274 if is_moderator:
274 if is_moderator:
275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
276 'moderate': context['moderator']},
276 'moderate': context['moderator']},
277 error_class=PlainErrorList)
277 error_class=PlainErrorList)
278 else:
278 else:
279 form = SettingsForm(initial={'theme': selected_theme},
279 form = SettingsForm(initial={'theme': selected_theme},
280 error_class=PlainErrorList)
280 error_class=PlainErrorList)
281
281
282 context['form'] = form
282 context['form'] = form
283
283
284 return render(request, 'boards/settings.html', context)
284 return render(request, 'boards/settings.html', context)
285
285
286
286
287 def all_tags(request):
287 def all_tags(request):
288 """All tags list"""
288 """All tags list"""
289
289
290 context = _init_default_context(request)
290 context = _init_default_context(request)
291 context['all_tags'] = Tag.objects.get_not_empty_tags()
291 context['all_tags'] = Tag.objects.get_not_empty_tags()
292
292
293 return render(request, 'boards/tags.html', context)
293 return render(request, 'boards/tags.html', context)
294
294
295
295
296 def jump_to_post(request, post_id):
296 def jump_to_post(request, post_id):
297 """Determine thread in which the requested post is and open it's page"""
297 """Determine thread in which the requested post is and open it's page"""
298
298
299 post = get_object_or_404(Post, id=post_id)
299 post = get_object_or_404(Post, id=post_id)
300
300
301 if not post.thread:
301 if not post.thread:
302 return redirect(thread, post_id=post.id)
302 return redirect(thread, post_id=post.id)
303 else:
303 else:
304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
305 + '#' + str(post.id))
305 + '#' + str(post.id))
306
306
307
307
308 def authors(request):
308 def authors(request):
309 """Show authors list"""
309 """Show authors list"""
310
310
311 context = _init_default_context(request)
311 context = _init_default_context(request)
312 context['authors'] = boards.authors.authors
312 context['authors'] = boards.authors.authors
313
313
314 return render(request, 'boards/authors.html', context)
314 return render(request, 'boards/authors.html', context)
315
315
316
316
317 @transaction.commit_on_success
317 @transaction.commit_on_success
318 def delete(request, post_id):
318 def delete(request, post_id):
319 """Delete post"""
319 """Delete post"""
320
320
321 user = _get_user(request)
321 user = _get_user(request)
322 post = get_object_or_404(Post, id=post_id)
322 post = get_object_or_404(Post, id=post_id)
323
323
324 if user.is_moderator():
324 if user.is_moderator():
325 # TODO Show confirmation page before deletion
325 # TODO Show confirmation page before deletion
326 Post.objects.delete_post(post)
326 Post.objects.delete_post(post)
327
327
328 if not post.thread:
328 if not post.thread:
329 return _redirect_to_next(request)
329 return _redirect_to_next(request)
330 else:
330 else:
331 return redirect(thread, post_id=post.thread.id)
331 return redirect(thread, post_id=post.thread.id)
332
332
333
333
334 @transaction.commit_on_success
334 @transaction.commit_on_success
335 def ban(request, post_id):
335 def ban(request, post_id):
336 """Ban user"""
336 """Ban user"""
337
337
338 user = _get_user(request)
338 user = _get_user(request)
339 post = get_object_or_404(Post, id=post_id)
339 post = get_object_or_404(Post, id=post_id)
340
340
341 if user.is_moderator():
341 if user.is_moderator():
342 # TODO Show confirmation page before ban
342 # TODO Show confirmation page before ban
343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
344 if created:
344 if created:
345 ban.reason = 'Banned for post ' + str(post_id)
345 ban.reason = 'Banned for post ' + str(post_id)
346 ban.save()
346 ban.save()
347
347
348 return _redirect_to_next(request)
348 return _redirect_to_next(request)
349
349
350
350
351 def you_are_banned(request):
351 def you_are_banned(request):
352 """Show the page that notifies that user is banned"""
352 """Show the page that notifies that user is banned"""
353
353
354 context = _init_default_context(request)
354 context = _init_default_context(request)
355
355
356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
357 context['ban_reason'] = ban.reason
357 context['ban_reason'] = ban.reason
358 return render(request, 'boards/staticpages/banned.html', context)
358 return render(request, 'boards/staticpages/banned.html', context)
359
359
360
360
361 def page_404(request):
361 def page_404(request):
362 """Show page 404 (not found error)"""
362 """Show page 404 (not found error)"""
363
363
364 context = _init_default_context(request)
364 context = _init_default_context(request)
365 return render(request, 'boards/404.html', context)
365 return render(request, 'boards/404.html', context)
366
366
367
367
368 @transaction.commit_on_success
368 @transaction.commit_on_success
369 def tag_subscribe(request, tag_name):
369 def tag_subscribe(request, tag_name):
370 """Add tag to favorites"""
370 """Add tag to favorites"""
371
371
372 user = _get_user(request)
372 user = _get_user(request)
373 tag = get_object_or_404(Tag, name=tag_name)
373 tag = get_object_or_404(Tag, name=tag_name)
374
374
375 if not tag in user.fav_tags.all():
375 if not tag in user.fav_tags.all():
376 user.add_tag(tag)
376 user.add_tag(tag)
377
377
378 return _redirect_to_next(request)
378 return _redirect_to_next(request)
379
379
380
380
381 @transaction.commit_on_success
381 @transaction.commit_on_success
382 def tag_unsubscribe(request, tag_name):
382 def tag_unsubscribe(request, tag_name):
383 """Remove tag from favorites"""
383 """Remove tag from favorites"""
384
384
385 user = _get_user(request)
385 user = _get_user(request)
386 tag = get_object_or_404(Tag, name=tag_name)
386 tag = get_object_or_404(Tag, name=tag_name)
387
387
388 if tag in user.fav_tags.all():
388 if tag in user.fav_tags.all():
389 user.remove_tag(tag)
389 user.remove_tag(tag)
390
390
391 return _redirect_to_next(request)
391 return _redirect_to_next(request)
392
392
393
393
394 def static_page(request, name):
394 def static_page(request, name):
395 """Show a static page that needs only tags list and a CSS"""
395 """Show a static page that needs only tags list and a CSS"""
396
396
397 context = _init_default_context(request)
397 context = _init_default_context(request)
398 return render(request, 'boards/staticpages/' + name + '.html', context)
398 return render(request, 'boards/staticpages/' + name + '.html', context)
399
399
400
400
401 def api_get_post(request, post_id):
401 def api_get_post(request, post_id):
402 """
402 """
403 Get the JSON of a post. This can be
403 Get the JSON of a post. This can be
404 used as and API for external clients.
404 used as and API for external clients.
405 """
405 """
406
406
407 post = get_object_or_404(Post, id=post_id)
407 post = get_object_or_404(Post, id=post_id)
408
408
409 json = serializers.serialize("json", [post], fields=(
409 json = serializers.serialize("json", [post], fields=(
410 "pub_time", "_text_rendered", "title", "text", "image",
410 "pub_time", "_text_rendered", "title", "text", "image",
411 "image_width", "image_height", "replies", "tags"
411 "image_width", "image_height", "replies", "tags"
412 ))
412 ))
413
413
414 return HttpResponse(content=json)
414 return HttpResponse(content=json)
415
415
416
416
417 def api_get_threaddiff(request, thread_id, last_update_time):
417 def api_get_threaddiff(request, thread_id, last_update_time):
418 thread = get_object_or_404(Post, id=thread_id)
418 thread = get_object_or_404(Post, id=thread_id)
419
419
420 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000)
420 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000)
421 added_posts = Post.objects.filter(thread=thread, pub_time__gt=filter_time)
422
421
423 json_data = {
422 json_data = {
424 'added': [],
423 'added': [],
424 'updated': [],
425 'last_update' : int(time.time() * 1000),
425 'last_update' : int(time.time() * 1000),
426 }
426 }
427 added_posts = Post.objects.filter(thread=thread, pub_time__gt=filter_time)
428 updated_posts = Post.objects.filter(thread=thread,
429 pub_time__lt=filter_time, last_edit_time__gt=filter_time)
427 for post in added_posts:
430 for post in added_posts:
428 json_data['added'].append(get_post(request, post.id).content.strip())
431 json_data['added'].append(get_post(request, post.id).content.strip())
432 for post in updated_posts:
433 json_data['updated'].append(get_post(request, post.id).content.strip())
429
434
430 return HttpResponse(content=json.dumps(json_data))
435 return HttpResponse(content=json.dumps(json_data))
431
436
432
437
433 def get_post(request, post_id):
438 def get_post(request, post_id):
434 """Get the html of a post. Used for popups."""
439 """Get the html of a post. Used for popups."""
435
440
436 post = get_object_or_404(Post, id=post_id)
441 post = get_object_or_404(Post, id=post_id)
437 thread = post.thread
442 thread = post.thread
438
443
439 context = RequestContext(request)
444 context = RequestContext(request)
440 context["post"] = post
445 context["post"] = post
441 context["can_bump"] = thread.can_bump()
446 context["can_bump"] = thread.can_bump()
442
447
443 return render(request, 'boards/post.html', context)
448 return render(request, 'boards/post.html', context)
444
449
445
450
446 def _get_theme(request, user=None):
451 def _get_theme(request, user=None):
447 """Get user's CSS theme"""
452 """Get user's CSS theme"""
448
453
449 if not user:
454 if not user:
450 user = _get_user(request)
455 user = _get_user(request)
451 theme = user.get_setting('theme')
456 theme = user.get_setting('theme')
452 if not theme:
457 if not theme:
453 theme = neboard.settings.DEFAULT_THEME
458 theme = neboard.settings.DEFAULT_THEME
454
459
455 return theme
460 return theme
456
461
457
462
458 def _init_default_context(request):
463 def _init_default_context(request):
459 """Create context with default values that are used in most views"""
464 """Create context with default values that are used in most views"""
460
465
461 context = RequestContext(request)
466 context = RequestContext(request)
462
467
463 user = _get_user(request)
468 user = _get_user(request)
464 context['user'] = user
469 context['user'] = user
465 context['tags'] = user.get_sorted_fav_tags()
470 context['tags'] = user.get_sorted_fav_tags()
466
471
467 theme = _get_theme(request, user)
472 theme = _get_theme(request, user)
468 context['theme'] = theme
473 context['theme'] = theme
469 context['theme_css'] = 'css/' + theme + '/base_page.css'
474 context['theme_css'] = 'css/' + theme + '/base_page.css'
470
475
471 # This shows the moderator panel
476 # This shows the moderator panel
472 moderate = user.get_setting(SETTING_MODERATE)
477 moderate = user.get_setting(SETTING_MODERATE)
473 if moderate == 'True':
478 if moderate == 'True':
474 context['moderator'] = user.is_moderator()
479 context['moderator'] = user.is_moderator()
475 else:
480 else:
476 context['moderator'] = False
481 context['moderator'] = False
477
482
478 return context
483 return context
479
484
480
485
481 def _get_user(request):
486 def _get_user(request):
482 """
487 """
483 Get current user from the session. If the user does not exist, create
488 Get current user from the session. If the user does not exist, create
484 a new one.
489 a new one.
485 """
490 """
486
491
487 session = request.session
492 session = request.session
488 if not 'user_id' in session:
493 if not 'user_id' in session:
489 request.session.save()
494 request.session.save()
490
495
491 md5 = hashlib.md5()
496 md5 = hashlib.md5()
492 md5.update(session.session_key)
497 md5.update(session.session_key)
493 new_id = md5.hexdigest()
498 new_id = md5.hexdigest()
494
499
495 time_now = timezone.now()
500 time_now = timezone.now()
496 user = User.objects.create(user_id=new_id, rank=RANK_USER,
501 user = User.objects.create(user_id=new_id, rank=RANK_USER,
497 registration_time=time_now)
502 registration_time=time_now)
498
503
499 session['user_id'] = user.id
504 session['user_id'] = user.id
500 else:
505 else:
501 user = User.objects.get(id=session['user_id'])
506 user = User.objects.get(id=session['user_id'])
502
507
503 return user
508 return user
504
509
505
510
506 def _redirect_to_next(request):
511 def _redirect_to_next(request):
507 """
512 """
508 If a 'next' parameter was specified, redirect to the next page. This is
513 If a 'next' parameter was specified, redirect to the next page. This is
509 used when the user is required to return to some page after the current
514 used when the user is required to return to some page after the current
510 view has finished its work.
515 view has finished its work.
511 """
516 """
512
517
513 if 'next' in request.GET:
518 if 'next' in request.GET:
514 next_page = request.GET['next']
519 next_page = request.GET['next']
515 return HttpResponseRedirect(next_page)
520 return HttpResponseRedirect(next_page)
516 else:
521 else:
517 return redirect(index)
522 return redirect(index)
518
523
519
524
520 @transaction.commit_on_success
525 @transaction.commit_on_success
521 def _ban_current_user(request):
526 def _ban_current_user(request):
522 """Add current user to the IP ban list"""
527 """Add current user to the IP ban list"""
523
528
524 ip = utils.get_client_ip(request)
529 ip = utils.get_client_ip(request)
525 ban, created = Ban.objects.get_or_create(ip=ip)
530 ban, created = Ban.objects.get_or_create(ip=ip)
526 if created:
531 if created:
527 ban.can_read = False
532 ban.can_read = False
528 ban.reason = BAN_REASON_SPAM
533 ban.reason = BAN_REASON_SPAM
529 ban.save()
534 ban.save()
530
535
531
536
532 def _remove_invalid_links(text):
537 def _remove_invalid_links(text):
533 """
538 """
534 Replace invalid links in posts so that they won't be parsed.
539 Replace invalid links in posts so that they won't be parsed.
535 Invalid links are links to non-existent posts
540 Invalid links are links to non-existent posts
536 """
541 """
537
542
538 for reply_number in re.finditer(REGEX_REPLY, text):
543 for reply_number in re.finditer(REGEX_REPLY, text):
539 post_id = reply_number.group(1)
544 post_id = reply_number.group(1)
540 post = Post.objects.filter(id=post_id)
545 post = Post.objects.filter(id=post_id)
541 if not post.exists():
546 if not post.exists():
542 text = string.replace(text, '>>' + id, id)
547 text = string.replace(text, '>>' + id, id)
543
548
544 return text
549 return text
General Comments 0
You need to be logged in to leave comments. Login now