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