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