##// END OF EJS Templates
Speed up referenced posts getting a bit
neko259 -
r397:7372aab3 default
parent child Browse files
Show More
@@ -1,272 +1,272 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 import re
5 import re
6
6
7 from django.db import models
7 from django.db import models
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 from boards import settings as boards_settings
13 from boards import settings as boards_settings
14 from boards import thumbs
14 from boards import thumbs
15
15
16 BAN_REASON_AUTO = 'Auto'
16 BAN_REASON_AUTO = 'Auto'
17
17
18 IMAGE_THUMB_SIZE = (200, 150)
18 IMAGE_THUMB_SIZE = (200, 150)
19
19
20 TITLE_MAX_LENGTH = 50
20 TITLE_MAX_LENGTH = 50
21
21
22 DEFAULT_MARKUP_TYPE = 'markdown'
22 DEFAULT_MARKUP_TYPE = 'markdown'
23
23
24 NO_PARENT = -1
24 NO_PARENT = -1
25 NO_IP = '0.0.0.0'
25 NO_IP = '0.0.0.0'
26 UNKNOWN_UA = ''
26 UNKNOWN_UA = ''
27 ALL_PAGES = -1
27 ALL_PAGES = -1
28 IMAGES_DIRECTORY = 'images/'
28 IMAGES_DIRECTORY = 'images/'
29 FILE_EXTENSION_DELIMITER = '.'
29 FILE_EXTENSION_DELIMITER = '.'
30
30
31 SETTING_MODERATE = "moderate"
31 SETTING_MODERATE = "moderate"
32
32
33 REGEX_REPLY = re.compile('>>(\d+)')
33 REGEX_REPLY = re.compile('>>(\d+)')
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 posting_time = timezone.now()
40 posting_time = timezone.now()
41
41
42 post = self.create(title=title,
42 post = self.create(title=title,
43 text=text,
43 text=text,
44 pub_time=posting_time,
44 pub_time=posting_time,
45 thread=thread,
45 thread=thread,
46 image=image,
46 image=image,
47 poster_ip=ip,
47 poster_ip=ip,
48 poster_user_agent=UNKNOWN_UA,
48 poster_user_agent=UNKNOWN_UA,
49 last_edit_time=posting_time,
49 last_edit_time=posting_time,
50 bump_time=posting_time,
50 bump_time=posting_time,
51 user=user)
51 user=user)
52
52
53 if tags:
53 if tags:
54 linked_tags = []
54 linked_tags = []
55 for tag in tags:
55 for tag in tags:
56 tag_linked_tags = tag.get_linked_tags()
56 tag_linked_tags = tag.get_linked_tags()
57 if len(tag_linked_tags) > 0:
57 if len(tag_linked_tags) > 0:
58 linked_tags.extend(tag_linked_tags)
58 linked_tags.extend(tag_linked_tags)
59
59
60 tags.extend(linked_tags)
60 tags.extend(linked_tags)
61 map(post.tags.add, tags)
61 map(post.tags.add, tags)
62 for tag in tags:
62 for tag in tags:
63 tag.threads.add(post)
63 tag.threads.add(post)
64
64
65 if thread:
65 if thread:
66 thread.replies.add(post)
66 thread.replies.add(post)
67 thread.bump()
67 thread.bump()
68 thread.last_edit_time = posting_time
68 thread.last_edit_time = posting_time
69 thread.save()
69 thread.save()
70 else:
70 else:
71 self._delete_old_threads()
71 self._delete_old_threads()
72
72
73 self.connect_replies(post)
73 self.connect_replies(post)
74
74
75 return post
75 return post
76
76
77 def delete_post(self, post):
77 def delete_post(self, post):
78 if post.replies.count() > 0:
78 if post.replies.count() > 0:
79 map(self.delete_post, post.replies.all())
79 map(self.delete_post, post.replies.all())
80
80
81 # Update thread's last edit time
81 # Update thread's last edit time
82 thread = post.thread
82 thread = post.thread
83 if thread:
83 if thread:
84 thread.last_edit_time = timezone.now()
84 thread.last_edit_time = timezone.now()
85 thread.save()
85 thread.save()
86
86
87 post.delete()
87 post.delete()
88
88
89 def delete_posts_by_ip(self, ip):
89 def delete_posts_by_ip(self, ip):
90 posts = self.filter(poster_ip=ip)
90 posts = self.filter(poster_ip=ip)
91 map(self.delete_post, posts)
91 map(self.delete_post, posts)
92
92
93 def get_threads(self, tag=None, page=ALL_PAGES,
93 def get_threads(self, tag=None, page=ALL_PAGES,
94 order_by='-bump_time'):
94 order_by='-bump_time'):
95 if tag:
95 if tag:
96 threads = tag.threads
96 threads = tag.threads
97
97
98 if threads.count() == 0:
98 if threads.count() == 0:
99 raise Http404
99 raise Http404
100 else:
100 else:
101 threads = self.filter(thread=None)
101 threads = self.filter(thread=None)
102
102
103 threads = threads.order_by(order_by)
103 threads = threads.order_by(order_by)
104
104
105 if page != ALL_PAGES:
105 if page != ALL_PAGES:
106 thread_count = threads.count()
106 thread_count = threads.count()
107
107
108 if page < self._get_page_count(thread_count):
108 if page < self._get_page_count(thread_count):
109 start_thread = page * settings.THREADS_PER_PAGE
109 start_thread = page * settings.THREADS_PER_PAGE
110 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
110 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
111 thread_count)
111 thread_count)
112 threads = threads[start_thread:end_thread]
112 threads = threads[start_thread:end_thread]
113
113
114 return threads
114 return threads
115
115
116 def get_thread(self, opening_post_id):
116 def get_thread(self, opening_post_id):
117 try:
117 try:
118 opening_post = self.get(id=opening_post_id, thread=None)
118 opening_post = self.get(id=opening_post_id, thread=None)
119 except Post.DoesNotExist:
119 except Post.DoesNotExist:
120 raise Http404
120 raise Http404
121
121
122 if opening_post.replies:
122 if opening_post.replies:
123 thread = [opening_post]
123 thread = [opening_post]
124 thread.extend(opening_post.replies.all().order_by('pub_time'))
124 thread.extend(opening_post.replies.all().order_by('pub_time'))
125
125
126 return thread
126 return thread
127
127
128 def get_thread_page_count(self, tag=None):
128 def get_thread_page_count(self, tag=None):
129 if tag:
129 if tag:
130 threads = self.filter(thread=None, tags=tag)
130 threads = self.filter(thread=None, tags=tag)
131 else:
131 else:
132 threads = self.filter(thread=None)
132 threads = self.filter(thread=None)
133
133
134 return self._get_page_count(threads.count())
134 return self._get_page_count(threads.count())
135
135
136 def _delete_old_threads(self):
136 def _delete_old_threads(self):
137 """
137 """
138 Preserves maximum thread count. If there are too many threads,
138 Preserves maximum thread count. If there are too many threads,
139 delete the old ones.
139 delete the old ones.
140 """
140 """
141
141
142 # TODO Move old threads to the archive instead of deleting them.
142 # TODO Move old threads to the archive instead of deleting them.
143 # Maybe make some 'old' field in the model to indicate the thread
143 # Maybe make some 'old' field in the model to indicate the thread
144 # must not be shown and be able for replying.
144 # must not be shown and be able for replying.
145
145
146 threads = self.get_threads()
146 threads = self.get_threads()
147 thread_count = threads.count()
147 thread_count = threads.count()
148
148
149 if thread_count > settings.MAX_THREAD_COUNT:
149 if thread_count > settings.MAX_THREAD_COUNT:
150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
151 old_threads = threads[thread_count - num_threads_to_delete:]
151 old_threads = threads[thread_count - num_threads_to_delete:]
152
152
153 map(self.delete_post, old_threads)
153 map(self.delete_post, old_threads)
154
154
155 def connect_replies(self, post):
155 def connect_replies(self, post):
156 """Connect replies to a post to show them as a refmap"""
156 """Connect replies to a post to show them as a refmap"""
157
157
158 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
158 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
159 post_id = reply_number.group(1)
159 post_id = reply_number.group(1)
160 ref_post = self.filter(id=post_id)
160 ref_post = self.filter(id=post_id)
161 if ref_post.count() > 0:
161 if ref_post.count() > 0:
162 referenced_post = ref_post[0]
162 referenced_post = ref_post[0]
163 referenced_post.referenced_posts.add(post)
163 referenced_post.referenced_posts.add(post)
164 referenced_post.last_edit_time = post.pub_time
164 referenced_post.last_edit_time = post.pub_time
165 referenced_post.save()
165 referenced_post.save()
166
166
167 def _get_page_count(self, thread_count):
167 def _get_page_count(self, thread_count):
168 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
168 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
169
169
170
170
171 class Post(models.Model):
171 class Post(models.Model):
172 """A post is a message."""
172 """A post is a message."""
173
173
174 objects = PostManager()
174 objects = PostManager()
175
175
176 class Meta:
176 class Meta:
177 app_label = 'boards'
177 app_label = 'boards'
178
178
179 def _update_image_filename(self, filename):
179 def _update_image_filename(self, filename):
180 """Get unique image filename"""
180 """Get unique image filename"""
181
181
182 path = IMAGES_DIRECTORY
182 path = IMAGES_DIRECTORY
183 new_name = str(int(time.mktime(time.gmtime())))
183 new_name = str(int(time.mktime(time.gmtime())))
184 new_name += str(int(random() * 1000))
184 new_name += str(int(random() * 1000))
185 new_name += FILE_EXTENSION_DELIMITER
185 new_name += FILE_EXTENSION_DELIMITER
186 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
186 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
187
187
188 return os.path.join(path, new_name)
188 return os.path.join(path, new_name)
189
189
190 title = models.CharField(max_length=TITLE_MAX_LENGTH)
190 title = models.CharField(max_length=TITLE_MAX_LENGTH)
191 pub_time = models.DateTimeField()
191 pub_time = models.DateTimeField()
192 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
192 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
193 escape_html=False)
193 escape_html=False)
194
194
195 image_width = models.IntegerField(default=0)
195 image_width = models.IntegerField(default=0)
196 image_height = models.IntegerField(default=0)
196 image_height = models.IntegerField(default=0)
197
197
198 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
198 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
199 blank=True, sizes=(IMAGE_THUMB_SIZE,),
199 blank=True, sizes=(IMAGE_THUMB_SIZE,),
200 width_field='image_width',
200 width_field='image_width',
201 height_field='image_height')
201 height_field='image_height')
202
202
203 poster_ip = models.GenericIPAddressField()
203 poster_ip = models.GenericIPAddressField()
204 poster_user_agent = models.TextField()
204 poster_user_agent = models.TextField()
205
205
206 thread = models.ForeignKey('Post', null=True, default=None)
206 thread = models.ForeignKey('Post', null=True, default=None)
207 tags = models.ManyToManyField('Tag')
207 tags = models.ManyToManyField('Tag')
208 last_edit_time = models.DateTimeField()
208 last_edit_time = models.DateTimeField()
209 bump_time = models.DateTimeField()
209 bump_time = models.DateTimeField()
210 user = models.ForeignKey('User', null=True, default=None)
210 user = models.ForeignKey('User', null=True, default=None)
211
211
212 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
212 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
213 blank=True, related_name='re+')
213 blank=True, related_name='re+')
214 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
214 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
215 null=True,
215 null=True,
216 blank=True, related_name='rfp+')
216 blank=True, related_name='rfp+')
217
217
218 def __unicode__(self):
218 def __unicode__(self):
219 return '#' + str(self.id) + ' ' + self.title + ' (' + \
219 return '#' + str(self.id) + ' ' + self.title + ' (' + \
220 self.text.raw[:50] + ')'
220 self.text.raw[:50] + ')'
221
221
222 def get_title(self):
222 def get_title(self):
223 title = self.title
223 title = self.title
224 if len(title) == 0:
224 if len(title) == 0:
225 title = self.text.raw[:20]
225 title = self.text.raw[:20]
226
226
227 return title
227 return title
228
228
229 def get_reply_count(self):
229 def get_reply_count(self):
230 return self.replies.count()
230 return self.replies.count()
231
231
232 def get_images_count(self):
232 def get_images_count(self):
233 images_count = 1 if self.image else 0
233 images_count = 1 if self.image else 0
234 images_count += self.replies.filter(image_width__gt=0).count()
234 images_count += self.replies.filter(image_width__gt=0).count()
235
235
236 return images_count
236 return images_count
237
237
238 def can_bump(self):
238 def can_bump(self):
239 """Check if the thread can be bumped by replying"""
239 """Check if the thread can be bumped by replying"""
240
240
241 post_count = self.get_reply_count()
241 post_count = self.get_reply_count()
242
242
243 return post_count <= settings.MAX_POSTS_PER_THREAD
243 return post_count <= settings.MAX_POSTS_PER_THREAD
244
244
245 def bump(self):
245 def bump(self):
246 """Bump (move to up) thread"""
246 """Bump (move to up) thread"""
247
247
248 if self.can_bump():
248 if self.can_bump():
249 self.bump_time = timezone.now()
249 self.bump_time = timezone.now()
250
250
251 def get_last_replies(self):
251 def get_last_replies(self):
252 if settings.LAST_REPLIES_COUNT > 0:
252 if settings.LAST_REPLIES_COUNT > 0:
253 reply_count = self.get_reply_count()
253 reply_count = self.get_reply_count()
254
254
255 if reply_count > 0:
255 if reply_count > 0:
256 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
256 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
257 reply_count)
257 reply_count)
258 last_replies = self.replies.all().order_by('pub_time')[
258 last_replies = self.replies.all().order_by('pub_time')[
259 reply_count - reply_count_to_show:]
259 reply_count - reply_count_to_show:]
260
260
261 return last_replies
261 return last_replies
262
262
263 def get_tags(self):
263 def get_tags(self):
264 """Get a sorted tag list"""
264 """Get a sorted tag list"""
265
265
266 return self.tags.order_by('name')
266 return self.tags.order_by('name')
267
267
268 def get_sorted_referenced_posts(self):
268 def get_sorted_referenced_posts(self):
269 return self.referenced_posts.order_by('id')
269 return self.referenced_posts.order_by('id')
270
270
271 def is_referenced(self):
271 def is_referenced(self):
272 return self.referenced_posts.count() > 0
272 return self.referenced_posts.all().exists()
General Comments 0
You need to be logged in to leave comments. Login now