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