##// END OF EJS Templates
Define signals outside of the post model
neko259 -
r1512:2edb321f decentral
parent child Browse files
Show More
@@ -0,0 +1,11 b''
1 from django.apps import AppConfig
2
3
4 class BoardsAppConfig(AppConfig):
5 name = 'boards'
6 verbose_name = 'Boards'
7
8 def ready(self):
9 super().ready()
10
11 import boards.signals No newline at end of file
@@ -0,0 +1,62 b''
1 import re
2 from boards.mdx_neboard import get_parser
3
4 from boards.models import Post
5 from boards.models.post import REGEX_NOTIFICATION
6 from boards.models.post import REGEX_REPLY
7 from boards.models.user import Notification
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
9 post_delete
10 from django.dispatch import receiver
11 from django.utils import timezone
12
13
14 @receiver(post_save, sender=Post)
15 def connect_replies(instance, **kwargs):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
17 post_id = reply_number.group(1)
18
19 try:
20 referenced_post = Post.objects.get(id=post_id)
21
22 referenced_post.referenced_posts.add(instance)
23 referenced_post.last_edit_time = instance.pub_time
24 referenced_post.build_refmap()
25 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
26 except Post.ObjectDoesNotExist:
27 pass
28
29
30 @receiver(post_save, sender=Post)
31 def connect_notifications(instance, **kwargs):
32 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
33 user_name = reply_number.group(1).lower()
34 Notification.objects.get_or_create(name=user_name, post=instance)
35
36
37 @receiver(pre_save, sender=Post)
38 def preparse_text(instance, **kwargs):
39 instance._text_rendered = get_parser().parse(instance.get_raw_text())
40
41
42 @receiver(pre_delete, sender=Post)
43 def delete_images(instance, **kwargs):
44 for image in instance.images.all():
45 image_refs_count = image.post_images.count()
46 if image_refs_count == 1:
47 image.delete()
48
49
50 @receiver(pre_delete, sender=Post)
51 def delete_attachments(instance, **kwargs):
52 for attachment in instance.attachments.all():
53 attachment_refs_count = attachment.attachment_posts.count()
54 if attachment_refs_count == 1:
55 attachment.delete()
56
57
58 @receiver(post_delete, sender=Post)
59 def update_thread_on_delete(instance, **kwargs):
60 thread = instance.get_thread()
61 thread.last_edit_time = timezone.now()
62 thread.save()
@@ -0,0 +1,1 b''
1 default_app_config = 'boards.apps.BoardsAppConfig' No newline at end of file
@@ -1,453 +1,401 b''
1 import logging
1 import logging
2 import uuid
2 import uuid
3
3
4 import re
4 import re
5 from boards import settings
5 from boards import settings
6 from boards.abstracts.tripcode import Tripcode
6 from boards.abstracts.tripcode import Tripcode
7 from boards.mdx_neboard import get_parser
7 from boards.mdx_neboard import get_parser
8 from boards.models import PostImage, Attachment, KeyPair, GlobalId
8 from boards.models import PostImage, Attachment, KeyPair, GlobalId
9 from boards.models.base import Viewable
9 from boards.models.base import Viewable
10 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
10 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
11 from boards.models.post.manager import PostManager
11 from boards.models.post.manager import PostManager
12 from boards.models.user import Notification
12 from boards.models.user import Notification
13 from django.core.exceptions import ObjectDoesNotExist
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.core.urlresolvers import reverse
14 from django.core.urlresolvers import reverse
15 from django.db import models
15 from django.db import models
16 from django.db.models import TextField, QuerySet
16 from django.db.models import TextField, QuerySet
17 from django.db.models.signals import pre_save, post_save, pre_delete, \
17 from django.db.models.signals import pre_save, post_save, pre_delete, \
18 post_delete
18 post_delete
19 from django.dispatch import receiver
19 from django.dispatch import receiver
20 from django.template.defaultfilters import truncatewords, striptags
20 from django.template.defaultfilters import truncatewords, striptags
21 from django.template.loader import render_to_string
21 from django.template.loader import render_to_string
22 from django.utils import timezone
22 from django.utils import timezone
23
23
24 CSS_CLS_HIDDEN_POST = 'hidden_post'
24 CSS_CLS_HIDDEN_POST = 'hidden_post'
25 CSS_CLS_DEAD_POST = 'dead_post'
25 CSS_CLS_DEAD_POST = 'dead_post'
26 CSS_CLS_ARCHIVE_POST = 'archive_post'
26 CSS_CLS_ARCHIVE_POST = 'archive_post'
27 CSS_CLS_POST = 'post'
27 CSS_CLS_POST = 'post'
28 CSS_CLS_MONOCHROME = 'monochrome'
28 CSS_CLS_MONOCHROME = 'monochrome'
29
29
30 TITLE_MAX_WORDS = 10
30 TITLE_MAX_WORDS = 10
31
31
32 APP_LABEL_BOARDS = 'boards'
32 APP_LABEL_BOARDS = 'boards'
33
33
34 BAN_REASON_AUTO = 'Auto'
34 BAN_REASON_AUTO = 'Auto'
35
35
36 IMAGE_THUMB_SIZE = (200, 150)
36 IMAGE_THUMB_SIZE = (200, 150)
37
37
38 TITLE_MAX_LENGTH = 200
38 TITLE_MAX_LENGTH = 200
39
39
40 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
40 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
41 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
41 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
42 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
42 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
43 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
43 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
44
44
45 PARAMETER_TRUNCATED = 'truncated'
45 PARAMETER_TRUNCATED = 'truncated'
46 PARAMETER_TAG = 'tag'
46 PARAMETER_TAG = 'tag'
47 PARAMETER_OFFSET = 'offset'
47 PARAMETER_OFFSET = 'offset'
48 PARAMETER_DIFF_TYPE = 'type'
48 PARAMETER_DIFF_TYPE = 'type'
49 PARAMETER_CSS_CLASS = 'css_class'
49 PARAMETER_CSS_CLASS = 'css_class'
50 PARAMETER_THREAD = 'thread'
50 PARAMETER_THREAD = 'thread'
51 PARAMETER_IS_OPENING = 'is_opening'
51 PARAMETER_IS_OPENING = 'is_opening'
52 PARAMETER_POST = 'post'
52 PARAMETER_POST = 'post'
53 PARAMETER_OP_ID = 'opening_post_id'
53 PARAMETER_OP_ID = 'opening_post_id'
54 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
54 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
55 PARAMETER_REPLY_LINK = 'reply_link'
55 PARAMETER_REPLY_LINK = 'reply_link'
56 PARAMETER_NEED_OP_DATA = 'need_op_data'
56 PARAMETER_NEED_OP_DATA = 'need_op_data'
57
57
58 POST_VIEW_PARAMS = (
58 POST_VIEW_PARAMS = (
59 'need_op_data',
59 'need_op_data',
60 'reply_link',
60 'reply_link',
61 'need_open_link',
61 'need_open_link',
62 'truncated',
62 'truncated',
63 'mode_tree',
63 'mode_tree',
64 'perms',
64 'perms',
65 'tree_depth',
65 'tree_depth',
66 )
66 )
67
67
68
68
69 class Post(models.Model, Viewable):
69 class Post(models.Model, Viewable):
70 """A post is a message."""
70 """A post is a message."""
71
71
72 objects = PostManager()
72 objects = PostManager()
73
73
74 class Meta:
74 class Meta:
75 app_label = APP_LABEL_BOARDS
75 app_label = APP_LABEL_BOARDS
76 ordering = ('id',)
76 ordering = ('id',)
77
77
78 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
78 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
79 pub_time = models.DateTimeField()
79 pub_time = models.DateTimeField()
80 text = TextField(blank=True, null=True)
80 text = TextField(blank=True, null=True)
81 _text_rendered = TextField(blank=True, null=True, editable=False)
81 _text_rendered = TextField(blank=True, null=True, editable=False)
82
82
83 images = models.ManyToManyField(PostImage, null=True, blank=True,
83 images = models.ManyToManyField(PostImage, null=True, blank=True,
84 related_name='post_images', db_index=True)
84 related_name='post_images', db_index=True)
85 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
85 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
86 related_name='attachment_posts')
86 related_name='attachment_posts')
87
87
88 poster_ip = models.GenericIPAddressField()
88 poster_ip = models.GenericIPAddressField()
89
89
90 # TODO This field can be removed cause UID is used for update now
90 # TODO This field can be removed cause UID is used for update now
91 last_edit_time = models.DateTimeField()
91 last_edit_time = models.DateTimeField()
92
92
93 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
93 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
94 null=True,
94 null=True,
95 blank=True, related_name='refposts',
95 blank=True, related_name='refposts',
96 db_index=True)
96 db_index=True)
97 refmap = models.TextField(null=True, blank=True)
97 refmap = models.TextField(null=True, blank=True)
98 threads = models.ManyToManyField('Thread', db_index=True,
98 threads = models.ManyToManyField('Thread', db_index=True,
99 related_name='multi_replies')
99 related_name='multi_replies')
100 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
100 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
101
101
102 url = models.TextField()
102 url = models.TextField()
103 uid = models.TextField(db_index=True)
103 uid = models.TextField(db_index=True)
104
104
105 # Global ID with author key. If the message was downloaded from another
105 # Global ID with author key. If the message was downloaded from another
106 # server, this indicates the server.
106 # server, this indicates the server.
107 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
107 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
108 on_delete=models.CASCADE)
108 on_delete=models.CASCADE)
109
109
110 tripcode = models.CharField(max_length=50, blank=True, default='')
110 tripcode = models.CharField(max_length=50, blank=True, default='')
111 opening = models.BooleanField(db_index=True)
111 opening = models.BooleanField(db_index=True)
112 hidden = models.BooleanField(default=False)
112 hidden = models.BooleanField(default=False)
113
113
114 def __str__(self):
114 def __str__(self):
115 return 'P#{}/{}'.format(self.id, self.get_title())
115 return 'P#{}/{}'.format(self.id, self.get_title())
116
116
117 def get_referenced_posts(self):
117 def get_referenced_posts(self):
118 threads = self.get_threads().all()
118 threads = self.get_threads().all()
119 return self.referenced_posts.filter(threads__in=threads)\
119 return self.referenced_posts.filter(threads__in=threads)\
120 .order_by('pub_time').distinct().all()
120 .order_by('pub_time').distinct().all()
121
121
122 def get_title(self) -> str:
122 def get_title(self) -> str:
123 return self.title
123 return self.title
124
124
125 def get_title_or_text(self):
125 def get_title_or_text(self):
126 title = self.get_title()
126 title = self.get_title()
127 if not title:
127 if not title:
128 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
128 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
129
129
130 return title
130 return title
131
131
132 def build_refmap(self) -> None:
132 def build_refmap(self) -> None:
133 """
133 """
134 Builds a replies map string from replies list. This is a cache to stop
134 Builds a replies map string from replies list. This is a cache to stop
135 the server from recalculating the map on every post show.
135 the server from recalculating the map on every post show.
136 """
136 """
137
137
138 post_urls = [refpost.get_link_view()
138 post_urls = [refpost.get_link_view()
139 for refpost in self.referenced_posts.all()]
139 for refpost in self.referenced_posts.all()]
140
140
141 self.refmap = ', '.join(post_urls)
141 self.refmap = ', '.join(post_urls)
142
142
143 def is_referenced(self) -> bool:
143 def is_referenced(self) -> bool:
144 return self.refmap and len(self.refmap) > 0
144 return self.refmap and len(self.refmap) > 0
145
145
146 def is_opening(self) -> bool:
146 def is_opening(self) -> bool:
147 """
147 """
148 Checks if this is an opening post or just a reply.
148 Checks if this is an opening post or just a reply.
149 """
149 """
150
150
151 return self.opening
151 return self.opening
152
152
153 def get_absolute_url(self, thread=None):
153 def get_absolute_url(self, thread=None):
154 url = None
154 url = None
155
155
156 if thread is None:
156 if thread is None:
157 thread = self.get_thread()
157 thread = self.get_thread()
158
158
159 # Url is cached only for the "main" thread. When getting url
159 # Url is cached only for the "main" thread. When getting url
160 # for other threads, do it manually.
160 # for other threads, do it manually.
161 if self.url:
161 if self.url:
162 url = self.url
162 url = self.url
163
163
164 if url is None:
164 if url is None:
165 opening = self.is_opening()
165 opening = self.is_opening()
166 opening_id = self.id if opening else thread.get_opening_post_id()
166 opening_id = self.id if opening else thread.get_opening_post_id()
167 url = reverse('thread', kwargs={'post_id': opening_id})
167 url = reverse('thread', kwargs={'post_id': opening_id})
168 if not opening:
168 if not opening:
169 url += '#' + str(self.id)
169 url += '#' + str(self.id)
170
170
171 return url
171 return url
172
172
173 def get_thread(self):
173 def get_thread(self):
174 return self.thread
174 return self.thread
175
175
176 def get_thread_id(self):
176 def get_thread_id(self):
177 return self.thread_id
177 return self.thread_id
178
178
179 def get_threads(self) -> QuerySet:
179 def get_threads(self) -> QuerySet:
180 """
180 """
181 Gets post's thread.
181 Gets post's thread.
182 """
182 """
183
183
184 return self.threads
184 return self.threads
185
185
186 def get_view(self, *args, **kwargs) -> str:
186 def get_view(self, *args, **kwargs) -> str:
187 """
187 """
188 Renders post's HTML view. Some of the post params can be passed over
188 Renders post's HTML view. Some of the post params can be passed over
189 kwargs for the means of caching (if we view the thread, some params
189 kwargs for the means of caching (if we view the thread, some params
190 are same for every post and don't need to be computed over and over.
190 are same for every post and don't need to be computed over and over.
191 """
191 """
192
192
193 thread = self.get_thread()
193 thread = self.get_thread()
194
194
195 css_classes = [CSS_CLS_POST]
195 css_classes = [CSS_CLS_POST]
196 if thread.is_archived():
196 if thread.is_archived():
197 css_classes.append(CSS_CLS_ARCHIVE_POST)
197 css_classes.append(CSS_CLS_ARCHIVE_POST)
198 elif not thread.can_bump():
198 elif not thread.can_bump():
199 css_classes.append(CSS_CLS_DEAD_POST)
199 css_classes.append(CSS_CLS_DEAD_POST)
200 if self.is_hidden():
200 if self.is_hidden():
201 css_classes.append(CSS_CLS_HIDDEN_POST)
201 css_classes.append(CSS_CLS_HIDDEN_POST)
202 if thread.is_monochrome():
202 if thread.is_monochrome():
203 css_classes.append(CSS_CLS_MONOCHROME)
203 css_classes.append(CSS_CLS_MONOCHROME)
204
204
205 params = dict()
205 params = dict()
206 for param in POST_VIEW_PARAMS:
206 for param in POST_VIEW_PARAMS:
207 if param in kwargs:
207 if param in kwargs:
208 params[param] = kwargs[param]
208 params[param] = kwargs[param]
209
209
210 params.update({
210 params.update({
211 PARAMETER_POST: self,
211 PARAMETER_POST: self,
212 PARAMETER_IS_OPENING: self.is_opening(),
212 PARAMETER_IS_OPENING: self.is_opening(),
213 PARAMETER_THREAD: thread,
213 PARAMETER_THREAD: thread,
214 PARAMETER_CSS_CLASS: ' '.join(css_classes),
214 PARAMETER_CSS_CLASS: ' '.join(css_classes),
215 })
215 })
216
216
217 return render_to_string('boards/post.html', params)
217 return render_to_string('boards/post.html', params)
218
218
219 def get_search_view(self, *args, **kwargs):
219 def get_search_view(self, *args, **kwargs):
220 return self.get_view(need_op_data=True, *args, **kwargs)
220 return self.get_view(need_op_data=True, *args, **kwargs)
221
221
222 def get_first_image(self) -> PostImage:
222 def get_first_image(self) -> PostImage:
223 return self.images.earliest('id')
223 return self.images.earliest('id')
224
224
225 def set_global_id(self, key_pair=None):
225 def set_global_id(self, key_pair=None):
226 """
226 """
227 Sets global id based on the given key pair. If no key pair is given,
227 Sets global id based on the given key pair. If no key pair is given,
228 default one is used.
228 default one is used.
229 """
229 """
230
230
231 if key_pair:
231 if key_pair:
232 key = key_pair
232 key = key_pair
233 else:
233 else:
234 try:
234 try:
235 key = KeyPair.objects.get(primary=True)
235 key = KeyPair.objects.get(primary=True)
236 except KeyPair.DoesNotExist:
236 except KeyPair.DoesNotExist:
237 # Do not update the global id because there is no key defined
237 # Do not update the global id because there is no key defined
238 return
238 return
239 global_id = GlobalId(key_type=key.key_type,
239 global_id = GlobalId(key_type=key.key_type,
240 key=key.public_key,
240 key=key.public_key,
241 local_id=self.id)
241 local_id=self.id)
242 global_id.save()
242 global_id.save()
243
243
244 self.global_id = global_id
244 self.global_id = global_id
245
245
246 self.save(update_fields=['global_id'])
246 self.save(update_fields=['global_id'])
247
247
248 def get_pub_time_str(self):
248 def get_pub_time_str(self):
249 return str(self.pub_time)
249 return str(self.pub_time)
250
250
251 def get_replied_ids(self):
251 def get_replied_ids(self):
252 """
252 """
253 Gets ID list of the posts that this post replies.
253 Gets ID list of the posts that this post replies.
254 """
254 """
255
255
256 raw_text = self.get_raw_text()
256 raw_text = self.get_raw_text()
257
257
258 local_replied = REGEX_REPLY.findall(raw_text)
258 local_replied = REGEX_REPLY.findall(raw_text)
259 global_replied = []
259 global_replied = []
260 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
260 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
261 key_type = match[0]
261 key_type = match[0]
262 key = match[1]
262 key = match[1]
263 local_id = match[2]
263 local_id = match[2]
264
264
265 try:
265 try:
266 global_id = GlobalId.objects.get(key_type=key_type,
266 global_id = GlobalId.objects.get(key_type=key_type,
267 key=key, local_id=local_id)
267 key=key, local_id=local_id)
268 for post in Post.objects.filter(global_id=global_id).only('id'):
268 for post in Post.objects.filter(global_id=global_id).only('id'):
269 global_replied.append(post.id)
269 global_replied.append(post.id)
270 except GlobalId.DoesNotExist:
270 except GlobalId.DoesNotExist:
271 pass
271 pass
272 return local_replied + global_replied
272 return local_replied + global_replied
273
273
274 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
274 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
275 include_last_update=False) -> str:
275 include_last_update=False) -> str:
276 """
276 """
277 Gets post HTML or JSON data that can be rendered on a page or used by
277 Gets post HTML or JSON data that can be rendered on a page or used by
278 API.
278 API.
279 """
279 """
280
280
281 return get_exporter(format_type).export(self, request,
281 return get_exporter(format_type).export(self, request,
282 include_last_update)
282 include_last_update)
283
283
284 def notify_clients(self, recursive=True):
284 def notify_clients(self, recursive=True):
285 """
285 """
286 Sends post HTML data to the thread web socket.
286 Sends post HTML data to the thread web socket.
287 """
287 """
288
288
289 if not settings.get_bool('External', 'WebsocketsEnabled'):
289 if not settings.get_bool('External', 'WebsocketsEnabled'):
290 return
290 return
291
291
292 thread_ids = list()
292 thread_ids = list()
293 for thread in self.get_threads().all():
293 for thread in self.get_threads().all():
294 thread_ids.append(thread.id)
294 thread_ids.append(thread.id)
295
295
296 thread.notify_clients()
296 thread.notify_clients()
297
297
298 if recursive:
298 if recursive:
299 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
299 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
300 post_id = reply_number.group(1)
300 post_id = reply_number.group(1)
301
301
302 try:
302 try:
303 ref_post = Post.objects.get(id=post_id)
303 ref_post = Post.objects.get(id=post_id)
304
304
305 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
305 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
306 # If post is in this thread, its thread was already notified.
306 # If post is in this thread, its thread was already notified.
307 # Otherwise, notify its thread separately.
307 # Otherwise, notify its thread separately.
308 ref_post.notify_clients(recursive=False)
308 ref_post.notify_clients(recursive=False)
309 except ObjectDoesNotExist:
309 except ObjectDoesNotExist:
310 pass
310 pass
311
311
312 def build_url(self):
312 def build_url(self):
313 self.url = self.get_absolute_url()
313 self.url = self.get_absolute_url()
314 self.save(update_fields=['url'])
314 self.save(update_fields=['url'])
315
315
316 def save(self, force_insert=False, force_update=False, using=None,
316 def save(self, force_insert=False, force_update=False, using=None,
317 update_fields=None):
317 update_fields=None):
318 new_post = self.id is None
318 new_post = self.id is None
319
319
320 self.uid = str(uuid.uuid4())
320 self.uid = str(uuid.uuid4())
321 if update_fields is not None and 'uid' not in update_fields:
321 if update_fields is not None and 'uid' not in update_fields:
322 update_fields += ['uid']
322 update_fields += ['uid']
323
323
324 if not new_post:
324 if not new_post:
325 for thread in self.get_threads().all():
325 for thread in self.get_threads().all():
326 thread.last_edit_time = self.last_edit_time
326 thread.last_edit_time = self.last_edit_time
327
327
328 thread.save(update_fields=['last_edit_time', 'status'])
328 thread.save(update_fields=['last_edit_time', 'status'])
329
329
330 super().save(force_insert, force_update, using, update_fields)
330 super().save(force_insert, force_update, using, update_fields)
331
331
332 if self.url is None:
332 if self.url is None:
333 self.build_url()
333 self.build_url()
334
334
335 def get_text(self) -> str:
335 def get_text(self) -> str:
336 return self._text_rendered
336 return self._text_rendered
337
337
338 def get_raw_text(self) -> str:
338 def get_raw_text(self) -> str:
339 return self.text
339 return self.text
340
340
341 def get_sync_text(self) -> str:
341 def get_sync_text(self) -> str:
342 """
342 """
343 Returns text applicable for sync. It has absolute post reflinks.
343 Returns text applicable for sync. It has absolute post reflinks.
344 """
344 """
345
345
346 replacements = dict()
346 replacements = dict()
347 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
347 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
348 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
348 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
349 replacements[post_id] = absolute_post_id
349 replacements[post_id] = absolute_post_id
350
350
351 text = self.get_raw_text() or ''
351 text = self.get_raw_text() or ''
352 for key in replacements:
352 for key in replacements:
353 text = text.replace('[post]{}[/post]'.format(key),
353 text = text.replace('[post]{}[/post]'.format(key),
354 '[post]{}[/post]'.format(replacements[key]))
354 '[post]{}[/post]'.format(replacements[key]))
355 text = text.replace('\r\n', '\n').replace('\r', '\n')
355 text = text.replace('\r\n', '\n').replace('\r', '\n')
356
356
357 return text
357 return text
358
358
359 def get_absolute_id(self) -> str:
359 def get_absolute_id(self) -> str:
360 """
360 """
361 If the post has many threads, shows its main thread OP id in the post
361 If the post has many threads, shows its main thread OP id in the post
362 ID.
362 ID.
363 """
363 """
364
364
365 if self.get_threads().count() > 1:
365 if self.get_threads().count() > 1:
366 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
366 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
367 else:
367 else:
368 return str(self.id)
368 return str(self.id)
369
369
370
370
371 def connect_threads(self, opening_posts):
371 def connect_threads(self, opening_posts):
372 for opening_post in opening_posts:
372 for opening_post in opening_posts:
373 threads = opening_post.get_threads().all()
373 threads = opening_post.get_threads().all()
374 for thread in threads:
374 for thread in threads:
375 if thread.can_bump():
375 if thread.can_bump():
376 thread.update_bump_status()
376 thread.update_bump_status()
377
377
378 thread.last_edit_time = self.last_edit_time
378 thread.last_edit_time = self.last_edit_time
379 thread.save(update_fields=['last_edit_time', 'status'])
379 thread.save(update_fields=['last_edit_time', 'status'])
380 self.threads.add(opening_post.get_thread())
380 self.threads.add(opening_post.get_thread())
381
381
382 def get_tripcode(self):
382 def get_tripcode(self):
383 if self.tripcode:
383 if self.tripcode:
384 return Tripcode(self.tripcode)
384 return Tripcode(self.tripcode)
385
385
386 def get_link_view(self):
386 def get_link_view(self):
387 """
387 """
388 Gets view of a reflink to the post.
388 Gets view of a reflink to the post.
389 """
389 """
390 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
390 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
391 self.id)
391 self.id)
392 if self.is_opening():
392 if self.is_opening():
393 result = '<b>{}</b>'.format(result)
393 result = '<b>{}</b>'.format(result)
394
394
395 return result
395 return result
396
396
397 def is_hidden(self) -> bool:
397 def is_hidden(self) -> bool:
398 return self.hidden
398 return self.hidden
399
399
400 def set_hidden(self, hidden):
400 def set_hidden(self, hidden):
401 self.hidden = hidden
401 self.hidden = hidden
402
403
404 # SIGNALS (Maybe move to other module?)
405 @receiver(post_save, sender=Post)
406 def connect_replies(instance, **kwargs):
407 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
408 post_id = reply_number.group(1)
409
410 try:
411 referenced_post = Post.objects.get(id=post_id)
412
413 referenced_post.referenced_posts.add(instance)
414 referenced_post.last_edit_time = instance.pub_time
415 referenced_post.build_refmap()
416 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
417 except ObjectDoesNotExist:
418 pass
419
420
421 @receiver(post_save, sender=Post)
422 def connect_notifications(instance, **kwargs):
423 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
424 user_name = reply_number.group(1).lower()
425 Notification.objects.get_or_create(name=user_name, post=instance)
426
427
428 @receiver(pre_save, sender=Post)
429 def preparse_text(instance, **kwargs):
430 instance._text_rendered = get_parser().parse(instance.get_raw_text())
431
432
433 @receiver(pre_delete, sender=Post)
434 def delete_images(instance, **kwargs):
435 for image in instance.images.all():
436 image_refs_count = image.post_images.count()
437 if image_refs_count == 1:
438 image.delete()
439
440
441 @receiver(pre_delete, sender=Post)
442 def delete_attachments(instance, **kwargs):
443 for attachment in instance.attachments.all():
444 attachment_refs_count = attachment.attachment_posts.count()
445 if attachment_refs_count == 1:
446 attachment.delete()
447
448
449 @receiver(post_delete, sender=Post)
450 def update_thread_on_delete(instance, **kwargs):
451 thread = instance.get_thread()
452 thread.last_edit_time = timezone.now()
453 thread.save()
General Comments 0
You need to be logged in to leave comments. Login now