##// END OF EJS Templates
Readded post cache
neko259 -
r394:f878102e default
parent child Browse files
Show More
@@ -1,295 +1,299 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import time
3 import time
4 import math
4 import math
5 import re
5 import re
6 from django.core.cache import cache
6
7
7 from django.db import models
8 from django.db import models
8 from django.http import Http404
9 from django.http import Http404
9 from django.utils import timezone
10 from django.utils import timezone
10 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
11
12
12 from neboard import settings
13 from neboard import settings
14 from boards import settings as boards_settings
13 from boards import thumbs
15 from boards import thumbs
14
16
15 BAN_REASON_AUTO = 'Auto'
17 BAN_REASON_AUTO = 'Auto'
16
18
17 IMAGE_THUMB_SIZE = (200, 150)
19 IMAGE_THUMB_SIZE = (200, 150)
18
20
19 TITLE_MAX_LENGTH = 50
21 TITLE_MAX_LENGTH = 50
20
22
21 DEFAULT_MARKUP_TYPE = 'markdown'
23 DEFAULT_MARKUP_TYPE = 'markdown'
22
24
23 NO_PARENT = -1
25 NO_PARENT = -1
24 NO_IP = '0.0.0.0'
26 NO_IP = '0.0.0.0'
25 UNKNOWN_UA = ''
27 UNKNOWN_UA = ''
26 ALL_PAGES = -1
28 ALL_PAGES = -1
27 IMAGES_DIRECTORY = 'images/'
29 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
30 FILE_EXTENSION_DELIMITER = '.'
29
31
30 SETTING_MODERATE = "moderate"
32 SETTING_MODERATE = "moderate"
31
33
32 REGEX_REPLY = re.compile('>>(\d+)')
34 REGEX_REPLY = re.compile('>>(\d+)')
33
35
34
36
35 class PostManager(models.Manager):
37 class PostManager(models.Manager):
36
38
37 def create_post(self, title, text, image=None, thread=None,
39 def create_post(self, title, text, image=None, thread=None,
38 ip=NO_IP, tags=None, user=None):
40 ip=NO_IP, tags=None, user=None):
39 posting_time = timezone.now()
41 posting_time = timezone.now()
40
42
41 post = self.create(title=title,
43 post = self.create(title=title,
42 text=text,
44 text=text,
43 pub_time=posting_time,
45 pub_time=posting_time,
44 thread=thread,
46 thread=thread,
45 image=image,
47 image=image,
46 poster_ip=ip,
48 poster_ip=ip,
47 poster_user_agent=UNKNOWN_UA,
49 poster_user_agent=UNKNOWN_UA,
48 last_edit_time=posting_time,
50 last_edit_time=posting_time,
49 bump_time=posting_time,
51 bump_time=posting_time,
50 user=user)
52 user=user)
51
53
52 if tags:
54 if tags:
53 linked_tags = []
55 linked_tags = []
54 for tag in tags:
56 for tag in tags:
55 tag_linked_tags = tag.get_linked_tags()
57 tag_linked_tags = tag.get_linked_tags()
56 if len(tag_linked_tags) > 0:
58 if len(tag_linked_tags) > 0:
57 linked_tags.extend(tag_linked_tags)
59 linked_tags.extend(tag_linked_tags)
58
60
59 tags.extend(linked_tags)
61 tags.extend(linked_tags)
60 map(post.tags.add, tags)
62 map(post.tags.add, tags)
61 for tag in tags:
63 for tag in tags:
62 tag.threads.add(post)
64 tag.threads.add(post)
63
65
64 if thread:
66 if thread:
65 thread.replies.add(post)
67 thread.replies.add(post)
66 thread.bump()
68 thread.bump()
67 thread.last_edit_time = posting_time
69 thread.last_edit_time = posting_time
68 thread.save()
70 thread.save()
69
70 #cache_key = thread.get_cache_key()
71 #cache.delete(cache_key)
72
73 else:
71 else:
74 self._delete_old_threads()
72 self._delete_old_threads()
75
73
76 self.connect_replies(post)
74 self.connect_replies(post)
77
75
78 return post
76 return post
79
77
80 def delete_post(self, post):
78 def delete_post(self, post):
81 if post.replies.count() > 0:
79 if post.replies.count() > 0:
82 map(self.delete_post, post.replies.all())
80 map(self.delete_post, post.replies.all())
83
81
84 # Update thread's last edit time (used as cache key)
82 # Update thread's last edit time (used as cache key)
85 thread = post.thread
83 thread = post.thread
86 if thread:
84 if thread:
85 thread.clear_cache()
86
87 thread.last_edit_time = timezone.now()
87 thread.last_edit_time = timezone.now()
88 thread.save()
88 thread.save()
89
89
90 #cache_key = thread.get_cache_key()
90 post.clear_cache()
91 #cache.delete(cache_key)
92
93 post.delete()
91 post.delete()
94
92
95 def delete_posts_by_ip(self, ip):
93 def delete_posts_by_ip(self, ip):
96 posts = self.filter(poster_ip=ip)
94 posts = self.filter(poster_ip=ip)
97 map(self.delete_post, posts)
95 map(self.delete_post, posts)
98
96
99 def get_threads(self, tag=None, page=ALL_PAGES,
97 def get_threads(self, tag=None, page=ALL_PAGES,
100 order_by='-bump_time'):
98 order_by='-bump_time'):
101 if tag:
99 if tag:
102 threads = tag.threads
100 threads = tag.threads
103
101
104 if threads.count() == 0:
102 if threads.count() == 0:
105 raise Http404
103 raise Http404
106 else:
104 else:
107 threads = self.filter(thread=None)
105 threads = self.filter(thread=None)
108
106
109 threads = threads.order_by(order_by)
107 threads = threads.order_by(order_by)
110
108
111 if page != ALL_PAGES:
109 if page != ALL_PAGES:
112 thread_count = threads.count()
110 thread_count = threads.count()
113
111
114 if page < self._get_page_count(thread_count):
112 if page < self._get_page_count(thread_count):
115 start_thread = page * settings.THREADS_PER_PAGE
113 start_thread = page * settings.THREADS_PER_PAGE
116 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
114 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
117 thread_count)
115 thread_count)
118 threads = threads[start_thread:end_thread]
116 threads = threads[start_thread:end_thread]
119
117
120 return threads
118 return threads
121
119
122 def get_thread(self, opening_post_id):
120 def get_thread(self, opening_post_id):
123 try:
121 try:
124 opening_post = self.get(id=opening_post_id, thread=None)
122 opening_post = self.get(id=opening_post_id, thread=None)
125 except Post.DoesNotExist:
123 except Post.DoesNotExist:
126 raise Http404
124 raise Http404
127
125
128 #cache_key = opening_post.get_cache_key()
126 cache_key = opening_post.get_cache_key()
129 #thread = cache.get(cache_key)
127 thread = cache.get(cache_key)
130 #if thread:
128 if thread:
131 # return thread
129 return thread
132
130
133 if opening_post.replies:
131 if opening_post.replies:
134 thread = [opening_post]
132 thread = [opening_post]
135 thread.extend(opening_post.replies.all().order_by('pub_time'))
133 thread.extend(opening_post.replies.all().order_by('pub_time'))
136
134
137 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
135 cache.set(cache_key, thread, boards_settings.CACHE_TIMEOUT)
138
136
139 return thread
137 return thread
140
138
141 def exists(self, post_id):
139 def exists(self, post_id):
142 posts = self.filter(id=post_id)
140 posts = self.filter(id=post_id)
143
141
144 return posts.count() > 0
142 return posts.count() > 0
145
143
146 def get_thread_page_count(self, tag=None):
144 def get_thread_page_count(self, tag=None):
147 if tag:
145 if tag:
148 threads = self.filter(thread=None, tags=tag)
146 threads = self.filter(thread=None, tags=tag)
149 else:
147 else:
150 threads = self.filter(thread=None)
148 threads = self.filter(thread=None)
151
149
152 return self._get_page_count(threads.count())
150 return self._get_page_count(threads.count())
153
151
154 def _delete_old_threads(self):
152 def _delete_old_threads(self):
155 """
153 """
156 Preserves maximum thread count. If there are too many threads,
154 Preserves maximum thread count. If there are too many threads,
157 delete the old ones.
155 delete the old ones.
158 """
156 """
159
157
160 # TODO Move old threads to the archive instead of deleting them.
158 # TODO Move old threads to the archive instead of deleting them.
161 # Maybe make some 'old' field in the model to indicate the thread
159 # Maybe make some 'old' field in the model to indicate the thread
162 # must not be shown and be able for replying.
160 # must not be shown and be able for replying.
163
161
164 threads = self.get_threads()
162 threads = self.get_threads()
165 thread_count = threads.count()
163 thread_count = threads.count()
166
164
167 if thread_count > settings.MAX_THREAD_COUNT:
165 if thread_count > settings.MAX_THREAD_COUNT:
168 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
166 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
169 old_threads = threads[thread_count - num_threads_to_delete:]
167 old_threads = threads[thread_count - num_threads_to_delete:]
170
168
171 map(self.delete_post, old_threads)
169 map(self.delete_post, old_threads)
172
170
173 def connect_replies(self, post):
171 def connect_replies(self, post):
174 """Connect replies to a post to show them as a refmap"""
172 """Connect replies to a post to show them as a refmap"""
175
173
176 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
174 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
177 post_id = reply_number.group(1)
175 post_id = reply_number.group(1)
178 ref_post = self.filter(id=post_id)
176 ref_post = self.filter(id=post_id)
179 if ref_post.count() > 0:
177 if ref_post.count() > 0:
180 referenced_post = ref_post[0]
178 referenced_post = ref_post[0]
181 referenced_post.referenced_posts.add(post)
179 referenced_post.referenced_posts.add(post)
182 referenced_post.last_edit_time = post.pub_time
180 referenced_post.last_edit_time = post.pub_time
183 referenced_post.save()
181 referenced_post.save()
184
182
185 def _get_page_count(self, thread_count):
183 def _get_page_count(self, thread_count):
186 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
184 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
187
185
188
186
189 class Post(models.Model):
187 class Post(models.Model):
190 """A post is a message."""
188 """A post is a message."""
191
189
192 objects = PostManager()
190 objects = PostManager()
193
191
194 class Meta:
192 class Meta:
195 app_label = 'boards'
193 app_label = 'boards'
196
194
197 def _update_image_filename(self, filename):
195 def _update_image_filename(self, filename):
198 """Get unique image filename"""
196 """Get unique image filename"""
199
197
200 path = IMAGES_DIRECTORY
198 path = IMAGES_DIRECTORY
201 new_name = str(int(time.mktime(time.gmtime())))
199 new_name = str(int(time.mktime(time.gmtime())))
202 new_name += str(int(random() * 1000))
200 new_name += str(int(random() * 1000))
203 new_name += FILE_EXTENSION_DELIMITER
201 new_name += FILE_EXTENSION_DELIMITER
204 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
205
203
206 return os.path.join(path, new_name)
204 return os.path.join(path, new_name)
207
205
208 title = models.CharField(max_length=TITLE_MAX_LENGTH)
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
209 pub_time = models.DateTimeField()
207 pub_time = models.DateTimeField()
210 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
211 escape_html=False)
209 escape_html=False)
212
210
213 image_width = models.IntegerField(default=0)
211 image_width = models.IntegerField(default=0)
214 image_height = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
215
213
216 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
214 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
217 blank=True, sizes=(IMAGE_THUMB_SIZE,),
215 blank=True, sizes=(IMAGE_THUMB_SIZE,),
218 width_field='image_width',
216 width_field='image_width',
219 height_field='image_height')
217 height_field='image_height')
220
218
221 poster_ip = models.GenericIPAddressField()
219 poster_ip = models.GenericIPAddressField()
222 poster_user_agent = models.TextField()
220 poster_user_agent = models.TextField()
223
221
224 thread = models.ForeignKey('Post', null=True, default=None)
222 thread = models.ForeignKey('Post', null=True, default=None)
225 tags = models.ManyToManyField('Tag')
223 tags = models.ManyToManyField('Tag')
226 last_edit_time = models.DateTimeField()
224 last_edit_time = models.DateTimeField()
227 bump_time = models.DateTimeField()
225 bump_time = models.DateTimeField()
228 user = models.ForeignKey('User', null=True, default=None)
226 user = models.ForeignKey('User', null=True, default=None)
229
227
230 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
228 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
231 blank=True, related_name='re+')
229 blank=True, related_name='re+')
232 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
230 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
233 null=True,
231 null=True,
234 blank=True, related_name='rfp+')
232 blank=True, related_name='rfp+')
235
233
236 def __unicode__(self):
234 def __unicode__(self):
237 return '#' + str(self.id) + ' ' + self.title + ' (' + \
235 return '#' + str(self.id) + ' ' + self.title + ' (' + \
238 self.text.raw[:50] + ')'
236 self.text.raw[:50] + ')'
239
237
240 def get_title(self):
238 def get_title(self):
241 title = self.title
239 title = self.title
242 if len(title) == 0:
240 if len(title) == 0:
243 title = self.text.raw[:20]
241 title = self.text.raw[:20]
244
242
245 return title
243 return title
246
244
247 def get_reply_count(self):
245 def get_reply_count(self):
248 return self.replies.count()
246 return self.replies.count()
249
247
250 def get_images_count(self):
248 def get_images_count(self):
251 images_count = 1 if self.image else 0
249 images_count = 1 if self.image else 0
252 images_count += self.replies.filter(image_width__gt=0).count()
250 images_count += self.replies.filter(image_width__gt=0).count()
253
251
254 return images_count
252 return images_count
255
253
256 def can_bump(self):
254 def can_bump(self):
257 """Check if the thread can be bumped by replying"""
255 """Check if the thread can be bumped by replying"""
258
256
259 post_count = self.get_reply_count()
257 post_count = self.get_reply_count()
260
258
261 return post_count <= settings.MAX_POSTS_PER_THREAD
259 return post_count <= settings.MAX_POSTS_PER_THREAD
262
260
261 def clear_cache(self):
262 """Remove the post from cache"""
263
264 cache_key = self.get_cache_key()
265 cache.delete(cache_key)
266
263 def bump(self):
267 def bump(self):
264 """Bump (move to up) thread"""
268 """Bump (move to up) thread"""
265
269
266 if self.can_bump():
270 if self.can_bump():
267 self.bump_time = timezone.now()
271 self.bump_time = timezone.now()
268
272
273 self.clear_cache()
274
269 def get_last_replies(self):
275 def get_last_replies(self):
270 if settings.LAST_REPLIES_COUNT > 0:
276 if settings.LAST_REPLIES_COUNT > 0:
271 reply_count = self.get_reply_count()
277 reply_count = self.get_reply_count()
272
278
273 if reply_count > 0:
279 if reply_count > 0:
274 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
280 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
275 reply_count)
281 reply_count)
276 last_replies = self.replies.all().order_by('pub_time')[
282 last_replies = self.replies.all().order_by('pub_time')[
277 reply_count - reply_count_to_show:]
283 reply_count - reply_count_to_show:]
278
284
279 return last_replies
285 return last_replies
280
286
281 def get_tags(self):
287 def get_tags(self):
282 """Get a sorted tag list"""
288 """Get a sorted tag list"""
283
289
284 return self.tags.order_by('name')
290 return self.tags.order_by('name')
285
291
286 def get_cache_key(self):
292 def get_cache_key(self):
287 return str(self.id) + str(self.last_edit_time.microsecond)
293 return 'thread_cache' + str(self.id)
288
294
289 def get_sorted_referenced_posts(self):
295 def get_sorted_referenced_posts(self):
290 return self.referenced_posts.order_by('id')
296 return self.referenced_posts.order_by('id')
291
297
292 def is_referenced(self):
298 def is_referenced(self):
293 return self.referenced_posts.count() > 0
299 return self.referenced_posts.count() > 0
294
295
General Comments 0
You need to be logged in to leave comments. Login now