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