##// END OF EJS Templates
Style cleanup
neko259 -
r608:1731e23d default
parent child Browse files
Show More
@@ -1,432 +1,431 b''
1 1 from datetime import datetime, timedelta, date
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 import hashlib
9 9
10 10 from django.core.cache import cache
11 11 from django.core.paginator import Paginator
12 12 from django.core.urlresolvers import reverse
13 13
14 14 from django.db import models, transaction
15 15 from django.http import Http404
16 16 from django.utils import timezone
17 17 from markupfield.fields import MarkupField
18 18
19 19 from neboard import settings
20 20 from boards import thumbs
21 21
22 22 MAX_TITLE_LENGTH = 50
23 23
24 24 APP_LABEL_BOARDS = 'boards'
25 25
26 26 CACHE_KEY_PPD = 'ppd'
27 27 CACHE_KEY_POST_URL = 'post_url'
28 28 CACHE_KEY_OPENING_POST = 'opening_post'
29 29
30 30 POSTS_PER_DAY_RANGE = range(7)
31 31
32 32 BAN_REASON_AUTO = 'Auto'
33 33
34 34 IMAGE_THUMB_SIZE = (200, 150)
35 35
36 36 TITLE_MAX_LENGTH = 50
37 37
38 38 DEFAULT_MARKUP_TYPE = 'markdown'
39 39
40 40 NO_PARENT = -1
41 41 NO_IP = '0.0.0.0'
42 42 UNKNOWN_UA = ''
43 43 ALL_PAGES = -1
44 44 IMAGES_DIRECTORY = 'images/'
45 45 FILE_EXTENSION_DELIMITER = '.'
46 46
47 47 SETTING_MODERATE = "moderate"
48 48
49 49 REGEX_REPLY = re.compile('>>(\d+)')
50 50
51 51
52 52 class PostManager(models.Manager):
53 53
54 54 def create_post(self, title, text, image=None, thread=None,
55 55 ip=NO_IP, tags=None, user=None):
56 56 """
57 57 Create new post
58 58 """
59 59
60 60 posting_time = timezone.now()
61 61 if not thread:
62 62 thread = Thread.objects.create(bump_time=posting_time,
63 63 last_edit_time=posting_time)
64 64 else:
65 65 thread.bump()
66 66 thread.last_edit_time = posting_time
67 67 thread.save()
68 68
69 69 post = self.create(title=title,
70 70 text=text,
71 71 pub_time=posting_time,
72 72 thread_new=thread,
73 73 image=image,
74 74 poster_ip=ip,
75 75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
76 76 # last!
77 77 last_edit_time=posting_time,
78 78 user=user)
79 79
80 80 thread.replies.add(post)
81 81 if tags:
82 82 linked_tags = []
83 83 for tag in tags:
84 84 tag_linked_tags = tag.get_linked_tags()
85 85 if len(tag_linked_tags) > 0:
86 86 linked_tags.extend(tag_linked_tags)
87 87
88 88 tags.extend(linked_tags)
89 89 map(thread.add_tag, tags)
90 90
91 91 # TODO Delete old threads only if this is a new thread
92 92 self._delete_old_threads()
93 93 self.connect_replies(post)
94 94
95 95 return post
96 96
97 97 def delete_post(self, post):
98 98 """
99 99 Delete post and update or delete its thread
100 100 """
101 101
102 102 thread = post.thread_new
103 103
104 104 if post.is_opening():
105 105 thread.delete_with_posts()
106 106 else:
107 107 thread.last_edit_time = timezone.now()
108 108 thread.save()
109 109
110 110 post.delete()
111 111
112 112 def delete_posts_by_ip(self, ip):
113 113 """
114 114 Delete all posts of the author with same IP
115 115 """
116 116
117 117 posts = self.filter(poster_ip=ip)
118 118 map(self.delete_post, posts)
119 119
120 120 # TODO Move this method to thread manager
121 121 def _delete_old_threads(self):
122 122 """
123 123 Preserves maximum thread count. If there are too many threads,
124 124 archive the old ones.
125 125 """
126 126
127 127 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
128 128 thread_count = threads.count()
129 129
130 130 if thread_count > settings.MAX_THREAD_COUNT:
131 131 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 132 old_threads = threads[thread_count - num_threads_to_delete:]
133 133
134 134 for thread in old_threads:
135 135 thread.archived = True
136 136 thread.last_edit_time = timezone.now()
137 137 thread.save()
138 138
139 139 def connect_replies(self, post):
140 140 """
141 141 Connect replies to a post to show them as a reflink map
142 142 """
143 143
144 144 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
145 145 post_id = reply_number.group(1)
146 146 ref_post = self.filter(id=post_id)
147 147 if ref_post.count() > 0:
148 148 referenced_post = ref_post[0]
149 149 referenced_post.referenced_posts.add(post)
150 150 referenced_post.last_edit_time = post.pub_time
151 151 referenced_post.save()
152 152
153 153 referenced_thread = referenced_post.thread_new
154 154 referenced_thread.last_edit_time = post.pub_time
155 155 referenced_thread.save()
156 156
157 157 def get_posts_per_day(self):
158 158 """
159 159 Get average count of posts per day for the last 7 days
160 160 """
161 161
162 162 today = date.today()
163 163 ppd = cache.get(CACHE_KEY_PPD + str(today))
164 164 if ppd:
165 165 return ppd
166 166
167 167 posts_per_days = []
168 168 for i in POSTS_PER_DAY_RANGE:
169 169 day_end = today - timedelta(i + 1)
170 170 day_start = today - timedelta(i + 2)
171 171
172 day_time_start = timezone.make_aware(datetime.combine(day_start,
173 dtime()), timezone.get_current_timezone())
174 day_time_end = timezone.make_aware(datetime.combine(day_end,
175 dtime()), timezone.get_current_timezone())
172 day_time_start = timezone.make_aware(datetime.combine(
173 day_start, dtime()), timezone.get_current_timezone())
174 day_time_end = timezone.make_aware(datetime.combine(
175 day_end, dtime()), timezone.get_current_timezone())
176 176
177 177 posts_per_days.append(float(self.filter(
178 178 pub_time__lte=day_time_end,
179 179 pub_time__gte=day_time_start).count()))
180 180
181 181 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
182 182 len(posts_per_days))
183 183 cache.set(CACHE_KEY_PPD + str(today), ppd)
184 184 return ppd
185 185
186 186
187 187 class Post(models.Model):
188 188 """A post is a message."""
189 189
190 190 objects = PostManager()
191 191
192 192 class Meta:
193 193 app_label = APP_LABEL_BOARDS
194 194
195 195 # TODO Save original file name to some field
196 196 def _update_image_filename(self, filename):
197 197 """Get unique image filename"""
198 198
199 199 path = IMAGES_DIRECTORY
200 200 new_name = str(int(time.mktime(time.gmtime())))
201 201 new_name += str(int(random() * 1000))
202 202 new_name += FILE_EXTENSION_DELIMITER
203 203 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
204 204
205 205 return os.path.join(path, new_name)
206 206
207 207 title = models.CharField(max_length=TITLE_MAX_LENGTH)
208 208 pub_time = models.DateTimeField()
209 209 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
210 210 escape_html=False)
211 211
212 212 image_width = models.IntegerField(default=0)
213 213 image_height = models.IntegerField(default=0)
214 214
215 215 image_pre_width = models.IntegerField(default=0)
216 216 image_pre_height = models.IntegerField(default=0)
217 217
218 218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
219 219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
220 220 width_field='image_width',
221 221 height_field='image_height',
222 222 preview_width_field='image_pre_width',
223 223 preview_height_field='image_pre_height')
224 224 image_hash = models.CharField(max_length=36)
225 225
226 226 poster_ip = models.GenericIPAddressField()
227 227 poster_user_agent = models.TextField()
228 228
229 229 thread = models.ForeignKey('Post', null=True, default=None)
230 230 thread_new = models.ForeignKey('Thread', null=True, default=None)
231 231 last_edit_time = models.DateTimeField()
232 232 user = models.ForeignKey('User', null=True, default=None)
233 233
234 234 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
235 235 null=True,
236 236 blank=True, related_name='rfp+')
237 237
238 238 def __unicode__(self):
239 239 return '#' + str(self.id) + ' ' + self.title + ' (' + \
240 240 self.text.raw[:50] + ')'
241 241
242 242 def get_title(self):
243 243 title = self.title
244 244 if len(title) == 0:
245 245 title = self.text.rendered
246 246
247 247 return title
248 248
249 249 def get_sorted_referenced_posts(self):
250 250 return self.referenced_posts.order_by('id')
251 251
252 252 def is_referenced(self):
253 253 return self.referenced_posts.all().exists()
254 254
255 255 def is_opening(self):
256 256 return self.thread_new.get_opening_post() == self
257 257
258 258 def save(self, *args, **kwargs):
259 259 """
260 260 Save the model and compute the image hash
261 261 """
262 262
263 263 if not self.pk and self.image:
264 264 md5 = hashlib.md5()
265 265 for chunk in self.image.chunks():
266 266 md5.update(chunk)
267 267 self.image_hash = md5.hexdigest()
268 268 super(Post, self).save(*args, **kwargs)
269 269
270 270 @transaction.atomic
271 271 def add_tag(self, tag):
272 272 edit_time = timezone.now()
273 273
274 274 thread = self.thread_new
275 275 thread.add_tag(tag)
276 276 self.last_edit_time = edit_time
277 277 self.save()
278 278
279 279 thread.last_edit_time = edit_time
280 280 thread.save()
281 281
282 282 @transaction.atomic
283 283 def remove_tag(self, tag):
284 284 edit_time = timezone.now()
285 285
286 286 thread = self.thread_new
287 287 thread.remove_tag(tag)
288 288 self.last_edit_time = edit_time
289 289 self.save()
290 290
291 291 thread.last_edit_time = edit_time
292 292 thread.save()
293 293
294 294 def get_url(self):
295 295 """
296 296 Get full url to this post
297 297 """
298 298
299 299 cache_key = CACHE_KEY_POST_URL + str(self.id)
300 300 link = cache.get(cache_key)
301 301
302 302 if not link:
303 303 opening_post = self.thread_new.get_opening_post()
304 304 if self != opening_post:
305 link = reverse('thread',
306 kwargs={'post_id': opening_post.id}) + '#' + str(
307 self.id)
305 link = reverse('thread', kwargs={
306 'post_id': opening_post.id}) + '#' + str(self.id)
308 307 else:
309 308 link = reverse('thread', kwargs={'post_id': self.id})
310 309
311 310 cache.set(cache_key, link)
312 311
313 312 return link
314 313
315 314
316 315 class Thread(models.Model):
317 316
318 317 class Meta:
319 318 app_label = APP_LABEL_BOARDS
320 319
321 320 tags = models.ManyToManyField('Tag')
322 321 bump_time = models.DateTimeField()
323 322 last_edit_time = models.DateTimeField()
324 323 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
325 324 blank=True, related_name='tre+')
326 325 archived = models.BooleanField(default=False)
327 326
328 327 def get_tags(self):
329 328 """
330 329 Get a sorted tag list
331 330 """
332 331
333 332 return self.tags.order_by('name')
334 333
335 334 def bump(self):
336 335 """
337 336 Bump (move to up) thread
338 337 """
339 338
340 339 if self.can_bump():
341 340 self.bump_time = timezone.now()
342 341
343 342 def get_reply_count(self):
344 343 return self.replies.count()
345 344
346 345 def get_images_count(self):
347 346 return self.replies.filter(image_width__gt=0).count()
348 347
349 348 def can_bump(self):
350 349 """
351 350 Check if the thread can be bumped by replying
352 351 """
353 352
354 353 if self.archived:
355 354 return False
356 355
357 356 post_count = self.get_reply_count()
358 357
359 358 return post_count < settings.MAX_POSTS_PER_THREAD
360 359
361 360 def delete_with_posts(self):
362 361 """
363 362 Completely delete thread and all its posts
364 363 """
365 364
366 365 if self.replies.count() > 0:
367 366 self.replies.all().delete()
368 367
369 368 self.delete()
370 369
371 370 def get_last_replies(self):
372 371 """
373 372 Get last replies, not including opening post
374 373 """
375 374
376 375 if settings.LAST_REPLIES_COUNT > 0:
377 376 reply_count = self.get_reply_count()
378 377
379 378 if reply_count > 0:
380 379 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
381 380 reply_count - 1)
382 last_replies = self.replies.all().order_by('pub_time')[
383 reply_count - reply_count_to_show:]
381 last_replies = self.replies.all().order_by(
382 'pub_time')[reply_count - reply_count_to_show:]
384 383
385 384 return last_replies
386 385
387 386 def get_skipped_replies_count(self):
388 387 last_replies = self.get_last_replies()
389 388 return self.get_reply_count() - len(last_replies) - 1
390 389
391 390 def get_replies(self):
392 391 """
393 392 Get sorted thread posts
394 393 """
395 394
396 395 return self.replies.all().order_by('pub_time')
397 396
398 397 def add_tag(self, tag):
399 398 """
400 399 Connect thread to a tag and tag to a thread
401 400 """
402 401
403 402 self.tags.add(tag)
404 403 tag.threads.add(self)
405 404
406 405 def remove_tag(self, tag):
407 406 self.tags.remove(tag)
408 407 tag.threads.remove(self)
409 408
410 409 def get_opening_post(self):
411 410 """
412 411 Get first post of the thread
413 412 """
414 413
415 414 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
416 415 # opening_post = cache.get(cache_key)
417 416 # if not opening_post:
418 417 opening_post = self.get_replies()[0]
419 418 # cache.set(cache_key, opening_post)
420 419
421 420 return opening_post
422 421
423 422 def __unicode__(self):
424 423 return str(self.id)
425 424
426 425 def get_pub_time(self):
427 426 """
428 427 Thread does not have its own pub time, so we need to get it from
429 428 the opening post
430 429 """
431 430
432 431 return self.get_opening_post().pub_time
General Comments 0
You need to be logged in to leave comments. Login now