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