##// END OF EJS Templates
Don't cache post URLs, this is not a heavy operation
neko259 -
r1111:ae8a8418 default
parent child Browse files
Show More
@@ -1,427 +1,426 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import logging
4 import re
4 import re
5
5
6 from django.core.exceptions import ObjectDoesNotExist
6 from django.core.exceptions import ObjectDoesNotExist
7 from django.core.urlresolvers import reverse
7 from django.core.urlresolvers import reverse
8 from django.db import models, transaction
8 from django.db import models, transaction
9 from django.db.models import TextField
9 from django.db.models import TextField
10 from django.template.loader import render_to_string
10 from django.template.loader import render_to_string
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13 from boards import settings
13 from boards import settings
14 from boards.mdx_neboard import Parser
14 from boards.mdx_neboard import Parser
15 from boards.models import PostImage
15 from boards.models import PostImage
16 from boards.models.base import Viewable
16 from boards.models.base import Viewable
17 from boards import utils
17 from boards import utils
18 from boards.models.user import Notification, Ban
18 from boards.models.user import Notification, Ban
19 import boards.models.thread
19 import boards.models.thread
20
20
21
21
22 APP_LABEL_BOARDS = 'boards'
22 APP_LABEL_BOARDS = 'boards'
23
23
24 POSTS_PER_DAY_RANGE = 7
24 POSTS_PER_DAY_RANGE = 7
25
25
26 BAN_REASON_AUTO = 'Auto'
26 BAN_REASON_AUTO = 'Auto'
27
27
28 IMAGE_THUMB_SIZE = (200, 150)
28 IMAGE_THUMB_SIZE = (200, 150)
29
29
30 TITLE_MAX_LENGTH = 200
30 TITLE_MAX_LENGTH = 200
31
31
32 # TODO This should be removed
32 # TODO This should be removed
33 NO_IP = '0.0.0.0'
33 NO_IP = '0.0.0.0'
34
34
35 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
35 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
36 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
36 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
37
37
38 PARAMETER_TRUNCATED = 'truncated'
38 PARAMETER_TRUNCATED = 'truncated'
39 PARAMETER_TAG = 'tag'
39 PARAMETER_TAG = 'tag'
40 PARAMETER_OFFSET = 'offset'
40 PARAMETER_OFFSET = 'offset'
41 PARAMETER_DIFF_TYPE = 'type'
41 PARAMETER_DIFF_TYPE = 'type'
42 PARAMETER_CSS_CLASS = 'css_class'
42 PARAMETER_CSS_CLASS = 'css_class'
43 PARAMETER_THREAD = 'thread'
43 PARAMETER_THREAD = 'thread'
44 PARAMETER_IS_OPENING = 'is_opening'
44 PARAMETER_IS_OPENING = 'is_opening'
45 PARAMETER_MODERATOR = 'moderator'
45 PARAMETER_MODERATOR = 'moderator'
46 PARAMETER_POST = 'post'
46 PARAMETER_POST = 'post'
47 PARAMETER_OP_ID = 'opening_post_id'
47 PARAMETER_OP_ID = 'opening_post_id'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
50
50
51 DIFF_TYPE_HTML = 'html'
51 DIFF_TYPE_HTML = 'html'
52 DIFF_TYPE_JSON = 'json'
52 DIFF_TYPE_JSON = 'json'
53
53
54 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
54 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
55
55
56
56
57 class PostManager(models.Manager):
57 class PostManager(models.Manager):
58 @transaction.atomic
58 @transaction.atomic
59 def create_post(self, title: str, text: str, image=None, thread=None,
59 def create_post(self, title: str, text: str, image=None, thread=None,
60 ip=NO_IP, tags: list=None, threads: list=None):
60 ip=NO_IP, tags: list=None, threads: list=None):
61 """
61 """
62 Creates new post
62 Creates new post
63 """
63 """
64
64
65 is_banned = Ban.objects.filter(ip=ip).exists()
65 is_banned = Ban.objects.filter(ip=ip).exists()
66
66
67 # TODO Raise specific exception and catch it in the views
67 # TODO Raise specific exception and catch it in the views
68 if is_banned:
68 if is_banned:
69 raise Exception("This user is banned")
69 raise Exception("This user is banned")
70
70
71 if not tags:
71 if not tags:
72 tags = []
72 tags = []
73 if not threads:
73 if not threads:
74 threads = []
74 threads = []
75
75
76 posting_time = timezone.now()
76 posting_time = timezone.now()
77 if not thread:
77 if not thread:
78 thread = boards.models.thread.Thread.objects.create(
78 thread = boards.models.thread.Thread.objects.create(
79 bump_time=posting_time, last_edit_time=posting_time)
79 bump_time=posting_time, last_edit_time=posting_time)
80 new_thread = True
80 new_thread = True
81 else:
81 else:
82 new_thread = False
82 new_thread = False
83
83
84 pre_text = Parser().preparse(text)
84 pre_text = Parser().preparse(text)
85
85
86 post = self.create(title=title,
86 post = self.create(title=title,
87 text=pre_text,
87 text=pre_text,
88 pub_time=posting_time,
88 pub_time=posting_time,
89 poster_ip=ip,
89 poster_ip=ip,
90 thread=thread,
90 thread=thread,
91 last_edit_time=posting_time)
91 last_edit_time=posting_time)
92 post.threads.add(thread)
92 post.threads.add(thread)
93
93
94 logger = logging.getLogger('boards.post.create')
94 logger = logging.getLogger('boards.post.create')
95
95
96 logger.info('Created post {} by {}'.format(post, post.poster_ip))
96 logger.info('Created post {} by {}'.format(post, post.poster_ip))
97
97
98 if image:
98 if image:
99 post.images.add(PostImage.objects.create_with_hash(image))
99 post.images.add(PostImage.objects.create_with_hash(image))
100
100
101 list(map(thread.add_tag, tags))
101 list(map(thread.add_tag, tags))
102
102
103 if new_thread:
103 if new_thread:
104 boards.models.thread.Thread.objects.process_oldest_threads()
104 boards.models.thread.Thread.objects.process_oldest_threads()
105 else:
105 else:
106 thread.last_edit_time = posting_time
106 thread.last_edit_time = posting_time
107 thread.bump()
107 thread.bump()
108 thread.save()
108 thread.save()
109
109
110 post.connect_replies()
110 post.connect_replies()
111 post.connect_threads(threads)
111 post.connect_threads(threads)
112 post.connect_notifications()
112 post.connect_notifications()
113
113
114 return post
114 return post
115
115
116 def delete_posts_by_ip(self, ip):
116 def delete_posts_by_ip(self, ip):
117 """
117 """
118 Deletes all posts of the author with same IP
118 Deletes all posts of the author with same IP
119 """
119 """
120
120
121 posts = self.filter(poster_ip=ip)
121 posts = self.filter(poster_ip=ip)
122 for post in posts:
122 for post in posts:
123 post.delete()
123 post.delete()
124
124
125 @utils.cached_result()
125 @utils.cached_result()
126 def get_posts_per_day(self) -> float:
126 def get_posts_per_day(self) -> float:
127 """
127 """
128 Gets average count of posts per day for the last 7 days
128 Gets average count of posts per day for the last 7 days
129 """
129 """
130
130
131 day_end = date.today()
131 day_end = date.today()
132 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
132 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
133
133
134 day_time_start = timezone.make_aware(datetime.combine(
134 day_time_start = timezone.make_aware(datetime.combine(
135 day_start, dtime()), timezone.get_current_timezone())
135 day_start, dtime()), timezone.get_current_timezone())
136 day_time_end = timezone.make_aware(datetime.combine(
136 day_time_end = timezone.make_aware(datetime.combine(
137 day_end, dtime()), timezone.get_current_timezone())
137 day_end, dtime()), timezone.get_current_timezone())
138
138
139 posts_per_period = float(self.filter(
139 posts_per_period = float(self.filter(
140 pub_time__lte=day_time_end,
140 pub_time__lte=day_time_end,
141 pub_time__gte=day_time_start).count())
141 pub_time__gte=day_time_start).count())
142
142
143 ppd = posts_per_period / POSTS_PER_DAY_RANGE
143 ppd = posts_per_period / POSTS_PER_DAY_RANGE
144
144
145 return ppd
145 return ppd
146
146
147
147
148 class Post(models.Model, Viewable):
148 class Post(models.Model, Viewable):
149 """A post is a message."""
149 """A post is a message."""
150
150
151 objects = PostManager()
151 objects = PostManager()
152
152
153 class Meta:
153 class Meta:
154 app_label = APP_LABEL_BOARDS
154 app_label = APP_LABEL_BOARDS
155 ordering = ('id',)
155 ordering = ('id',)
156
156
157 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
157 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
158 pub_time = models.DateTimeField()
158 pub_time = models.DateTimeField()
159 text = TextField(blank=True, null=True)
159 text = TextField(blank=True, null=True)
160 _text_rendered = TextField(blank=True, null=True, editable=False)
160 _text_rendered = TextField(blank=True, null=True, editable=False)
161
161
162 images = models.ManyToManyField(PostImage, null=True, blank=True,
162 images = models.ManyToManyField(PostImage, null=True, blank=True,
163 related_name='ip+', db_index=True)
163 related_name='ip+', db_index=True)
164
164
165 poster_ip = models.GenericIPAddressField()
165 poster_ip = models.GenericIPAddressField()
166
166
167 last_edit_time = models.DateTimeField()
167 last_edit_time = models.DateTimeField()
168
168
169 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
169 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
170 null=True,
170 null=True,
171 blank=True, related_name='rfp+',
171 blank=True, related_name='rfp+',
172 db_index=True)
172 db_index=True)
173 refmap = models.TextField(null=True, blank=True)
173 refmap = models.TextField(null=True, blank=True)
174 threads = models.ManyToManyField('Thread', db_index=True)
174 threads = models.ManyToManyField('Thread', db_index=True)
175 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
175 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
176
176
177 def __str__(self):
177 def __str__(self):
178 return 'P#{}/{}'.format(self.id, self.title)
178 return 'P#{}/{}'.format(self.id, self.title)
179
179
180 def get_title(self) -> str:
180 def get_title(self) -> str:
181 """
181 """
182 Gets original post title or part of its text.
182 Gets original post title or part of its text.
183 """
183 """
184
184
185 title = self.title
185 title = self.title
186 if not title:
186 if not title:
187 title = self.get_text()
187 title = self.get_text()
188
188
189 return title
189 return title
190
190
191 def build_refmap(self) -> None:
191 def build_refmap(self) -> None:
192 """
192 """
193 Builds a replies map string from replies list. This is a cache to stop
193 Builds a replies map string from replies list. This is a cache to stop
194 the server from recalculating the map on every post show.
194 the server from recalculating the map on every post show.
195 """
195 """
196
196
197 post_urls = [REFMAP_STR.format(refpost.get_url(), refpost.id)
197 post_urls = [REFMAP_STR.format(refpost.get_url(), refpost.id)
198 for refpost in self.referenced_posts.all()]
198 for refpost in self.referenced_posts.all()]
199
199
200 self.refmap = ', '.join(post_urls)
200 self.refmap = ', '.join(post_urls)
201
201
202 def is_referenced(self) -> bool:
202 def is_referenced(self) -> bool:
203 return self.refmap and len(self.refmap) > 0
203 return self.refmap and len(self.refmap) > 0
204
204
205 def is_opening(self) -> bool:
205 def is_opening(self) -> bool:
206 """
206 """
207 Checks if this is an opening post or just a reply.
207 Checks if this is an opening post or just a reply.
208 """
208 """
209
209
210 return self.get_thread().get_opening_post_id() == self.id
210 return self.get_thread().get_opening_post_id() == self.id
211
211
212 @utils.cached_result()
213 def get_url(self):
212 def get_url(self):
214 """
213 """
215 Gets full url to the post.
214 Gets full url to the post.
216 """
215 """
217
216
218 thread = self.get_thread()
217 thread = self.get_thread()
219
218
220 opening_id = thread.get_opening_post_id()
219 opening_id = thread.get_opening_post_id()
221
220
222 thread_url = reverse('thread', kwargs={'post_id': opening_id})
221 thread_url = reverse('thread', kwargs={'post_id': opening_id})
223 if self.id != opening_id:
222 if self.id != opening_id:
224 thread_url += '#' + str(self.id)
223 thread_url += '#' + str(self.id)
225
224
226 return thread_url
225 return thread_url
227
226
228 def get_thread(self):
227 def get_thread(self):
229 return self.thread
228 return self.thread
230
229
231 def get_threads(self) -> list:
230 def get_threads(self) -> list:
232 """
231 """
233 Gets post's thread.
232 Gets post's thread.
234 """
233 """
235
234
236 return self.threads
235 return self.threads
237
236
238 def get_view(self, moderator=False, need_open_link=False,
237 def get_view(self, moderator=False, need_open_link=False,
239 truncated=False, reply_link=False, *args, **kwargs) -> str:
238 truncated=False, reply_link=False, *args, **kwargs) -> str:
240 """
239 """
241 Renders post's HTML view. Some of the post params can be passed over
240 Renders post's HTML view. Some of the post params can be passed over
242 kwargs for the means of caching (if we view the thread, some params
241 kwargs for the means of caching (if we view the thread, some params
243 are same for every post and don't need to be computed over and over.
242 are same for every post and don't need to be computed over and over.
244 """
243 """
245
244
246 thread = self.get_thread()
245 thread = self.get_thread()
247 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
246 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
248
247
249 if is_opening:
248 if is_opening:
250 opening_post_id = self.id
249 opening_post_id = self.id
251 else:
250 else:
252 opening_post_id = thread.get_opening_post_id()
251 opening_post_id = thread.get_opening_post_id()
253
252
254 css_class = 'post'
253 css_class = 'post'
255 if thread.archived:
254 if thread.archived:
256 css_class += ' archive_post'
255 css_class += ' archive_post'
257 elif not thread.can_bump():
256 elif not thread.can_bump():
258 css_class += ' dead_post'
257 css_class += ' dead_post'
259
258
260 return render_to_string('boards/post.html', {
259 return render_to_string('boards/post.html', {
261 PARAMETER_POST: self,
260 PARAMETER_POST: self,
262 PARAMETER_MODERATOR: moderator,
261 PARAMETER_MODERATOR: moderator,
263 PARAMETER_IS_OPENING: is_opening,
262 PARAMETER_IS_OPENING: is_opening,
264 PARAMETER_THREAD: thread,
263 PARAMETER_THREAD: thread,
265 PARAMETER_CSS_CLASS: css_class,
264 PARAMETER_CSS_CLASS: css_class,
266 PARAMETER_NEED_OPEN_LINK: need_open_link,
265 PARAMETER_NEED_OPEN_LINK: need_open_link,
267 PARAMETER_TRUNCATED: truncated,
266 PARAMETER_TRUNCATED: truncated,
268 PARAMETER_OP_ID: opening_post_id,
267 PARAMETER_OP_ID: opening_post_id,
269 PARAMETER_REPLY_LINK: reply_link,
268 PARAMETER_REPLY_LINK: reply_link,
270 })
269 })
271
270
272 def get_search_view(self, *args, **kwargs):
271 def get_search_view(self, *args, **kwargs):
273 return self.get_view(args, kwargs)
272 return self.get_view(args, kwargs)
274
273
275 def get_first_image(self) -> PostImage:
274 def get_first_image(self) -> PostImage:
276 return self.images.earliest('id')
275 return self.images.earliest('id')
277
276
278 def delete(self, using=None):
277 def delete(self, using=None):
279 """
278 """
280 Deletes all post images and the post itself.
279 Deletes all post images and the post itself.
281 """
280 """
282
281
283 for image in self.images.all():
282 for image in self.images.all():
284 image_refs_count = Post.objects.filter(images__in=[image]).count()
283 image_refs_count = Post.objects.filter(images__in=[image]).count()
285 if image_refs_count == 1:
284 if image_refs_count == 1:
286 image.delete()
285 image.delete()
287
286
288 thread = self.get_thread()
287 thread = self.get_thread()
289 thread.last_edit_time = timezone.now()
288 thread.last_edit_time = timezone.now()
290 thread.save()
289 thread.save()
291
290
292 super(Post, self).delete(using)
291 super(Post, self).delete(using)
293
292
294 logging.getLogger('boards.post.delete').info(
293 logging.getLogger('boards.post.delete').info(
295 'Deleted post {}'.format(self))
294 'Deleted post {}'.format(self))
296
295
297 # TODO Implement this with OOP, e.g. use the factory and HtmlPostData class
296 # TODO Implement this with OOP, e.g. use the factory and HtmlPostData class
298 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
297 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
299 include_last_update=False) -> str:
298 include_last_update=False) -> str:
300 """
299 """
301 Gets post HTML or JSON data that can be rendered on a page or used by
300 Gets post HTML or JSON data that can be rendered on a page or used by
302 API.
301 API.
303 """
302 """
304
303
305 if format_type == DIFF_TYPE_HTML:
304 if format_type == DIFF_TYPE_HTML:
306 if PARAMETER_TRUNCATED in request.GET:
305 if PARAMETER_TRUNCATED in request.GET:
307 truncated = True
306 truncated = True
308 reply_link = False
307 reply_link = False
309 else:
308 else:
310 truncated = False
309 truncated = False
311 reply_link = True
310 reply_link = True
312
311
313 return self.get_view(truncated=truncated, reply_link=reply_link,
312 return self.get_view(truncated=truncated, reply_link=reply_link,
314 moderator=utils.is_moderator(request))
313 moderator=utils.is_moderator(request))
315 elif format_type == DIFF_TYPE_JSON:
314 elif format_type == DIFF_TYPE_JSON:
316 post_json = {
315 post_json = {
317 'id': self.id,
316 'id': self.id,
318 'title': self.title,
317 'title': self.title,
319 'text': self._text_rendered,
318 'text': self._text_rendered,
320 }
319 }
321 if self.images.exists():
320 if self.images.exists():
322 post_image = self.get_first_image()
321 post_image = self.get_first_image()
323 post_json['image'] = post_image.image.url
322 post_json['image'] = post_image.image.url
324 post_json['image_preview'] = post_image.image.url_200x150
323 post_json['image_preview'] = post_image.image.url_200x150
325 if include_last_update:
324 if include_last_update:
326 post_json['bump_time'] = utils.datetime_to_epoch(
325 post_json['bump_time'] = utils.datetime_to_epoch(
327 self.get_thread().bump_time)
326 self.get_thread().bump_time)
328 return post_json
327 return post_json
329
328
330 def notify_clients(self, recursive=True):
329 def notify_clients(self, recursive=True):
331 """
330 """
332 Sends post HTML data to the thread web socket.
331 Sends post HTML data to the thread web socket.
333 """
332 """
334
333
335 if not settings.WEBSOCKETS_ENABLED:
334 if not settings.WEBSOCKETS_ENABLED:
336 return
335 return
337
336
338 thread_ids = list()
337 thread_ids = list()
339 for thread in self.get_threads().all():
338 for thread in self.get_threads().all():
340 thread_ids.append(thread.id)
339 thread_ids.append(thread.id)
341
340
342 thread.notify_clients()
341 thread.notify_clients()
343
342
344 if recursive:
343 if recursive:
345 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
344 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
346 post_id = reply_number.group(1)
345 post_id = reply_number.group(1)
347
346
348 try:
347 try:
349 ref_post = Post.objects.get(id=post_id)
348 ref_post = Post.objects.get(id=post_id)
350
349
351 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
350 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
352 # If post is in this thread, its thread was already notified.
351 # If post is in this thread, its thread was already notified.
353 # Otherwise, notify its thread separately.
352 # Otherwise, notify its thread separately.
354 ref_post.notify_clients(recursive=False)
353 ref_post.notify_clients(recursive=False)
355 except ObjectDoesNotExist:
354 except ObjectDoesNotExist:
356 pass
355 pass
357
356
358 def save(self, force_insert=False, force_update=False, using=None,
357 def save(self, force_insert=False, force_update=False, using=None,
359 update_fields=None):
358 update_fields=None):
360 self._text_rendered = Parser().parse(self.get_raw_text())
359 self._text_rendered = Parser().parse(self.get_raw_text())
361
360
362 if self.id:
361 if self.id:
363 for thread in self.get_threads().all():
362 for thread in self.get_threads().all():
364 if thread.can_bump():
363 if thread.can_bump():
365 thread.update_bump_status()
364 thread.update_bump_status()
366 thread.last_edit_time = self.last_edit_time
365 thread.last_edit_time = self.last_edit_time
367
366
368 thread.save(update_fields=['last_edit_time', 'bumpable'])
367 thread.save(update_fields=['last_edit_time', 'bumpable'])
369
368
370 super().save(force_insert, force_update, using, update_fields)
369 super().save(force_insert, force_update, using, update_fields)
371
370
372 def get_text(self) -> str:
371 def get_text(self) -> str:
373 return self._text_rendered
372 return self._text_rendered
374
373
375 def get_raw_text(self) -> str:
374 def get_raw_text(self) -> str:
376 return self.text
375 return self.text
377
376
378 def get_absolute_id(self) -> str:
377 def get_absolute_id(self) -> str:
379 """
378 """
380 If the post has many threads, shows its main thread OP id in the post
379 If the post has many threads, shows its main thread OP id in the post
381 ID.
380 ID.
382 """
381 """
383
382
384 if self.get_threads().count() > 1:
383 if self.get_threads().count() > 1:
385 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
384 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
386 else:
385 else:
387 return str(self.id)
386 return str(self.id)
388
387
389 def connect_notifications(self):
388 def connect_notifications(self):
390 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
389 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
391 user_name = reply_number.group(1).lower()
390 user_name = reply_number.group(1).lower()
392 Notification.objects.get_or_create(name=user_name, post=self)
391 Notification.objects.get_or_create(name=user_name, post=self)
393
392
394 def connect_replies(self):
393 def connect_replies(self):
395 """
394 """
396 Connects replies to a post to show them as a reflink map
395 Connects replies to a post to show them as a reflink map
397 """
396 """
398
397
399 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
398 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
400 post_id = reply_number.group(1)
399 post_id = reply_number.group(1)
401
400
402 try:
401 try:
403 referenced_post = Post.objects.get(id=post_id)
402 referenced_post = Post.objects.get(id=post_id)
404
403
405 referenced_post.referenced_posts.add(self)
404 referenced_post.referenced_posts.add(self)
406 referenced_post.last_edit_time = self.pub_time
405 referenced_post.last_edit_time = self.pub_time
407 referenced_post.build_refmap()
406 referenced_post.build_refmap()
408 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
407 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
409 except ObjectDoesNotExist:
408 except ObjectDoesNotExist:
410 pass
409 pass
411
410
412 def connect_threads(self, opening_posts):
411 def connect_threads(self, opening_posts):
413 """
412 """
414 If the referenced post is an OP in another thread,
413 If the referenced post is an OP in another thread,
415 make this post multi-thread.
414 make this post multi-thread.
416 """
415 """
417
416
418 for opening_post in opening_posts:
417 for opening_post in opening_posts:
419 threads = opening_post.get_threads().all()
418 threads = opening_post.get_threads().all()
420 for thread in threads:
419 for thread in threads:
421 if thread.can_bump():
420 if thread.can_bump():
422 thread.update_bump_status()
421 thread.update_bump_status()
423
422
424 thread.last_edit_time = self.last_edit_time
423 thread.last_edit_time = self.last_edit_time
425 thread.save(update_fields=['last_edit_time', 'bumpable'])
424 thread.save(update_fields=['last_edit_time', 'bumpable'])
426
425
427 self.threads.add(thread)
426 self.threads.add(thread)
General Comments 0
You need to be logged in to leave comments. Login now