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