##// END OF EJS Templates
Cache threads
neko259 -
r324:c419aa11 default
parent child Browse files
Show More
@@ -1,403 +1,421 b''
1 1 import os
2 2 from random import random
3 3 import time
4 4 import math
5 5 from django.core.cache import cache
6 6
7 7 from django.db import models
8 8 from django.db.models import Count
9 9 from django.http import Http404
10 10 from django.utils import timezone
11 11 from markupfield.fields import MarkupField
12 12 from boards import settings as board_settings
13 13
14 14 from neboard import settings
15 15 import thumbs
16 16
17 17 import re
18 18
19 19 IMAGE_THUMB_SIZE = (200, 150)
20 20
21 21 TITLE_MAX_LENGTH = 50
22 22
23 23 DEFAULT_MARKUP_TYPE = 'markdown'
24 24
25 25 NO_PARENT = -1
26 26 NO_IP = '0.0.0.0'
27 27 UNKNOWN_UA = ''
28 28 ALL_PAGES = -1
29 29 OPENING_POST_POPULARITY_WEIGHT = 2
30 30 IMAGES_DIRECTORY = 'images/'
31 31 FILE_EXTENSION_DELIMITER = '.'
32 32
33 33 RANK_ADMIN = 0
34 34 RANK_MODERATOR = 10
35 35 RANK_USER = 100
36 36
37 37 SETTING_MODERATE = "moderate"
38 38
39 39 REGEX_REPLY = re.compile('>>(\d+)')
40 40
41 41
42 42 class PostManager(models.Manager):
43 43
44 44 def create_post(self, title, text, image=None, thread=None,
45 45 ip=NO_IP, tags=None, user=None):
46 46 post = self.create(title=title,
47 47 text=text,
48 48 pub_time=timezone.now(),
49 49 thread=thread,
50 50 image=image,
51 51 poster_ip=ip,
52 52 poster_user_agent=UNKNOWN_UA,
53 53 last_edit_time=timezone.now(),
54 54 bump_time=timezone.now(),
55 55 user=user)
56 56
57 57 if tags:
58 58 map(post.tags.add, tags)
59 59 for tag in tags:
60 60 tag.threads.add(post)
61 61
62 62 if thread:
63 63 thread.replies.add(post)
64 64 thread.bump()
65 65 thread.last_edit_time = timezone.now()
66 66 thread.save()
67
68 cache_key = thread.get_cache_key
69 cache.delete(cache_key)
70
67 71 else:
68 72 self._delete_old_threads()
69 73
70 74 self.connect_replies(post)
71 75
72 76 return post
73 77
74 78 def delete_post(self, post):
75 79 if post.replies.count() > 0:
76 80 map(self.delete_post, post.replies.all())
77 81
78 82 # Update thread's last edit time (used as cache key)
79 83 thread = post.thread
80 84 if thread:
81 85 thread.last_edit_time = timezone.now()
82 86 thread.save()
83 87
88 cache_key = thread.get_cache_key
89 cache.delete(cache_key)
90
84 91 post.delete()
85 92
86 93 def delete_posts_by_ip(self, ip):
87 94 posts = self.filter(poster_ip=ip)
88 95 map(self.delete_post, posts)
89 96
90 97 def get_threads(self, tag=None, page=ALL_PAGES,
91 98 order_by='-bump_time'):
92 99 if tag:
93 100 threads = tag.threads
94 101
95 102 if threads.count() == 0:
96 103 raise Http404
97 104 else:
98 105 threads = self.filter(thread=None)
99 106
100 107 threads = threads.order_by(order_by)
101 108
102 109 if page != ALL_PAGES:
103 110 thread_count = threads.count()
104 111
105 112 if page < self._get_page_count(thread_count):
106 113 start_thread = page * settings.THREADS_PER_PAGE
107 114 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
108 115 thread_count)
109 116 threads = threads[start_thread:end_thread]
110 117
111 118 return threads
112 119
113 120 def get_thread(self, opening_post_id):
114 121 try:
115 122 opening_post = self.get(id=opening_post_id, thread=None)
116 123 except Post.DoesNotExist:
117 124 raise Http404
118 125
126 cache_key = opening_post.get_cache_key()
127 thread = cache.get(cache_key)
128 if thread:
129 return thread
130
119 131 if opening_post.replies:
120 132 thread = [opening_post]
121 133 thread.extend(opening_post.replies.all().order_by('pub_time'))
122 134
135 cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
136
123 137 return thread
124 138
125 139 def exists(self, post_id):
126 140 posts = self.filter(id=post_id)
127 141
128 142 return posts.count() > 0
129 143
130 144 def get_thread_page_count(self, tag=None):
131 145 if tag:
132 146 threads = self.filter(thread=None, tags=tag)
133 147 else:
134 148 threads = self.filter(thread=None)
135 149
136 150 return self._get_page_count(threads.count())
137 151
138 152 def _delete_old_threads(self):
139 153 """
140 154 Preserves maximum thread count. If there are too many threads,
141 155 delete the old ones.
142 156 """
143 157
144 158 # TODO Move old threads to the archive instead of deleting them.
145 159 # Maybe make some 'old' field in the model to indicate the thread
146 160 # must not be shown and be able for replying.
147 161
148 162 threads = self.get_threads()
149 163 thread_count = threads.count()
150 164
151 165 if thread_count > settings.MAX_THREAD_COUNT:
152 166 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
153 167 old_threads = threads[thread_count - num_threads_to_delete:]
154 168
155 169 map(self.delete_post, old_threads)
156 170
157 171 def connect_replies(self, post):
158 172 """Connect replies to a post to show them as a refmap"""
159 173
160 174 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
161 175 id = reply_number.group(1)
162 176 ref_post = self.filter(id=id)
163 177 if ref_post.count() > 0:
164 178 ref_post[0].referenced_posts.add(post)
165 179
166 180 def _get_page_count(self, thread_count):
167 181 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
168 182
169 183
170 184 class TagManager(models.Manager):
171 185
172 186 def get_not_empty_tags(self):
173 187 tags = self.annotate(Count('threads')) \
174 188 .filter(threads__count__gt=0).order_by('name')
175 189
176 190 return tags
177 191
178 192
179 193 class Tag(models.Model):
180 194 """
181 195 A tag is a text node assigned to the post. The tag serves as a board
182 196 section. There can be multiple tags for each message
183 197 """
184 198
185 199 objects = TagManager()
186 200
187 201 name = models.CharField(max_length=100)
188 202 threads = models.ManyToManyField('Post', null=True,
189 203 blank=True, related_name='tag+')
190 204 linked = models.ForeignKey('Tag', null=True, blank=True)
191 205
192 206 def __unicode__(self):
193 207 return self.name
194 208
195 209 def is_empty(self):
196 210 return self.get_post_count() == 0
197 211
198 212 def get_post_count(self):
199 213 return self.threads.count()
200 214
201 215 def get_popularity(self):
202 216 posts_with_tag = Post.objects.get_threads(tag=self)
203 217 reply_count = 0
204 218 for post in posts_with_tag:
205 219 reply_count += post.get_reply_count()
206 220 reply_count += OPENING_POST_POPULARITY_WEIGHT
207 221
208 222 return reply_count
209 223
210 224 def get_linked_tags(self):
211 225 tag_list = []
212 226 self.get_linked_tags_list(tag_list)
213 227
214 228 return tag_list
215 229
216 230 def get_linked_tags_list(self, tag_list=[]):
217 231 """
218 232 Returns the list of tags linked to current. The list can be got
219 233 through returned value or tag_list parameter
220 234 """
221 235
222 236 linked_tag = self.linked
223 237
224 238 if linked_tag and not (linked_tag in tag_list):
225 239 tag_list.append(linked_tag)
226 240
227 241 linked_tag.get_linked_tags_list(tag_list)
228 242
229 243
230 244 class Post(models.Model):
231 245 """A post is a message."""
232 246
233 247 objects = PostManager()
234 248
235 249 def _update_image_filename(self, filename):
236 250 """Get unique image filename"""
237 251
238 252 path = IMAGES_DIRECTORY
239 253 new_name = str(int(time.mktime(time.gmtime())))
240 254 new_name += str(int(random() * 1000))
241 255 new_name += FILE_EXTENSION_DELIMITER
242 256 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
243 257
244 258 return os.path.join(path, new_name)
245 259
246 260 title = models.CharField(max_length=TITLE_MAX_LENGTH)
247 261 pub_time = models.DateTimeField()
248 262 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
249 263 escape_html=False)
250 264
251 265 image_width = models.IntegerField(default=0)
252 266 image_height = models.IntegerField(default=0)
253 267
254 268 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
255 269 blank=True, sizes=(IMAGE_THUMB_SIZE,),
256 270 width_field='image_width',
257 271 height_field='image_height')
258 272
259 273 poster_ip = models.GenericIPAddressField()
260 274 poster_user_agent = models.TextField()
261 275
262 276 thread = models.ForeignKey('Post', null=True, default=None)
263 277 tags = models.ManyToManyField(Tag)
264 278 last_edit_time = models.DateTimeField()
265 279 bump_time = models.DateTimeField()
266 280 user = models.ForeignKey('User', null=True, default=None)
267 281
268 282 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
269 283 blank=True, related_name='re+')
270 284 referenced_posts = models.ManyToManyField('Post', symmetrical=False, null=True,
271 285 blank=True, related_name='rfp+')
272 286
273 287 def __unicode__(self):
274 288 return '#' + str(self.id) + ' ' + self.title + ' (' + \
275 289 self.text.raw[:50] + ')'
276 290
277 291 def get_title(self):
278 292 title = self.title
279 293 if len(title) == 0:
280 294 title = self.text.raw[:20]
281 295
282 296 return title
283 297
284 298 def get_reply_count(self):
285 299 return self.replies.count()
286 300
287 301 def get_images_count(self):
288 302 images_count = 1 if self.image else 0
289 303 images_count += self.replies.filter(image_width__gt=0).count()
290 304
291 305 return images_count
292 306
293 307 def can_bump(self):
294 308 """Check if the thread can be bumped by replying"""
295 309
296 310 post_count = self.get_reply_count() + 1
297 311
298 312 return post_count <= settings.MAX_POSTS_PER_THREAD
299 313
300 314 def bump(self):
301 315 """Bump (move to up) thread"""
302 316
303 317 if self.can_bump():
304 318 self.bump_time = timezone.now()
305 319
320
306 321 def get_last_replies(self):
307 322 if settings.LAST_REPLIES_COUNT > 0:
308 323 reply_count = self.get_reply_count()
309 324
310 325 if reply_count > 0:
311 326 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
312 327 reply_count)
313 328 last_replies = self.replies.all().order_by('pub_time')[reply_count -
314 329 reply_count_to_show:]
315 330
316 331 return last_replies
317 332
318 333 def get_tags(self):
319 334 """Get a sorted tag list"""
320 335
321 336 return self.tags.order_by('name')
322 337
338 def get_cache_key(self):
339 return str(self.id) + str(self.last_edit_time)
340
323 341
324 342 class User(models.Model):
325 343
326 344 user_id = models.CharField(max_length=50)
327 345 rank = models.IntegerField()
328 346
329 347 registration_time = models.DateTimeField()
330 348
331 349 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
332 350 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
333 351 blank=True)
334 352
335 353 def save_setting(self, name, value):
336 354 setting, created = Setting.objects.get_or_create(name=name, user=self)
337 355 setting.value = str(value)
338 356 setting.save()
339 357
340 358 return setting
341 359
342 360 def get_setting(self, name):
343 361 if Setting.objects.filter(name=name, user=self).exists():
344 362 setting = Setting.objects.get(name=name, user=self)
345 363 setting_value = setting.value
346 364 else:
347 365 setting_value = None
348 366
349 367 return setting_value
350 368
351 369 def is_moderator(self):
352 370 return RANK_MODERATOR >= self.rank
353 371
354 372 def get_sorted_fav_tags(self):
355 373 cache_key = self._get_tag_cache_key()
356 374 fav_tags = cache.get(cache_key)
357 375 if fav_tags:
358 376 return fav_tags
359 377
360 378 tags = self.fav_tags.annotate(Count('threads'))\
361 379 .filter(threads__count__gt=0).order_by('name')
362 380
363 381 if tags:
364 382 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
365 383
366 384 return tags
367 385
368 386 def get_post_count(self):
369 387 return Post.objects.filter(user=self).count()
370 388
371 389 def __unicode__(self):
372 390 return self.user_id + '(' + str(self.rank) + ')'
373 391
374 392 def get_last_access_time(self):
375 393 posts = Post.objects.filter(user=self)
376 394 if posts.count() > 0:
377 395 return posts.latest('pub_time').pub_time
378 396
379 397 def add_tag(self, tag):
380 398 self.fav_tags.add(tag)
381 399 cache.delete(self._get_tag_cache_key())
382 400
383 401 def remove_tag(self, tag):
384 402 self.fav_tags.remove(tag)
385 403 cache.delete(self._get_tag_cache_key())
386 404
387 405 def _get_tag_cache_key(self):
388 406 return self.user_id + '_tags'
389 407
390 408
391 409 class Setting(models.Model):
392 410
393 411 name = models.CharField(max_length=50)
394 412 value = models.CharField(max_length=50)
395 413 user = models.ForeignKey(User)
396 414
397 415
398 416 class Ban(models.Model):
399 417
400 418 ip = models.GenericIPAddressField()
401 419
402 420 def __unicode__(self):
403 421 return self.ip
General Comments 0
You need to be logged in to leave comments. Login now