##// END OF EJS Templates
Fixed post reflinks that were parsed as quotes in the line start
neko259 -
r925:1c0513f9 default
parent child Browse files
Show More
@@ -1,441 +1,441 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
5
6 from adjacent import Client
6 from adjacent import Client
7 from django.core.cache import cache
7 from django.core.cache import cache
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 bbcode_extended
15 from boards.mdx_neboard import bbcode_extended
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.models.thread import Thread
18 from boards.models.thread import Thread
19 from boards.utils import datetime_to_epoch
19 from boards.utils import datetime_to_epoch
20
20
21
21
22 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
22 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
23 WS_NOTIFICATION_TYPE = 'notification_type'
23 WS_NOTIFICATION_TYPE = 'notification_type'
24
24
25 WS_CHANNEL_THREAD = "thread:"
25 WS_CHANNEL_THREAD = "thread:"
26
26
27 APP_LABEL_BOARDS = 'boards'
27 APP_LABEL_BOARDS = 'boards'
28
28
29 CACHE_KEY_PPD = 'ppd'
29 CACHE_KEY_PPD = 'ppd'
30 CACHE_KEY_POST_URL = 'post_url'
30 CACHE_KEY_POST_URL = 'post_url'
31
31
32 POSTS_PER_DAY_RANGE = 7
32 POSTS_PER_DAY_RANGE = 7
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 # TODO This should be removed
40 # TODO This should be removed
41 NO_IP = '0.0.0.0'
41 NO_IP = '0.0.0.0'
42
42
43 # TODO Real user agent should be saved instead of this
43 # TODO Real user agent should be saved instead of this
44 UNKNOWN_UA = ''
44 UNKNOWN_UA = ''
45
45
46 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
46 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
47
47
48 PARAMETER_TRUNCATED = 'truncated'
48 PARAMETER_TRUNCATED = 'truncated'
49 PARAMETER_TAG = 'tag'
49 PARAMETER_TAG = 'tag'
50 PARAMETER_OFFSET = 'offset'
50 PARAMETER_OFFSET = 'offset'
51 PARAMETER_DIFF_TYPE = 'type'
51 PARAMETER_DIFF_TYPE = 'type'
52 PARAMETER_BUMPABLE = 'bumpable'
52 PARAMETER_BUMPABLE = 'bumpable'
53 PARAMETER_THREAD = 'thread'
53 PARAMETER_THREAD = 'thread'
54 PARAMETER_IS_OPENING = 'is_opening'
54 PARAMETER_IS_OPENING = 'is_opening'
55 PARAMETER_MODERATOR = 'moderator'
55 PARAMETER_MODERATOR = 'moderator'
56 PARAMETER_POST = 'post'
56 PARAMETER_POST = 'post'
57 PARAMETER_OP_ID = 'opening_post_id'
57 PARAMETER_OP_ID = 'opening_post_id'
58 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
58 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
59
59
60 DIFF_TYPE_HTML = 'html'
60 DIFF_TYPE_HTML = 'html'
61 DIFF_TYPE_JSON = 'json'
61 DIFF_TYPE_JSON = 'json'
62
62
63 PREPARSE_PATTERNS = {
63 PREPARSE_PATTERNS = {
64 r'>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
64 r'>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
65 r'^>(.+)': r'[quote]\1[/quote]', # Quote ">text"
65 r'^(?<!>)>(.+)': r'[quote]\1[/quote]', # Quote ">text"
66 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
66 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
67 }
67 }
68
68
69
69
70 class PostManager(models.Manager):
70 class PostManager(models.Manager):
71 @transaction.atomic
71 @transaction.atomic
72 def create_post(self, title: str, text: str, image=None, thread=None,
72 def create_post(self, title: str, text: str, image=None, thread=None,
73 ip=NO_IP, tags: list=None):
73 ip=NO_IP, tags: list=None):
74 """
74 """
75 Creates new post
75 Creates new post
76 """
76 """
77
77
78 if not tags:
78 if not tags:
79 tags = []
79 tags = []
80
80
81 posting_time = timezone.now()
81 posting_time = timezone.now()
82 if not thread:
82 if not thread:
83 thread = Thread.objects.create(bump_time=posting_time,
83 thread = Thread.objects.create(bump_time=posting_time,
84 last_edit_time=posting_time)
84 last_edit_time=posting_time)
85 new_thread = True
85 new_thread = True
86 else:
86 else:
87 new_thread = False
87 new_thread = False
88
88
89 pre_text = self._preparse_text(text)
89 pre_text = self._preparse_text(text)
90
90
91 post = self.create(title=title,
91 post = self.create(title=title,
92 text=pre_text,
92 text=pre_text,
93 pub_time=posting_time,
93 pub_time=posting_time,
94 thread_new=thread,
94 thread_new=thread,
95 poster_ip=ip,
95 poster_ip=ip,
96 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
96 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
97 # last!
97 # last!
98 last_edit_time=posting_time)
98 last_edit_time=posting_time)
99
99
100 logger = logging.getLogger('boards.post.create')
100 logger = logging.getLogger('boards.post.create')
101
101
102 logger.info('Created post {} by {}'.format(
102 logger.info('Created post {} by {}'.format(
103 post, post.poster_ip))
103 post, post.poster_ip))
104
104
105 if image:
105 if image:
106 post_image = PostImage.objects.create(image=image)
106 post_image = PostImage.objects.create(image=image)
107 post.images.add(post_image)
107 post.images.add(post_image)
108 logger.info('Created image #{} for post #{}'.format(
108 logger.info('Created image #{} for post #{}'.format(
109 post_image.id, post.id))
109 post_image.id, post.id))
110
110
111 thread.replies.add(post)
111 thread.replies.add(post)
112 list(map(thread.add_tag, tags))
112 list(map(thread.add_tag, tags))
113
113
114 if new_thread:
114 if new_thread:
115 Thread.objects.process_oldest_threads()
115 Thread.objects.process_oldest_threads()
116 else:
116 else:
117 thread.bump()
117 thread.bump()
118 thread.last_edit_time = posting_time
118 thread.last_edit_time = posting_time
119 thread.save()
119 thread.save()
120
120
121 self.connect_replies(post)
121 self.connect_replies(post)
122
122
123 return post
123 return post
124
124
125 def delete_posts_by_ip(self, ip):
125 def delete_posts_by_ip(self, ip):
126 """
126 """
127 Deletes all posts of the author with same IP
127 Deletes all posts of the author with same IP
128 """
128 """
129
129
130 posts = self.filter(poster_ip=ip)
130 posts = self.filter(poster_ip=ip)
131 for post in posts:
131 for post in posts:
132 post.delete()
132 post.delete()
133
133
134 def connect_replies(self, post):
134 def connect_replies(self, post):
135 """
135 """
136 Connects replies to a post to show them as a reflink map
136 Connects replies to a post to show them as a reflink map
137 """
137 """
138
138
139 for reply_number in re.finditer(REGEX_REPLY, post.get_raw_text()):
139 for reply_number in re.finditer(REGEX_REPLY, post.get_raw_text()):
140 post_id = reply_number.group(1)
140 post_id = reply_number.group(1)
141 ref_post = self.filter(id=post_id)
141 ref_post = self.filter(id=post_id)
142 if ref_post.count() > 0:
142 if ref_post.count() > 0:
143 referenced_post = ref_post[0]
143 referenced_post = ref_post[0]
144 referenced_post.referenced_posts.add(post)
144 referenced_post.referenced_posts.add(post)
145 referenced_post.last_edit_time = post.pub_time
145 referenced_post.last_edit_time = post.pub_time
146 referenced_post.build_refmap()
146 referenced_post.build_refmap()
147 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
147 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
148
148
149 referenced_thread = referenced_post.get_thread()
149 referenced_thread = referenced_post.get_thread()
150 referenced_thread.last_edit_time = post.pub_time
150 referenced_thread.last_edit_time = post.pub_time
151 referenced_thread.save(update_fields=['last_edit_time'])
151 referenced_thread.save(update_fields=['last_edit_time'])
152
152
153 def get_posts_per_day(self):
153 def get_posts_per_day(self):
154 """
154 """
155 Gets average count of posts per day for the last 7 days
155 Gets average count of posts per day for the last 7 days
156 """
156 """
157
157
158 day_end = date.today()
158 day_end = date.today()
159 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
159 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
160
160
161 cache_key = CACHE_KEY_PPD + str(day_end)
161 cache_key = CACHE_KEY_PPD + str(day_end)
162 ppd = cache.get(cache_key)
162 ppd = cache.get(cache_key)
163 if ppd:
163 if ppd:
164 return ppd
164 return ppd
165
165
166 day_time_start = timezone.make_aware(datetime.combine(
166 day_time_start = timezone.make_aware(datetime.combine(
167 day_start, dtime()), timezone.get_current_timezone())
167 day_start, dtime()), timezone.get_current_timezone())
168 day_time_end = timezone.make_aware(datetime.combine(
168 day_time_end = timezone.make_aware(datetime.combine(
169 day_end, dtime()), timezone.get_current_timezone())
169 day_end, dtime()), timezone.get_current_timezone())
170
170
171 posts_per_period = float(self.filter(
171 posts_per_period = float(self.filter(
172 pub_time__lte=day_time_end,
172 pub_time__lte=day_time_end,
173 pub_time__gte=day_time_start).count())
173 pub_time__gte=day_time_start).count())
174
174
175 ppd = posts_per_period / POSTS_PER_DAY_RANGE
175 ppd = posts_per_period / POSTS_PER_DAY_RANGE
176
176
177 cache.set(cache_key, ppd)
177 cache.set(cache_key, ppd)
178 return ppd
178 return ppd
179
179
180 def _preparse_text(self, text):
180 def _preparse_text(self, text):
181 """
181 """
182 Preparses text to change patterns like '>>' to a proper bbcode
182 Preparses text to change patterns like '>>' to a proper bbcode
183 tags.
183 tags.
184 """
184 """
185
185
186 for key, value in PREPARSE_PATTERNS.items():
186 for key, value in PREPARSE_PATTERNS.items():
187 text = re.sub(key, value, text, flags=re.MULTILINE)
187 text = re.sub(key, value, text, flags=re.MULTILINE)
188
188
189 return text
189 return text
190
190
191
191
192 class Post(models.Model, Viewable):
192 class Post(models.Model, Viewable):
193 """A post is a message."""
193 """A post is a message."""
194
194
195 objects = PostManager()
195 objects = PostManager()
196
196
197 class Meta:
197 class Meta:
198 app_label = APP_LABEL_BOARDS
198 app_label = APP_LABEL_BOARDS
199 ordering = ('id',)
199 ordering = ('id',)
200
200
201 title = models.CharField(max_length=TITLE_MAX_LENGTH)
201 title = models.CharField(max_length=TITLE_MAX_LENGTH)
202 pub_time = models.DateTimeField()
202 pub_time = models.DateTimeField()
203 text = TextField(blank=True, null=True)
203 text = TextField(blank=True, null=True)
204 _text_rendered = TextField(blank=True, null=True, editable=False)
204 _text_rendered = TextField(blank=True, null=True, editable=False)
205
205
206 images = models.ManyToManyField(PostImage, null=True, blank=True,
206 images = models.ManyToManyField(PostImage, null=True, blank=True,
207 related_name='ip+', db_index=True)
207 related_name='ip+', db_index=True)
208
208
209 poster_ip = models.GenericIPAddressField()
209 poster_ip = models.GenericIPAddressField()
210 poster_user_agent = models.TextField()
210 poster_user_agent = models.TextField()
211
211
212 thread_new = models.ForeignKey('Thread', null=True, default=None,
212 thread_new = models.ForeignKey('Thread', null=True, default=None,
213 db_index=True)
213 db_index=True)
214 last_edit_time = models.DateTimeField()
214 last_edit_time = models.DateTimeField()
215
215
216 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
216 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
217 null=True,
217 null=True,
218 blank=True, related_name='rfp+',
218 blank=True, related_name='rfp+',
219 db_index=True)
219 db_index=True)
220 refmap = models.TextField(null=True, blank=True)
220 refmap = models.TextField(null=True, blank=True)
221
221
222 def __str__(self):
222 def __str__(self):
223 return 'P#{}/{}'.format(self.id, self.title)
223 return 'P#{}/{}'.format(self.id, self.title)
224
224
225 def get_title(self) -> str:
225 def get_title(self) -> str:
226 """
226 """
227 Gets original post title or part of its text.
227 Gets original post title or part of its text.
228 """
228 """
229
229
230 title = self.title
230 title = self.title
231 if not title:
231 if not title:
232 title = self.get_text()
232 title = self.get_text()
233
233
234 return title
234 return title
235
235
236 def build_refmap(self) -> None:
236 def build_refmap(self) -> None:
237 """
237 """
238 Builds a replies map string from replies list. This is a cache to stop
238 Builds a replies map string from replies list. This is a cache to stop
239 the server from recalculating the map on every post show.
239 the server from recalculating the map on every post show.
240 """
240 """
241 map_string = ''
241 map_string = ''
242
242
243 first = True
243 first = True
244 for refpost in self.referenced_posts.all():
244 for refpost in self.referenced_posts.all():
245 if not first:
245 if not first:
246 map_string += ', '
246 map_string += ', '
247 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
247 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
248 refpost.id)
248 refpost.id)
249 first = False
249 first = False
250
250
251 self.refmap = map_string
251 self.refmap = map_string
252
252
253 def get_sorted_referenced_posts(self):
253 def get_sorted_referenced_posts(self):
254 return self.refmap
254 return self.refmap
255
255
256 def is_referenced(self) -> bool:
256 def is_referenced(self) -> bool:
257 if not self.refmap:
257 if not self.refmap:
258 return False
258 return False
259 else:
259 else:
260 return len(self.refmap) > 0
260 return len(self.refmap) > 0
261
261
262 def is_opening(self) -> bool:
262 def is_opening(self) -> bool:
263 """
263 """
264 Checks if this is an opening post or just a reply.
264 Checks if this is an opening post or just a reply.
265 """
265 """
266
266
267 return self.get_thread().get_opening_post_id() == self.id
267 return self.get_thread().get_opening_post_id() == self.id
268
268
269 @transaction.atomic
269 @transaction.atomic
270 def add_tag(self, tag):
270 def add_tag(self, tag):
271 edit_time = timezone.now()
271 edit_time = timezone.now()
272
272
273 thread = self.get_thread()
273 thread = self.get_thread()
274 thread.add_tag(tag)
274 thread.add_tag(tag)
275 self.last_edit_time = edit_time
275 self.last_edit_time = edit_time
276 self.save(update_fields=['last_edit_time'])
276 self.save(update_fields=['last_edit_time'])
277
277
278 thread.last_edit_time = edit_time
278 thread.last_edit_time = edit_time
279 thread.save(update_fields=['last_edit_time'])
279 thread.save(update_fields=['last_edit_time'])
280
280
281 def get_url(self, thread=None):
281 def get_url(self, thread=None):
282 """
282 """
283 Gets full url to the post.
283 Gets full url to the post.
284 """
284 """
285
285
286 cache_key = CACHE_KEY_POST_URL + str(self.id)
286 cache_key = CACHE_KEY_POST_URL + str(self.id)
287 link = cache.get(cache_key)
287 link = cache.get(cache_key)
288
288
289 if not link:
289 if not link:
290 if not thread:
290 if not thread:
291 thread = self.get_thread()
291 thread = self.get_thread()
292
292
293 opening_id = thread.get_opening_post_id()
293 opening_id = thread.get_opening_post_id()
294
294
295 if self.id != opening_id:
295 if self.id != opening_id:
296 link = reverse('thread', kwargs={
296 link = reverse('thread', kwargs={
297 'post_id': opening_id}) + '#' + str(self.id)
297 'post_id': opening_id}) + '#' + str(self.id)
298 else:
298 else:
299 link = reverse('thread', kwargs={'post_id': self.id})
299 link = reverse('thread', kwargs={'post_id': self.id})
300
300
301 cache.set(cache_key, link)
301 cache.set(cache_key, link)
302
302
303 return link
303 return link
304
304
305 def get_thread(self) -> Thread:
305 def get_thread(self) -> Thread:
306 """
306 """
307 Gets post's thread.
307 Gets post's thread.
308 """
308 """
309
309
310 return self.thread_new
310 return self.thread_new
311
311
312 def get_referenced_posts(self):
312 def get_referenced_posts(self):
313 return self.referenced_posts.only('id', 'thread_new')
313 return self.referenced_posts.only('id', 'thread_new')
314
314
315 def get_view(self, moderator=False, need_open_link=False,
315 def get_view(self, moderator=False, need_open_link=False,
316 truncated=False, *args, **kwargs):
316 truncated=False, *args, **kwargs):
317 """
317 """
318 Renders post's HTML view. Some of the post params can be passed over
318 Renders post's HTML view. Some of the post params can be passed over
319 kwargs for the means of caching (if we view the thread, some params
319 kwargs for the means of caching (if we view the thread, some params
320 are same for every post and don't need to be computed over and over.
320 are same for every post and don't need to be computed over and over.
321 """
321 """
322
322
323 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
323 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
324 thread = kwargs.get(PARAMETER_THREAD, self.get_thread())
324 thread = kwargs.get(PARAMETER_THREAD, self.get_thread())
325 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
325 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
326
326
327 if is_opening:
327 if is_opening:
328 opening_post_id = self.id
328 opening_post_id = self.id
329 else:
329 else:
330 opening_post_id = thread.get_opening_post_id()
330 opening_post_id = thread.get_opening_post_id()
331
331
332 return render_to_string('boards/post.html', {
332 return render_to_string('boards/post.html', {
333 PARAMETER_POST: self,
333 PARAMETER_POST: self,
334 PARAMETER_MODERATOR: moderator,
334 PARAMETER_MODERATOR: moderator,
335 PARAMETER_IS_OPENING: is_opening,
335 PARAMETER_IS_OPENING: is_opening,
336 PARAMETER_THREAD: thread,
336 PARAMETER_THREAD: thread,
337 PARAMETER_BUMPABLE: can_bump,
337 PARAMETER_BUMPABLE: can_bump,
338 PARAMETER_NEED_OPEN_LINK: need_open_link,
338 PARAMETER_NEED_OPEN_LINK: need_open_link,
339 PARAMETER_TRUNCATED: truncated,
339 PARAMETER_TRUNCATED: truncated,
340 PARAMETER_OP_ID: opening_post_id,
340 PARAMETER_OP_ID: opening_post_id,
341 })
341 })
342
342
343 def get_search_view(self, *args, **kwargs):
343 def get_search_view(self, *args, **kwargs):
344 return self.get_view(args, kwargs)
344 return self.get_view(args, kwargs)
345
345
346 def get_first_image(self) -> PostImage:
346 def get_first_image(self) -> PostImage:
347 return self.images.earliest('id')
347 return self.images.earliest('id')
348
348
349 def delete(self, using=None):
349 def delete(self, using=None):
350 """
350 """
351 Deletes all post images and the post itself. If the post is opening,
351 Deletes all post images and the post itself. If the post is opening,
352 thread with all posts is deleted.
352 thread with all posts is deleted.
353 """
353 """
354
354
355 self.images.all().delete()
355 self.images.all().delete()
356
356
357 if self.is_opening():
357 if self.is_opening():
358 self.get_thread().delete()
358 self.get_thread().delete()
359 else:
359 else:
360 thread = self.get_thread()
360 thread = self.get_thread()
361 thread.last_edit_time = timezone.now()
361 thread.last_edit_time = timezone.now()
362 thread.save()
362 thread.save()
363
363
364 super(Post, self).delete(using)
364 super(Post, self).delete(using)
365
365
366 logging.getLogger('boards.post.delete').info(
366 logging.getLogger('boards.post.delete').info(
367 'Deleted post {}'.format(self))
367 'Deleted post {}'.format(self))
368
368
369 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
369 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
370 include_last_update=False):
370 include_last_update=False):
371 """
371 """
372 Gets post HTML or JSON data that can be rendered on a page or used by
372 Gets post HTML or JSON data that can be rendered on a page or used by
373 API.
373 API.
374 """
374 """
375
375
376 if format_type == DIFF_TYPE_HTML:
376 if format_type == DIFF_TYPE_HTML:
377 params = dict()
377 params = dict()
378 params['post'] = self
378 params['post'] = self
379 if PARAMETER_TRUNCATED in request.GET:
379 if PARAMETER_TRUNCATED in request.GET:
380 params[PARAMETER_TRUNCATED] = True
380 params[PARAMETER_TRUNCATED] = True
381
381
382 return render_to_string('boards/api_post.html', params)
382 return render_to_string('boards/api_post.html', params)
383 elif format_type == DIFF_TYPE_JSON:
383 elif format_type == DIFF_TYPE_JSON:
384 post_json = {
384 post_json = {
385 'id': self.id,
385 'id': self.id,
386 'title': self.title,
386 'title': self.title,
387 'text': self._text_rendered,
387 'text': self._text_rendered,
388 }
388 }
389 if self.images.exists():
389 if self.images.exists():
390 post_image = self.get_first_image()
390 post_image = self.get_first_image()
391 post_json['image'] = post_image.image.url
391 post_json['image'] = post_image.image.url
392 post_json['image_preview'] = post_image.image.url_200x150
392 post_json['image_preview'] = post_image.image.url_200x150
393 if include_last_update:
393 if include_last_update:
394 post_json['bump_time'] = datetime_to_epoch(
394 post_json['bump_time'] = datetime_to_epoch(
395 self.thread_new.bump_time)
395 self.thread_new.bump_time)
396 return post_json
396 return post_json
397
397
398 def send_to_websocket(self, request, recursive=True):
398 def send_to_websocket(self, request, recursive=True):
399 """
399 """
400 Sends post HTML data to the thread web socket.
400 Sends post HTML data to the thread web socket.
401 """
401 """
402
402
403 if not settings.WEBSOCKETS_ENABLED:
403 if not settings.WEBSOCKETS_ENABLED:
404 return
404 return
405
405
406 client = Client()
406 client = Client()
407
407
408 thread = self.get_thread()
408 thread = self.get_thread()
409 thread_id = thread.id
409 thread_id = thread.id
410 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
410 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
411 client.publish(channel_name, {
411 client.publish(channel_name, {
412 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
412 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
413 })
413 })
414 client.send()
414 client.send()
415
415
416 logger = logging.getLogger('boards.post.websocket')
416 logger = logging.getLogger('boards.post.websocket')
417
417
418 logger.info('Sent notification from post #{} to channel {}'.format(
418 logger.info('Sent notification from post #{} to channel {}'.format(
419 self.id, channel_name))
419 self.id, channel_name))
420
420
421 if recursive:
421 if recursive:
422 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
422 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
423 post_id = reply_number.group(1)
423 post_id = reply_number.group(1)
424 ref_post = Post.objects.filter(id=post_id)[0]
424 ref_post = Post.objects.filter(id=post_id)[0]
425
425
426 # If post is in this thread, its thread was already notified.
426 # If post is in this thread, its thread was already notified.
427 # Otherwise, notify its thread separately.
427 # Otherwise, notify its thread separately.
428 if ref_post.thread_new_id != thread_id:
428 if ref_post.thread_new_id != thread_id:
429 ref_post.send_to_websocket(request, recursive=False)
429 ref_post.send_to_websocket(request, recursive=False)
430
430
431 def save(self, force_insert=False, force_update=False, using=None,
431 def save(self, force_insert=False, force_update=False, using=None,
432 update_fields=None):
432 update_fields=None):
433 self._text_rendered = bbcode_extended(self.get_raw_text())
433 self._text_rendered = bbcode_extended(self.get_raw_text())
434
434
435 super().save(force_insert, force_update, using, update_fields)
435 super().save(force_insert, force_update, using, update_fields)
436
436
437 def get_text(self) -> str:
437 def get_text(self) -> str:
438 return self._text_rendered
438 return self._text_rendered
439
439
440 def get_raw_text(self) -> str:
440 def get_raw_text(self) -> str:
441 return self.text
441 return self.text
General Comments 0
You need to be logged in to leave comments. Login now