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