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