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