##// END OF EJS Templates
Fixed thread autoarchiving
neko259 -
r603:1075472a default
parent child Browse files
Show More
@@ -1,431 +1,431 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import os
3 import os
4 from random import random
4 from random import random
5 import time
5 import time
6 import math
6 import math
7 import re
7 import re
8 import hashlib
8 import hashlib
9
9
10 from django.core.cache import cache
10 from django.core.cache import cache
11 from django.core.paginator import Paginator
11 from django.core.paginator import Paginator
12 from django.core.urlresolvers import reverse
12 from django.core.urlresolvers import reverse
13
13
14 from django.db import models, transaction
14 from django.db import models, transaction
15 from django.http import Http404
15 from django.http import Http404
16 from django.utils import timezone
16 from django.utils import timezone
17 from markupfield.fields import MarkupField
17 from markupfield.fields import MarkupField
18
18
19 from neboard import settings
19 from neboard import settings
20 from boards import thumbs
20 from boards import thumbs
21
21
22 MAX_TITLE_LENGTH = 50
22 MAX_TITLE_LENGTH = 50
23
23
24 APP_LABEL_BOARDS = 'boards'
24 APP_LABEL_BOARDS = 'boards'
25
25
26 CACHE_KEY_PPD = 'ppd'
26 CACHE_KEY_PPD = 'ppd'
27 CACHE_KEY_POST_URL = 'post_url'
27 CACHE_KEY_POST_URL = 'post_url'
28 CACHE_KEY_OPENING_POST = 'opening_post'
28 CACHE_KEY_OPENING_POST = 'opening_post'
29
29
30 POSTS_PER_DAY_RANGE = range(7)
30 POSTS_PER_DAY_RANGE = range(7)
31
31
32 BAN_REASON_AUTO = 'Auto'
32 BAN_REASON_AUTO = 'Auto'
33
33
34 IMAGE_THUMB_SIZE = (200, 150)
34 IMAGE_THUMB_SIZE = (200, 150)
35
35
36 TITLE_MAX_LENGTH = 50
36 TITLE_MAX_LENGTH = 50
37
37
38 DEFAULT_MARKUP_TYPE = 'markdown'
38 DEFAULT_MARKUP_TYPE = 'markdown'
39
39
40 NO_PARENT = -1
40 NO_PARENT = -1
41 NO_IP = '0.0.0.0'
41 NO_IP = '0.0.0.0'
42 UNKNOWN_UA = ''
42 UNKNOWN_UA = ''
43 ALL_PAGES = -1
43 ALL_PAGES = -1
44 IMAGES_DIRECTORY = 'images/'
44 IMAGES_DIRECTORY = 'images/'
45 FILE_EXTENSION_DELIMITER = '.'
45 FILE_EXTENSION_DELIMITER = '.'
46
46
47 SETTING_MODERATE = "moderate"
47 SETTING_MODERATE = "moderate"
48
48
49 REGEX_REPLY = re.compile('>>(\d+)')
49 REGEX_REPLY = re.compile('>>(\d+)')
50
50
51
51
52 class PostManager(models.Manager):
52 class PostManager(models.Manager):
53
53
54 def create_post(self, title, text, image=None, thread=None,
54 def create_post(self, title, text, image=None, thread=None,
55 ip=NO_IP, tags=None, user=None):
55 ip=NO_IP, tags=None, user=None):
56 """
56 """
57 Create new post
57 Create new post
58 """
58 """
59
59
60 posting_time = timezone.now()
60 posting_time = timezone.now()
61 if not thread:
61 if not thread:
62 thread = Thread.objects.create(bump_time=posting_time,
62 thread = Thread.objects.create(bump_time=posting_time,
63 last_edit_time=posting_time)
63 last_edit_time=posting_time)
64 else:
64 else:
65 thread.bump()
65 thread.bump()
66 thread.last_edit_time = posting_time
66 thread.last_edit_time = posting_time
67 thread.save()
67 thread.save()
68
68
69 post = self.create(title=title,
69 post = self.create(title=title,
70 text=text,
70 text=text,
71 pub_time=posting_time,
71 pub_time=posting_time,
72 thread_new=thread,
72 thread_new=thread,
73 image=image,
73 image=image,
74 poster_ip=ip,
74 poster_ip=ip,
75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
76 # last!
76 # last!
77 last_edit_time=posting_time,
77 last_edit_time=posting_time,
78 user=user)
78 user=user)
79
79
80 thread.replies.add(post)
80 thread.replies.add(post)
81 if tags:
81 if tags:
82 linked_tags = []
82 linked_tags = []
83 for tag in tags:
83 for tag in tags:
84 tag_linked_tags = tag.get_linked_tags()
84 tag_linked_tags = tag.get_linked_tags()
85 if len(tag_linked_tags) > 0:
85 if len(tag_linked_tags) > 0:
86 linked_tags.extend(tag_linked_tags)
86 linked_tags.extend(tag_linked_tags)
87
87
88 tags.extend(linked_tags)
88 tags.extend(linked_tags)
89 map(thread.add_tag, tags)
89 map(thread.add_tag, tags)
90
90
91 self._delete_old_threads()
91 self._delete_old_threads()
92 self.connect_replies(post)
92 self.connect_replies(post)
93
93
94 return post
94 return post
95
95
96 def delete_post(self, post):
96 def delete_post(self, post):
97 """
97 """
98 Delete post and update or delete its thread
98 Delete post and update or delete its thread
99 """
99 """
100
100
101 thread = post.thread_new
101 thread = post.thread_new
102
102
103 if post.is_opening():
103 if post.is_opening():
104 thread.delete_with_posts()
104 thread.delete_with_posts()
105 else:
105 else:
106 thread.last_edit_time = timezone.now()
106 thread.last_edit_time = timezone.now()
107 thread.save()
107 thread.save()
108
108
109 post.delete()
109 post.delete()
110
110
111 def delete_posts_by_ip(self, ip):
111 def delete_posts_by_ip(self, ip):
112 """
112 """
113 Delete all posts of the author with same IP
113 Delete all posts of the author with same IP
114 """
114 """
115
115
116 posts = self.filter(poster_ip=ip)
116 posts = self.filter(poster_ip=ip)
117 map(self.delete_post, posts)
117 map(self.delete_post, posts)
118
118
119 # TODO Move this method to thread manager
119 # TODO Move this method to thread manager
120 def _delete_old_threads(self):
120 def _delete_old_threads(self):
121 """
121 """
122 Preserves maximum thread count. If there are too many threads,
122 Preserves maximum thread count. If there are too many threads,
123 archive the old ones.
123 archive the old ones.
124 """
124 """
125
125
126 threads = Thread.objects.filter(archived=False)
126 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
127 thread_count = threads.count()
127 thread_count = threads.count()
128
128
129 if thread_count > settings.MAX_THREAD_COUNT:
129 if thread_count > settings.MAX_THREAD_COUNT:
130 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
130 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
131 old_threads = threads[thread_count - num_threads_to_delete:]
131 old_threads = threads[thread_count - num_threads_to_delete:]
132
132
133 for thread in old_threads:
133 for thread in old_threads:
134 thread.archived = True
134 thread.archived = True
135 thread.last_edit_time = timezone.now()
135 thread.last_edit_time = timezone.now()
136 thread.save()
136 thread.save()
137
137
138 def connect_replies(self, post):
138 def connect_replies(self, post):
139 """
139 """
140 Connect replies to a post to show them as a reflink map
140 Connect replies to a post to show them as a reflink map
141 """
141 """
142
142
143 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
143 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
144 post_id = reply_number.group(1)
144 post_id = reply_number.group(1)
145 ref_post = self.filter(id=post_id)
145 ref_post = self.filter(id=post_id)
146 if ref_post.count() > 0:
146 if ref_post.count() > 0:
147 referenced_post = ref_post[0]
147 referenced_post = ref_post[0]
148 referenced_post.referenced_posts.add(post)
148 referenced_post.referenced_posts.add(post)
149 referenced_post.last_edit_time = post.pub_time
149 referenced_post.last_edit_time = post.pub_time
150 referenced_post.save()
150 referenced_post.save()
151
151
152 referenced_thread = referenced_post.thread_new
152 referenced_thread = referenced_post.thread_new
153 referenced_thread.last_edit_time = post.pub_time
153 referenced_thread.last_edit_time = post.pub_time
154 referenced_thread.save()
154 referenced_thread.save()
155
155
156 def get_posts_per_day(self):
156 def get_posts_per_day(self):
157 """
157 """
158 Get average count of posts per day for the last 7 days
158 Get average count of posts per day for the last 7 days
159 """
159 """
160
160
161 today = date.today()
161 today = date.today()
162 ppd = cache.get(CACHE_KEY_PPD + str(today))
162 ppd = cache.get(CACHE_KEY_PPD + str(today))
163 if ppd:
163 if ppd:
164 return ppd
164 return ppd
165
165
166 posts_per_days = []
166 posts_per_days = []
167 for i in POSTS_PER_DAY_RANGE:
167 for i in POSTS_PER_DAY_RANGE:
168 day_end = today - timedelta(i + 1)
168 day_end = today - timedelta(i + 1)
169 day_start = today - timedelta(i + 2)
169 day_start = today - timedelta(i + 2)
170
170
171 day_time_start = timezone.make_aware(datetime.combine(day_start,
171 day_time_start = timezone.make_aware(datetime.combine(day_start,
172 dtime()), timezone.get_current_timezone())
172 dtime()), timezone.get_current_timezone())
173 day_time_end = timezone.make_aware(datetime.combine(day_end,
173 day_time_end = timezone.make_aware(datetime.combine(day_end,
174 dtime()), timezone.get_current_timezone())
174 dtime()), timezone.get_current_timezone())
175
175
176 posts_per_days.append(float(self.filter(
176 posts_per_days.append(float(self.filter(
177 pub_time__lte=day_time_end,
177 pub_time__lte=day_time_end,
178 pub_time__gte=day_time_start).count()))
178 pub_time__gte=day_time_start).count()))
179
179
180 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
180 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
181 len(posts_per_days))
181 len(posts_per_days))
182 cache.set(CACHE_KEY_PPD + str(today), ppd)
182 cache.set(CACHE_KEY_PPD + str(today), ppd)
183 return ppd
183 return ppd
184
184
185
185
186 class Post(models.Model):
186 class Post(models.Model):
187 """A post is a message."""
187 """A post is a message."""
188
188
189 objects = PostManager()
189 objects = PostManager()
190
190
191 class Meta:
191 class Meta:
192 app_label = APP_LABEL_BOARDS
192 app_label = APP_LABEL_BOARDS
193
193
194 # TODO Save original file name to some field
194 # TODO Save original file name to some field
195 def _update_image_filename(self, filename):
195 def _update_image_filename(self, filename):
196 """Get unique image filename"""
196 """Get unique image filename"""
197
197
198 path = IMAGES_DIRECTORY
198 path = IMAGES_DIRECTORY
199 new_name = str(int(time.mktime(time.gmtime())))
199 new_name = str(int(time.mktime(time.gmtime())))
200 new_name += str(int(random() * 1000))
200 new_name += str(int(random() * 1000))
201 new_name += FILE_EXTENSION_DELIMITER
201 new_name += FILE_EXTENSION_DELIMITER
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
203
203
204 return os.path.join(path, new_name)
204 return os.path.join(path, new_name)
205
205
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
207 pub_time = models.DateTimeField()
207 pub_time = models.DateTimeField()
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
209 escape_html=False)
209 escape_html=False)
210
210
211 image_width = models.IntegerField(default=0)
211 image_width = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
213
213
214 image_pre_width = models.IntegerField(default=0)
214 image_pre_width = models.IntegerField(default=0)
215 image_pre_height = models.IntegerField(default=0)
215 image_pre_height = models.IntegerField(default=0)
216
216
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 width_field='image_width',
219 width_field='image_width',
220 height_field='image_height',
220 height_field='image_height',
221 preview_width_field='image_pre_width',
221 preview_width_field='image_pre_width',
222 preview_height_field='image_pre_height')
222 preview_height_field='image_pre_height')
223 image_hash = models.CharField(max_length=36)
223 image_hash = models.CharField(max_length=36)
224
224
225 poster_ip = models.GenericIPAddressField()
225 poster_ip = models.GenericIPAddressField()
226 poster_user_agent = models.TextField()
226 poster_user_agent = models.TextField()
227
227
228 thread = models.ForeignKey('Post', null=True, default=None)
228 thread = models.ForeignKey('Post', null=True, default=None)
229 thread_new = models.ForeignKey('Thread', null=True, default=None)
229 thread_new = models.ForeignKey('Thread', null=True, default=None)
230 last_edit_time = models.DateTimeField()
230 last_edit_time = models.DateTimeField()
231 user = models.ForeignKey('User', null=True, default=None)
231 user = models.ForeignKey('User', null=True, default=None)
232
232
233 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
233 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
234 null=True,
234 null=True,
235 blank=True, related_name='rfp+')
235 blank=True, related_name='rfp+')
236
236
237 def __unicode__(self):
237 def __unicode__(self):
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
239 self.text.raw[:50] + ')'
239 self.text.raw[:50] + ')'
240
240
241 def get_title(self):
241 def get_title(self):
242 title = self.title
242 title = self.title
243 if len(title) == 0:
243 if len(title) == 0:
244 title = self.text.rendered
244 title = self.text.rendered
245
245
246 return title
246 return title
247
247
248 def get_sorted_referenced_posts(self):
248 def get_sorted_referenced_posts(self):
249 return self.referenced_posts.order_by('id')
249 return self.referenced_posts.order_by('id')
250
250
251 def is_referenced(self):
251 def is_referenced(self):
252 return self.referenced_posts.all().exists()
252 return self.referenced_posts.all().exists()
253
253
254 def is_opening(self):
254 def is_opening(self):
255 return self.thread_new.get_opening_post() == self
255 return self.thread_new.get_opening_post() == self
256
256
257 def save(self, *args, **kwargs):
257 def save(self, *args, **kwargs):
258 """
258 """
259 Save the model and compute the image hash
259 Save the model and compute the image hash
260 """
260 """
261
261
262 if not self.pk and self.image:
262 if not self.pk and self.image:
263 md5 = hashlib.md5()
263 md5 = hashlib.md5()
264 for chunk in self.image.chunks():
264 for chunk in self.image.chunks():
265 md5.update(chunk)
265 md5.update(chunk)
266 self.image_hash = md5.hexdigest()
266 self.image_hash = md5.hexdigest()
267 super(Post, self).save(*args, **kwargs)
267 super(Post, self).save(*args, **kwargs)
268
268
269 @transaction.atomic
269 @transaction.atomic
270 def add_tag(self, tag):
270 def add_tag(self, tag):
271 edit_time = timezone.now()
271 edit_time = timezone.now()
272
272
273 thread = self.thread_new
273 thread = self.thread_new
274 thread.add_tag(tag)
274 thread.add_tag(tag)
275 self.last_edit_time = edit_time
275 self.last_edit_time = edit_time
276 self.save()
276 self.save()
277
277
278 thread.last_edit_time = edit_time
278 thread.last_edit_time = edit_time
279 thread.save()
279 thread.save()
280
280
281 @transaction.atomic
281 @transaction.atomic
282 def remove_tag(self, tag):
282 def remove_tag(self, tag):
283 edit_time = timezone.now()
283 edit_time = timezone.now()
284
284
285 thread = self.thread_new
285 thread = self.thread_new
286 thread.remove_tag(tag)
286 thread.remove_tag(tag)
287 self.last_edit_time = edit_time
287 self.last_edit_time = edit_time
288 self.save()
288 self.save()
289
289
290 thread.last_edit_time = edit_time
290 thread.last_edit_time = edit_time
291 thread.save()
291 thread.save()
292
292
293 def get_url(self):
293 def get_url(self):
294 """
294 """
295 Get full url to this post
295 Get full url to this post
296 """
296 """
297
297
298 cache_key = CACHE_KEY_POST_URL + str(self.id)
298 cache_key = CACHE_KEY_POST_URL + str(self.id)
299 link = cache.get(cache_key)
299 link = cache.get(cache_key)
300
300
301 if not link:
301 if not link:
302 opening_post = self.thread_new.get_opening_post()
302 opening_post = self.thread_new.get_opening_post()
303 if self != opening_post:
303 if self != opening_post:
304 link = reverse('thread',
304 link = reverse('thread',
305 kwargs={'post_id': opening_post.id}) + '#' + str(
305 kwargs={'post_id': opening_post.id}) + '#' + str(
306 self.id)
306 self.id)
307 else:
307 else:
308 link = reverse('thread', kwargs={'post_id': self.id})
308 link = reverse('thread', kwargs={'post_id': self.id})
309
309
310 cache.set(cache_key, link)
310 cache.set(cache_key, link)
311
311
312 return link
312 return link
313
313
314
314
315 class Thread(models.Model):
315 class Thread(models.Model):
316
316
317 class Meta:
317 class Meta:
318 app_label = APP_LABEL_BOARDS
318 app_label = APP_LABEL_BOARDS
319
319
320 tags = models.ManyToManyField('Tag')
320 tags = models.ManyToManyField('Tag')
321 bump_time = models.DateTimeField()
321 bump_time = models.DateTimeField()
322 last_edit_time = models.DateTimeField()
322 last_edit_time = models.DateTimeField()
323 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
323 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
324 blank=True, related_name='tre+')
324 blank=True, related_name='tre+')
325 archived = models.BooleanField(default=False)
325 archived = models.BooleanField(default=False)
326
326
327 def get_tags(self):
327 def get_tags(self):
328 """
328 """
329 Get a sorted tag list
329 Get a sorted tag list
330 """
330 """
331
331
332 return self.tags.order_by('name')
332 return self.tags.order_by('name')
333
333
334 def bump(self):
334 def bump(self):
335 """
335 """
336 Bump (move to up) thread
336 Bump (move to up) thread
337 """
337 """
338
338
339 if self.can_bump():
339 if self.can_bump():
340 self.bump_time = timezone.now()
340 self.bump_time = timezone.now()
341
341
342 def get_reply_count(self):
342 def get_reply_count(self):
343 return self.replies.count()
343 return self.replies.count()
344
344
345 def get_images_count(self):
345 def get_images_count(self):
346 return self.replies.filter(image_width__gt=0).count()
346 return self.replies.filter(image_width__gt=0).count()
347
347
348 def can_bump(self):
348 def can_bump(self):
349 """
349 """
350 Check if the thread can be bumped by replying
350 Check if the thread can be bumped by replying
351 """
351 """
352
352
353 if self.archived:
353 if self.archived:
354 return False
354 return False
355
355
356 post_count = self.get_reply_count()
356 post_count = self.get_reply_count()
357
357
358 return post_count < settings.MAX_POSTS_PER_THREAD
358 return post_count < settings.MAX_POSTS_PER_THREAD
359
359
360 def delete_with_posts(self):
360 def delete_with_posts(self):
361 """
361 """
362 Completely delete thread and all its posts
362 Completely delete thread and all its posts
363 """
363 """
364
364
365 if self.replies.count() > 0:
365 if self.replies.count() > 0:
366 self.replies.all().delete()
366 self.replies.all().delete()
367
367
368 self.delete()
368 self.delete()
369
369
370 def get_last_replies(self):
370 def get_last_replies(self):
371 """
371 """
372 Get last replies, not including opening post
372 Get last replies, not including opening post
373 """
373 """
374
374
375 if settings.LAST_REPLIES_COUNT > 0:
375 if settings.LAST_REPLIES_COUNT > 0:
376 reply_count = self.get_reply_count()
376 reply_count = self.get_reply_count()
377
377
378 if reply_count > 0:
378 if reply_count > 0:
379 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
379 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
380 reply_count - 1)
380 reply_count - 1)
381 last_replies = self.replies.all().order_by('pub_time')[
381 last_replies = self.replies.all().order_by('pub_time')[
382 reply_count - reply_count_to_show:]
382 reply_count - reply_count_to_show:]
383
383
384 return last_replies
384 return last_replies
385
385
386 def get_skipped_replies_count(self):
386 def get_skipped_replies_count(self):
387 last_replies = self.get_last_replies()
387 last_replies = self.get_last_replies()
388 return self.get_reply_count() - len(last_replies) - 1
388 return self.get_reply_count() - len(last_replies) - 1
389
389
390 def get_replies(self):
390 def get_replies(self):
391 """
391 """
392 Get sorted thread posts
392 Get sorted thread posts
393 """
393 """
394
394
395 return self.replies.all().order_by('pub_time')
395 return self.replies.all().order_by('pub_time')
396
396
397 def add_tag(self, tag):
397 def add_tag(self, tag):
398 """
398 """
399 Connect thread to a tag and tag to a thread
399 Connect thread to a tag and tag to a thread
400 """
400 """
401
401
402 self.tags.add(tag)
402 self.tags.add(tag)
403 tag.threads.add(self)
403 tag.threads.add(self)
404
404
405 def remove_tag(self, tag):
405 def remove_tag(self, tag):
406 self.tags.remove(tag)
406 self.tags.remove(tag)
407 tag.threads.remove(self)
407 tag.threads.remove(self)
408
408
409 def get_opening_post(self):
409 def get_opening_post(self):
410 """
410 """
411 Get first post of the thread
411 Get first post of the thread
412 """
412 """
413
413
414 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
414 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
415 # opening_post = cache.get(cache_key)
415 # opening_post = cache.get(cache_key)
416 # if not opening_post:
416 # if not opening_post:
417 opening_post = self.get_replies()[0]
417 opening_post = self.get_replies()[0]
418 # cache.set(cache_key, opening_post)
418 # cache.set(cache_key, opening_post)
419
419
420 return opening_post
420 return opening_post
421
421
422 def __unicode__(self):
422 def __unicode__(self):
423 return str(self.id)
423 return str(self.id)
424
424
425 def get_pub_time(self):
425 def get_pub_time(self):
426 """
426 """
427 Thread does not have its own pub time, so we need to get it from
427 Thread does not have its own pub time, so we need to get it from
428 the opening post
428 the opening post
429 """
429 """
430
430
431 return self.get_opening_post().pub_time
431 return self.get_opening_post().pub_time
@@ -1,93 +1,94 b''
1 from boards.models import Thread, Post
1 from boards.models import Thread, Post
2 from django.db import models
2 from django.db import models
3 from django.db.models import Count
3 from django.db.models import Count
4
4
5 __author__ = 'neko259'
5 __author__ = 'neko259'
6
6
7 MAX_TAG_FONT = 1
7 MAX_TAG_FONT = 1
8 MIN_TAG_FONT = 0.2
8 MIN_TAG_FONT = 0.2
9
9
10 TAG_POPULARITY_MULTIPLIER = 20
10 TAG_POPULARITY_MULTIPLIER = 20
11
11
12 ARCHIVE_POPULARITY_MODIFIER = 0.5
12 ARCHIVE_POPULARITY_MODIFIER = 0.5
13
13
14
14
15 class TagManager(models.Manager):
15 class TagManager(models.Manager):
16
16
17 def get_not_empty_tags(self):
17 def get_not_empty_tags(self):
18 tags = self.annotate(Count('threads')) \
18 tags = self.annotate(Count('threads')) \
19 .filter(threads__count__gt=0).order_by('name')
19 .filter(threads__count__gt=0).order_by('name')
20
20
21 return tags
21 return tags
22
22
23
23
24 class Tag(models.Model):
24 class Tag(models.Model):
25 """
25 """
26 A tag is a text node assigned to the thread. The tag serves as a board
26 A tag is a text node assigned to the thread. The tag serves as a board
27 section. There can be multiple tags for each thread
27 section. There can be multiple tags for each thread
28 """
28 """
29
29
30 objects = TagManager()
30 objects = TagManager()
31
31
32 class Meta:
32 class Meta:
33 app_label = 'boards'
33 app_label = 'boards'
34
34
35 name = models.CharField(max_length=100)
35 name = models.CharField(max_length=100)
36 threads = models.ManyToManyField(Thread, null=True,
36 threads = models.ManyToManyField(Thread, null=True,
37 blank=True, related_name='tag+')
37 blank=True, related_name='tag+')
38 linked = models.ForeignKey('Tag', null=True, blank=True)
38 linked = models.ForeignKey('Tag', null=True, blank=True)
39
39
40 def __unicode__(self):
40 def __unicode__(self):
41 return self.name
41 return self.name
42
42
43 def is_empty(self):
43 def is_empty(self):
44 return self.get_post_count() == 0
44 return self.get_post_count() == 0
45
45
46 def get_post_count(self):
46 def get_post_count(self):
47 return self.threads.count()
47 return self.threads.count()
48
48
49 def get_popularity(self):
49 def get_popularity(self):
50 all_post_count = Post.objects.all().count()
50 all_post_count = Post.objects.all().count()
51
51
52 tag_reply_count = 0.0
52 tag_reply_count = 0.0
53
53
54 for thread in self.threads.all():
54 for thread in self.threads.all():
55 if thread.archived:
55 if thread.archived:
56 modifier = ARCHIVE_POPULARITY_MODIFIER
56 modifier = ARCHIVE_POPULARITY_MODIFIER
57 else:
57 else:
58 modifier = 1
58 modifier = 1
59 tag_reply_count += thread.get_reply_count() * modifier
59 tag_reply_count += thread.get_reply_count() * modifier \
60 / thread.tags.all().count()
60
61
61 popularity = tag_reply_count / all_post_count / thread.tags.all().count()
62 popularity = tag_reply_count / all_post_count
62
63
63 return popularity
64 return popularity
64
65
65 def get_linked_tags(self):
66 def get_linked_tags(self):
66 tag_list = []
67 tag_list = []
67 self.get_linked_tags_list(tag_list)
68 self.get_linked_tags_list(tag_list)
68
69
69 return tag_list
70 return tag_list
70
71
71 def get_linked_tags_list(self, tag_list=[]):
72 def get_linked_tags_list(self, tag_list=[]):
72 """
73 """
73 Returns the list of tags linked to current. The list can be got
74 Returns the list of tags linked to current. The list can be got
74 through returned value or tag_list parameter
75 through returned value or tag_list parameter
75 """
76 """
76
77
77 linked_tag = self.linked
78 linked_tag = self.linked
78
79
79 if linked_tag and not (linked_tag in tag_list):
80 if linked_tag and not (linked_tag in tag_list):
80 tag_list.append(linked_tag)
81 tag_list.append(linked_tag)
81
82
82 linked_tag.get_linked_tags_list(tag_list)
83 linked_tag.get_linked_tags_list(tag_list)
83
84
84 def get_font_value(self):
85 def get_font_value(self):
85 """Get tag font value to differ most popular tags in the list"""
86 """Get tag font value to differ most popular tags in the list"""
86
87
87 popularity = self.get_popularity()
88 popularity = self.get_popularity()
88
89
89 font_value = popularity * Tag.objects.get_not_empty_tags().count()
90 font_value = popularity * Tag.objects.get_not_empty_tags().count()
90 font_value = max(font_value, MIN_TAG_FONT)
91 font_value = max(font_value, MIN_TAG_FONT)
91 font_value = min(font_value, MAX_TAG_FONT)
92 font_value = min(font_value, MAX_TAG_FONT)
92
93
93 return str(font_value)
94 return str(font_value)
General Comments 0
You need to be logged in to leave comments. Login now