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