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