##// END OF EJS Templates
Optimized getting threads for tag.
neko259 -
r183:9bd4c7a4 default
parent child Browse files
Show More
@@ -1,323 +1,323 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.db.models import Count
8 from django.db.models import Count
9 from django.http import Http404
9 from django.http import Http404
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, thread=None,
36 def create_post(self, title, text, image=None, thread=None,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 thread=thread,
41 thread=thread,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if tags:
48 if tags:
49 map(post.tags.add, tags)
49 map(post.tags.add, tags)
50
50
51 if thread:
51 if thread:
52 thread.replies.add(post)
52 thread.replies.add(post)
53 thread.bump()
53 thread.bump()
54 else:
54 else:
55 self._delete_old_threads()
55 self._delete_old_threads()
56
56
57 return post
57 return post
58
58
59 def delete_post(self, post):
59 def delete_post(self, post):
60 if post.replies.count() > 0:
60 if post.replies.count() > 0:
61 map(self.delete_post, post.replies.all())
61 map(self.delete_post, post.replies.all())
62 post.delete()
62 post.delete()
63
63
64 def delete_posts_by_ip(self, ip):
64 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
65 posts = self.filter(poster_ip=ip)
66 map(self.delete_post, posts)
66 map(self.delete_post, posts)
67
67
68 def get_threads(self, tag=None, page=ALL_PAGES,
68 def get_threads(self, tag=None, page=ALL_PAGES,
69 order_by='-last_edit_time'):
69 order_by='-last_edit_time'):
70 if tag:
70 if tag:
71 threads = self.filter(thread=None, tags=tag)
71 threads = tag.threads
72
72
73 # TODO This needs to be uncommented when 'all tags' view won't
73 # TODO This needs to be uncommented when 'all tags' view won't
74 # use this method to get threads for tag
74 # use this method to get threads for tag
75
75
76 # if threads.count() == 0:
76 # if threads.count() == 0:
77 # raise Http404
77 # raise Http404
78 else:
78 else:
79 threads = self.filter(thread=None)
79 threads = self.filter(thread=None)
80
80
81 threads = threads.order_by(order_by)
81 threads = threads.order_by(order_by)
82
82
83 if page != ALL_PAGES:
83 if page != ALL_PAGES:
84 thread_count = threads.count()
84 thread_count = threads.count()
85
85
86 if page < self.get_thread_page_count(tag=tag):
86 if page < self.get_thread_page_count(tag=tag):
87 start_thread = page * settings.THREADS_PER_PAGE
87 start_thread = page * settings.THREADS_PER_PAGE
88 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
88 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
89 thread_count)
89 thread_count)
90 threads = threads[start_thread:end_thread]
90 threads = threads[start_thread:end_thread]
91
91
92 return threads
92 return threads
93
93
94 def get_thread(self, opening_post_id):
94 def get_thread(self, opening_post_id):
95 try:
95 try:
96 opening_post = self.get(id=opening_post_id, thread=None)
96 opening_post = self.get(id=opening_post_id, thread=None)
97 except Post.DoesNotExist:
97 except Post.DoesNotExist:
98 raise Http404
98 raise Http404
99
99
100 if opening_post.replies:
100 if opening_post.replies:
101 thread = [opening_post]
101 thread = [opening_post]
102 thread.extend(opening_post.replies.all())
102 thread.extend(opening_post.replies.all())
103
103
104 return thread
104 return thread
105
105
106 def exists(self, post_id):
106 def exists(self, post_id):
107 posts = self.filter(id=post_id)
107 posts = self.filter(id=post_id)
108
108
109 return posts.count() > 0
109 return posts.count() > 0
110
110
111 def get_thread_page_count(self, tag=None):
111 def get_thread_page_count(self, tag=None):
112 if tag:
112 if tag:
113 threads = self.filter(thread=None, tags=tag)
113 threads = self.filter(thread=None, tags=tag)
114 else:
114 else:
115 threads = self.filter(thread=None)
115 threads = self.filter(thread=None)
116
116
117 return int(math.ceil(threads.count() / float(
117 return int(math.ceil(threads.count() / float(
118 settings.THREADS_PER_PAGE)))
118 settings.THREADS_PER_PAGE)))
119
119
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 delete the old ones.
123 delete the old ones.
124 """
124 """
125
125
126 # TODO Move old threads to the archive instead of deleting them.
126 # TODO Move old threads to the archive instead of deleting them.
127 # Maybe make some 'old' field in the model to indicate the thread
127 # Maybe make some 'old' field in the model to indicate the thread
128 # must not be shown and be able for replying.
128 # must not be shown and be able for replying.
129
129
130 threads = self.get_threads()
130 threads = self.get_threads()
131 thread_count = len(threads)
131 thread_count = len(threads)
132
132
133 if thread_count > settings.MAX_THREAD_COUNT:
133 if thread_count > settings.MAX_THREAD_COUNT:
134 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
134 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
135 old_threads = threads[thread_count - num_threads_to_delete:]
135 old_threads = threads[thread_count - num_threads_to_delete:]
136
136
137 map(self.delete_post, old_threads)
137 map(self.delete_post, old_threads)
138
138
139
139
140 class TagManager(models.Manager):
140 class TagManager(models.Manager):
141
141
142 def get_not_empty_tags(self):
142 def get_not_empty_tags(self):
143 tags = self.annotate(Count('threads')) \
143 tags = self.annotate(Count('threads')) \
144 .filter(threads__count__gt=0).order_by('name')
144 .filter(threads__count__gt=0).order_by('name')
145
145
146 return tags
146 return tags
147
147
148
148
149 class Tag(models.Model):
149 class Tag(models.Model):
150 """
150 """
151 A tag is a text node assigned to the post. The tag serves as a board
151 A tag is a text node assigned to the post. The tag serves as a board
152 section. There can be multiple tags for each message
152 section. There can be multiple tags for each message
153 """
153 """
154
154
155 objects = TagManager()
155 objects = TagManager()
156
156
157 name = models.CharField(max_length=100)
157 name = models.CharField(max_length=100)
158 threads = models.ManyToManyField('Post', null=True,
158 threads = models.ManyToManyField('Post', null=True,
159 blank=True, related_name='tag+')
159 blank=True, related_name='tag+')
160
160
161 def __unicode__(self):
161 def __unicode__(self):
162 return self.name
162 return self.name
163
163
164 def is_empty(self):
164 def is_empty(self):
165 return self.get_post_count() == 0
165 return self.get_post_count() == 0
166
166
167 def get_post_count(self):
167 def get_post_count(self):
168 return self.threads.count()
168 return self.threads.count()
169
169
170 def get_popularity(self):
170 def get_popularity(self):
171 posts_with_tag = Post.objects.get_threads(tag=self)
171 posts_with_tag = Post.objects.get_threads(tag=self)
172 reply_count = 0
172 reply_count = 0
173 for post in posts_with_tag:
173 for post in posts_with_tag:
174 reply_count += post.get_reply_count()
174 reply_count += post.get_reply_count()
175 reply_count += OPENING_POST_POPULARITY_WEIGHT
175 reply_count += OPENING_POST_POPULARITY_WEIGHT
176
176
177 return reply_count
177 return reply_count
178
178
179
179
180 class Post(models.Model):
180 class Post(models.Model):
181 """A post is a message."""
181 """A post is a message."""
182
182
183 objects = PostManager()
183 objects = PostManager()
184
184
185 def _update_image_filename(self, filename):
185 def _update_image_filename(self, filename):
186 """Get unique image filename"""
186 """Get unique image filename"""
187
187
188 path = IMAGES_DIRECTORY
188 path = IMAGES_DIRECTORY
189 new_name = str(int(time.mktime(time.gmtime())))
189 new_name = str(int(time.mktime(time.gmtime())))
190 new_name += str(int(random() * 1000))
190 new_name += str(int(random() * 1000))
191 new_name += FILE_EXTENSION_DELIMITER
191 new_name += FILE_EXTENSION_DELIMITER
192 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
192 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
193
193
194 return os.path.join(path, new_name)
194 return os.path.join(path, new_name)
195
195
196 title = models.CharField(max_length=TITLE_MAX_LENGTH)
196 title = models.CharField(max_length=TITLE_MAX_LENGTH)
197 pub_time = models.DateTimeField()
197 pub_time = models.DateTimeField()
198 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
198 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
199 escape_html=False)
199 escape_html=False)
200
200
201 image_width = models.IntegerField(default=0)
201 image_width = models.IntegerField(default=0)
202 image_height = models.IntegerField(default=0)
202 image_height = models.IntegerField(default=0)
203
203
204 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
204 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
205 blank=True, sizes=(IMAGE_THUMB_SIZE,),
205 blank=True, sizes=(IMAGE_THUMB_SIZE,),
206 width_field='image_width',
206 width_field='image_width',
207 height_field='image_height')
207 height_field='image_height')
208
208
209 poster_ip = models.GenericIPAddressField()
209 poster_ip = models.GenericIPAddressField()
210 poster_user_agent = models.TextField()
210 poster_user_agent = models.TextField()
211
211
212 # TODO Remove this field after everything has been updated to 'thread'
212 # TODO Remove this field after everything has been updated to 'thread'
213 parent = models.BigIntegerField(default=NO_PARENT)
213 parent = models.BigIntegerField(default=NO_PARENT)
214
214
215 thread = models.ForeignKey('Post', null=True, default=None)
215 thread = models.ForeignKey('Post', null=True, default=None)
216 tags = models.ManyToManyField(Tag)
216 tags = models.ManyToManyField(Tag)
217 last_edit_time = models.DateTimeField()
217 last_edit_time = models.DateTimeField()
218 user = models.ForeignKey('User', null=True, default=None)
218 user = models.ForeignKey('User', null=True, default=None)
219
219
220 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
220 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
221 blank=True, related_name='re+')
221 blank=True, related_name='re+')
222
222
223 def __unicode__(self):
223 def __unicode__(self):
224 return '#' + str(self.id) + ' ' + self.title + ' (' + \
224 return '#' + str(self.id) + ' ' + self.title + ' (' + \
225 self.text.raw[:50] + ')'
225 self.text.raw[:50] + ')'
226
226
227 def get_title(self):
227 def get_title(self):
228 title = self.title
228 title = self.title
229 if len(title) == 0:
229 if len(title) == 0:
230 title = self.text.raw[:20]
230 title = self.text.raw[:20]
231
231
232 return title
232 return title
233
233
234 def get_reply_count(self):
234 def get_reply_count(self):
235 return self.replies.count()
235 return self.replies.count()
236
236
237 def get_images_count(self):
237 def get_images_count(self):
238 images_count = 1 if self.image else 0
238 images_count = 1 if self.image else 0
239 images_count += self.replies.filter(image_width__gt=0).count()
239 images_count += self.replies.filter(image_width__gt=0).count()
240
240
241 return images_count
241 return images_count
242
242
243 def can_bump(self):
243 def can_bump(self):
244 """Check if the thread can be bumped by replying"""
244 """Check if the thread can be bumped by replying"""
245
245
246 post_count = self.get_reply_count() + 1
246 post_count = self.get_reply_count() + 1
247
247
248 return post_count <= settings.MAX_POSTS_PER_THREAD
248 return post_count <= settings.MAX_POSTS_PER_THREAD
249
249
250 def bump(self):
250 def bump(self):
251 """Bump (move to up) thread"""
251 """Bump (move to up) thread"""
252
252
253 if self.can_bump():
253 if self.can_bump():
254 self.last_edit_time = timezone.now()
254 self.last_edit_time = timezone.now()
255 self.save()
255 self.save()
256
256
257 def get_last_replies(self):
257 def get_last_replies(self):
258 if settings.LAST_REPLIES_COUNT > 0:
258 if settings.LAST_REPLIES_COUNT > 0:
259 reply_count = self.get_reply_count()
259 reply_count = self.get_reply_count()
260
260
261 if reply_count > 0:
261 if reply_count > 0:
262 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
262 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
263 reply_count)
263 reply_count)
264 last_replies = self.replies.all()[reply_count -
264 last_replies = self.replies.all()[reply_count -
265 reply_count_to_show:]
265 reply_count_to_show:]
266
266
267 return last_replies
267 return last_replies
268
268
269
269
270 class User(models.Model):
270 class User(models.Model):
271
271
272 user_id = models.CharField(max_length=50)
272 user_id = models.CharField(max_length=50)
273 rank = models.IntegerField()
273 rank = models.IntegerField()
274
274
275 registration_time = models.DateTimeField()
275 registration_time = models.DateTimeField()
276 last_access_time = models.DateTimeField()
276 last_access_time = models.DateTimeField()
277
277
278 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
278 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
279 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
279 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
280 blank=True)
280 blank=True)
281
281
282 def save_setting(self, name, value):
282 def save_setting(self, name, value):
283 setting, created = Setting.objects.get_or_create(name=name, user=self)
283 setting, created = Setting.objects.get_or_create(name=name, user=self)
284 setting.value = value
284 setting.value = value
285 setting.save()
285 setting.save()
286
286
287 return setting
287 return setting
288
288
289 def get_setting(self, name):
289 def get_setting(self, name):
290 if Setting.objects.filter(name=name, user=self).exists():
290 if Setting.objects.filter(name=name, user=self).exists():
291 setting = Setting.objects.get(name=name, user=self)
291 setting = Setting.objects.get(name=name, user=self)
292 setting_value = setting.value
292 setting_value = setting.value
293 else:
293 else:
294 setting_value = None
294 setting_value = None
295
295
296 return setting_value
296 return setting_value
297
297
298 def is_moderator(self):
298 def is_moderator(self):
299 return RANK_MODERATOR >= self.rank
299 return RANK_MODERATOR >= self.rank
300
300
301 def get_sorted_fav_tags(self):
301 def get_sorted_fav_tags(self):
302 tags = self.fav_tags.annotate(Count('threads'))\
302 tags = self.fav_tags.annotate(Count('threads'))\
303 .filter(threads__count__gt=0).order_by('name')
303 .filter(threads__count__gt=0).order_by('name')
304
304
305 return tags
305 return tags
306
306
307 def __unicode__(self):
307 def __unicode__(self):
308 return self.user_id + '(' + str(self.rank) + ')'
308 return self.user_id + '(' + str(self.rank) + ')'
309
309
310
310
311 class Setting(models.Model):
311 class Setting(models.Model):
312
312
313 name = models.CharField(max_length=50)
313 name = models.CharField(max_length=50)
314 value = models.CharField(max_length=50)
314 value = models.CharField(max_length=50)
315 user = models.ForeignKey(User)
315 user = models.ForeignKey(User)
316
316
317
317
318 class Ban(models.Model):
318 class Ban(models.Model):
319
319
320 ip = models.GenericIPAddressField()
320 ip = models.GenericIPAddressField()
321
321
322 def __unicode__(self):
322 def __unicode__(self):
323 return self.ip
323 return self.ip
General Comments 0
You need to be logged in to leave comments. Login now