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