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