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