##// END OF EJS Templates
Temp fix for the post cache problem
neko259 -
r595:cfe2f372 default
parent child Browse files
Show More
@@ -1,451 +1,451 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 This method may not be needed any more, because django's paginator
119 # TODO This method may not be needed any more, because django's paginator
120 # is used
120 # is used
121 def get_threads(self, tag=None, page=ALL_PAGES,
121 def get_threads(self, tag=None, page=ALL_PAGES,
122 order_by='-bump_time', archived=False):
122 order_by='-bump_time', archived=False):
123 if tag:
123 if tag:
124 threads = tag.threads
124 threads = tag.threads
125
125
126 if not threads.exists():
126 if not threads.exists():
127 raise Http404
127 raise Http404
128 else:
128 else:
129 threads = Thread.objects.all()
129 threads = Thread.objects.all()
130
130
131 threads = threads.filter(archived=archived).order_by(order_by)
131 threads = threads.filter(archived=archived).order_by(order_by)
132
132
133 if page != ALL_PAGES:
133 if page != ALL_PAGES:
134 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
134 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
135 page).object_list
135 page).object_list
136
136
137 return threads
137 return threads
138
138
139 # TODO Move this method to thread manager
139 # TODO Move this method to thread manager
140 def _delete_old_threads(self):
140 def _delete_old_threads(self):
141 """
141 """
142 Preserves maximum thread count. If there are too many threads,
142 Preserves maximum thread count. If there are too many threads,
143 archive the old ones.
143 archive the old ones.
144 """
144 """
145
145
146 threads = self.get_threads()
146 threads = self.get_threads()
147 thread_count = threads.count()
147 thread_count = threads.count()
148
148
149 if thread_count > settings.MAX_THREAD_COUNT:
149 if thread_count > settings.MAX_THREAD_COUNT:
150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
151 old_threads = threads[thread_count - num_threads_to_delete:]
151 old_threads = threads[thread_count - num_threads_to_delete:]
152
152
153 for thread in old_threads:
153 for thread in old_threads:
154 thread.archived = True
154 thread.archived = True
155 thread.last_edit_time = timezone.now()
155 thread.last_edit_time = timezone.now()
156 thread.save()
156 thread.save()
157
157
158 def connect_replies(self, post):
158 def connect_replies(self, post):
159 """
159 """
160 Connect replies to a post to show them as a reflink map
160 Connect replies to a post to show them as a reflink map
161 """
161 """
162
162
163 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
163 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
164 post_id = reply_number.group(1)
164 post_id = reply_number.group(1)
165 ref_post = self.filter(id=post_id)
165 ref_post = self.filter(id=post_id)
166 if ref_post.count() > 0:
166 if ref_post.count() > 0:
167 referenced_post = ref_post[0]
167 referenced_post = ref_post[0]
168 referenced_post.referenced_posts.add(post)
168 referenced_post.referenced_posts.add(post)
169 referenced_post.last_edit_time = post.pub_time
169 referenced_post.last_edit_time = post.pub_time
170 referenced_post.save()
170 referenced_post.save()
171
171
172 referenced_thread = referenced_post.thread_new
172 referenced_thread = referenced_post.thread_new
173 referenced_thread.last_edit_time = post.pub_time
173 referenced_thread.last_edit_time = post.pub_time
174 referenced_thread.save()
174 referenced_thread.save()
175
175
176 def get_posts_per_day(self):
176 def get_posts_per_day(self):
177 """
177 """
178 Get average count of posts per day for the last 7 days
178 Get average count of posts per day for the last 7 days
179 """
179 """
180
180
181 today = date.today()
181 today = date.today()
182 ppd = cache.get(CACHE_KEY_PPD + str(today))
182 ppd = cache.get(CACHE_KEY_PPD + str(today))
183 if ppd:
183 if ppd:
184 return ppd
184 return ppd
185
185
186 posts_per_days = []
186 posts_per_days = []
187 for i in POSTS_PER_DAY_RANGE:
187 for i in POSTS_PER_DAY_RANGE:
188 day_end = today - timedelta(i + 1)
188 day_end = today - timedelta(i + 1)
189 day_start = today - timedelta(i + 2)
189 day_start = today - timedelta(i + 2)
190
190
191 day_time_start = timezone.make_aware(datetime.combine(day_start,
191 day_time_start = timezone.make_aware(datetime.combine(day_start,
192 dtime()), timezone.get_current_timezone())
192 dtime()), timezone.get_current_timezone())
193 day_time_end = timezone.make_aware(datetime.combine(day_end,
193 day_time_end = timezone.make_aware(datetime.combine(day_end,
194 dtime()), timezone.get_current_timezone())
194 dtime()), timezone.get_current_timezone())
195
195
196 posts_per_days.append(float(self.filter(
196 posts_per_days.append(float(self.filter(
197 pub_time__lte=day_time_end,
197 pub_time__lte=day_time_end,
198 pub_time__gte=day_time_start).count()))
198 pub_time__gte=day_time_start).count()))
199
199
200 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
200 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
201 len(posts_per_days))
201 len(posts_per_days))
202 cache.set(CACHE_KEY_PPD + str(today), ppd)
202 cache.set(CACHE_KEY_PPD + str(today), ppd)
203 return ppd
203 return ppd
204
204
205
205
206 class Post(models.Model):
206 class Post(models.Model):
207 """A post is a message."""
207 """A post is a message."""
208
208
209 objects = PostManager()
209 objects = PostManager()
210
210
211 class Meta:
211 class Meta:
212 app_label = APP_LABEL_BOARDS
212 app_label = APP_LABEL_BOARDS
213
213
214 # TODO Save original file name to some field
214 # TODO Save original file name to some field
215 def _update_image_filename(self, filename):
215 def _update_image_filename(self, filename):
216 """Get unique image filename"""
216 """Get unique image filename"""
217
217
218 path = IMAGES_DIRECTORY
218 path = IMAGES_DIRECTORY
219 new_name = str(int(time.mktime(time.gmtime())))
219 new_name = str(int(time.mktime(time.gmtime())))
220 new_name += str(int(random() * 1000))
220 new_name += str(int(random() * 1000))
221 new_name += FILE_EXTENSION_DELIMITER
221 new_name += FILE_EXTENSION_DELIMITER
222 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
222 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
223
223
224 return os.path.join(path, new_name)
224 return os.path.join(path, new_name)
225
225
226 title = models.CharField(max_length=TITLE_MAX_LENGTH)
226 title = models.CharField(max_length=TITLE_MAX_LENGTH)
227 pub_time = models.DateTimeField()
227 pub_time = models.DateTimeField()
228 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
228 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
229 escape_html=False)
229 escape_html=False)
230
230
231 image_width = models.IntegerField(default=0)
231 image_width = models.IntegerField(default=0)
232 image_height = models.IntegerField(default=0)
232 image_height = models.IntegerField(default=0)
233
233
234 image_pre_width = models.IntegerField(default=0)
234 image_pre_width = models.IntegerField(default=0)
235 image_pre_height = models.IntegerField(default=0)
235 image_pre_height = models.IntegerField(default=0)
236
236
237 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
237 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
238 blank=True, sizes=(IMAGE_THUMB_SIZE,),
238 blank=True, sizes=(IMAGE_THUMB_SIZE,),
239 width_field='image_width',
239 width_field='image_width',
240 height_field='image_height',
240 height_field='image_height',
241 preview_width_field='image_pre_width',
241 preview_width_field='image_pre_width',
242 preview_height_field='image_pre_height')
242 preview_height_field='image_pre_height')
243 image_hash = models.CharField(max_length=36)
243 image_hash = models.CharField(max_length=36)
244
244
245 poster_ip = models.GenericIPAddressField()
245 poster_ip = models.GenericIPAddressField()
246 poster_user_agent = models.TextField()
246 poster_user_agent = models.TextField()
247
247
248 thread = models.ForeignKey('Post', null=True, default=None)
248 thread = models.ForeignKey('Post', null=True, default=None)
249 thread_new = models.ForeignKey('Thread', null=True, default=None)
249 thread_new = models.ForeignKey('Thread', null=True, default=None)
250 last_edit_time = models.DateTimeField()
250 last_edit_time = models.DateTimeField()
251 user = models.ForeignKey('User', null=True, default=None)
251 user = models.ForeignKey('User', null=True, default=None)
252
252
253 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
253 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
254 null=True,
254 null=True,
255 blank=True, related_name='rfp+')
255 blank=True, related_name='rfp+')
256
256
257 def __unicode__(self):
257 def __unicode__(self):
258 return '#' + str(self.id) + ' ' + self.title + ' (' + \
258 return '#' + str(self.id) + ' ' + self.title + ' (' + \
259 self.text.raw[:50] + ')'
259 self.text.raw[:50] + ')'
260
260
261 def get_title(self):
261 def get_title(self):
262 title = self.title
262 title = self.title
263 if len(title) == 0:
263 if len(title) == 0:
264 title = self.text.rendered
264 title = self.text.rendered
265
265
266 return title
266 return title
267
267
268 def get_sorted_referenced_posts(self):
268 def get_sorted_referenced_posts(self):
269 return self.referenced_posts.order_by('id')
269 return self.referenced_posts.order_by('id')
270
270
271 def is_referenced(self):
271 def is_referenced(self):
272 return self.referenced_posts.all().exists()
272 return self.referenced_posts.all().exists()
273
273
274 def is_opening(self):
274 def is_opening(self):
275 return self.thread_new.get_opening_post() == self
275 return self.thread_new.get_opening_post() == self
276
276
277 def save(self, *args, **kwargs):
277 def save(self, *args, **kwargs):
278 """
278 """
279 Save the model and compute the image hash
279 Save the model and compute the image hash
280 """
280 """
281
281
282 if not self.pk and self.image:
282 if not self.pk and self.image:
283 md5 = hashlib.md5()
283 md5 = hashlib.md5()
284 for chunk in self.image.chunks():
284 for chunk in self.image.chunks():
285 md5.update(chunk)
285 md5.update(chunk)
286 self.image_hash = md5.hexdigest()
286 self.image_hash = md5.hexdigest()
287 super(Post, self).save(*args, **kwargs)
287 super(Post, self).save(*args, **kwargs)
288
288
289 @transaction.atomic
289 @transaction.atomic
290 def add_tag(self, tag):
290 def add_tag(self, tag):
291 edit_time = timezone.now()
291 edit_time = timezone.now()
292
292
293 thread = self.thread_new
293 thread = self.thread_new
294 thread.add_tag(tag)
294 thread.add_tag(tag)
295 self.last_edit_time = edit_time
295 self.last_edit_time = edit_time
296 self.save()
296 self.save()
297
297
298 thread.last_edit_time = edit_time
298 thread.last_edit_time = edit_time
299 thread.save()
299 thread.save()
300
300
301 @transaction.atomic
301 @transaction.atomic
302 def remove_tag(self, tag):
302 def remove_tag(self, tag):
303 edit_time = timezone.now()
303 edit_time = timezone.now()
304
304
305 thread = self.thread_new
305 thread = self.thread_new
306 thread.remove_tag(tag)
306 thread.remove_tag(tag)
307 self.last_edit_time = edit_time
307 self.last_edit_time = edit_time
308 self.save()
308 self.save()
309
309
310 thread.last_edit_time = edit_time
310 thread.last_edit_time = edit_time
311 thread.save()
311 thread.save()
312
312
313 def get_url(self):
313 def get_url(self):
314 """
314 """
315 Get full url to this post
315 Get full url to this post
316 """
316 """
317
317
318 cache_key = CACHE_KEY_POST_URL + str(self.id)
318 cache_key = CACHE_KEY_POST_URL + str(self.id)
319 link = cache.get(cache_key)
319 link = cache.get(cache_key)
320
320
321 if not link:
321 if not link:
322 opening_post = self.thread_new.get_opening_post()
322 opening_post = self.thread_new.get_opening_post()
323 if self != opening_post:
323 if self != opening_post:
324 link = reverse('thread',
324 link = reverse('thread',
325 kwargs={'post_id': opening_post.id}) + '#' + str(
325 kwargs={'post_id': opening_post.id}) + '#' + str(
326 self.id)
326 self.id)
327 else:
327 else:
328 link = reverse('thread', kwargs={'post_id': self.id})
328 link = reverse('thread', kwargs={'post_id': self.id})
329
329
330 cache.set(cache_key, link)
330 cache.set(cache_key, link)
331
331
332 return link
332 return link
333
333
334
334
335 class Thread(models.Model):
335 class Thread(models.Model):
336
336
337 class Meta:
337 class Meta:
338 app_label = APP_LABEL_BOARDS
338 app_label = APP_LABEL_BOARDS
339
339
340 tags = models.ManyToManyField('Tag')
340 tags = models.ManyToManyField('Tag')
341 bump_time = models.DateTimeField()
341 bump_time = models.DateTimeField()
342 last_edit_time = models.DateTimeField()
342 last_edit_time = models.DateTimeField()
343 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
343 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
344 blank=True, related_name='tre+')
344 blank=True, related_name='tre+')
345 archived = models.BooleanField(default=False)
345 archived = models.BooleanField(default=False)
346
346
347 def get_tags(self):
347 def get_tags(self):
348 """
348 """
349 Get a sorted tag list
349 Get a sorted tag list
350 """
350 """
351
351
352 return self.tags.order_by('name')
352 return self.tags.order_by('name')
353
353
354 def bump(self):
354 def bump(self):
355 """
355 """
356 Bump (move to up) thread
356 Bump (move to up) thread
357 """
357 """
358
358
359 if self.can_bump():
359 if self.can_bump():
360 self.bump_time = timezone.now()
360 self.bump_time = timezone.now()
361
361
362 def get_reply_count(self):
362 def get_reply_count(self):
363 return self.replies.count()
363 return self.replies.count()
364
364
365 def get_images_count(self):
365 def get_images_count(self):
366 return self.replies.filter(image_width__gt=0).count()
366 return self.replies.filter(image_width__gt=0).count()
367
367
368 def can_bump(self):
368 def can_bump(self):
369 """
369 """
370 Check if the thread can be bumped by replying
370 Check if the thread can be bumped by replying
371 """
371 """
372
372
373 if self.archived:
373 if self.archived:
374 return False
374 return False
375
375
376 post_count = self.get_reply_count()
376 post_count = self.get_reply_count()
377
377
378 return post_count < settings.MAX_POSTS_PER_THREAD
378 return post_count < settings.MAX_POSTS_PER_THREAD
379
379
380 def delete_with_posts(self):
380 def delete_with_posts(self):
381 """
381 """
382 Completely delete thread and all its posts
382 Completely delete thread and all its posts
383 """
383 """
384
384
385 if self.replies.count() > 0:
385 if self.replies.count() > 0:
386 self.replies.all().delete()
386 self.replies.all().delete()
387
387
388 self.delete()
388 self.delete()
389
389
390 def get_last_replies(self):
390 def get_last_replies(self):
391 """
391 """
392 Get last replies, not including opening post
392 Get last replies, not including opening post
393 """
393 """
394
394
395 if settings.LAST_REPLIES_COUNT > 0:
395 if settings.LAST_REPLIES_COUNT > 0:
396 reply_count = self.get_reply_count()
396 reply_count = self.get_reply_count()
397
397
398 if reply_count > 0:
398 if reply_count > 0:
399 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
399 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
400 reply_count - 1)
400 reply_count - 1)
401 last_replies = self.replies.all().order_by('pub_time')[
401 last_replies = self.replies.all().order_by('pub_time')[
402 reply_count - reply_count_to_show:]
402 reply_count - reply_count_to_show:]
403
403
404 return last_replies
404 return last_replies
405
405
406 def get_skipped_replies_count(self):
406 def get_skipped_replies_count(self):
407 last_replies = self.get_last_replies()
407 last_replies = self.get_last_replies()
408 return self.get_reply_count() - len(last_replies) - 1
408 return self.get_reply_count() - len(last_replies) - 1
409
409
410 def get_replies(self):
410 def get_replies(self):
411 """
411 """
412 Get sorted thread posts
412 Get sorted thread posts
413 """
413 """
414
414
415 return self.replies.all().order_by('pub_time')
415 return self.replies.all().order_by('pub_time')
416
416
417 def add_tag(self, tag):
417 def add_tag(self, tag):
418 """
418 """
419 Connect thread to a tag and tag to a thread
419 Connect thread to a tag and tag to a thread
420 """
420 """
421
421
422 self.tags.add(tag)
422 self.tags.add(tag)
423 tag.threads.add(self)
423 tag.threads.add(self)
424
424
425 def remove_tag(self, tag):
425 def remove_tag(self, tag):
426 self.tags.remove(tag)
426 self.tags.remove(tag)
427 tag.threads.remove(self)
427 tag.threads.remove(self)
428
428
429 def get_opening_post(self):
429 def get_opening_post(self):
430 """
430 """
431 Get first post of the thread
431 Get first post of the thread
432 """
432 """
433
433
434 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
434 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
435 opening_post = cache.get(cache_key)
435 # opening_post = cache.get(cache_key)
436 if not opening_post:
436 # if not opening_post:
437 opening_post = self.get_replies()[0]
437 opening_post = self.get_replies()[0]
438 cache.set(cache_key, opening_post)
438 # cache.set(cache_key, opening_post)
439
439
440 return opening_post
440 return opening_post
441
441
442 def __unicode__(self):
442 def __unicode__(self):
443 return str(self.id)
443 return str(self.id)
444
444
445 def get_pub_time(self):
445 def get_pub_time(self):
446 """
446 """
447 Thread does not have its own pub time, so we need to get it from
447 Thread does not have its own pub time, so we need to get it from
448 the opening post
448 the opening post
449 """
449 """
450
450
451 return self.get_opening_post().pub_time
451 return self.get_opening_post().pub_time
General Comments 0
You need to be logged in to leave comments. Login now