##// END OF EJS Templates
Removed obsolete parameter from post view tag
neko259 -
r988:67afa53e default
parent child Browse files
Show More
@@ -1,455 +1,455 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 thread=thread,
95 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
95 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
96 # last!
96 # last!
97 last_edit_time=posting_time)
97 last_edit_time=posting_time)
98 post.threads.add(thread)
98 post.threads.add(thread)
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 # 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
107 # instead of createing the new one
107 # instead of createing the new one
108 image_hash = PostImage.get_hash(image)
108 image_hash = PostImage.get_hash(image)
109 existing = PostImage.objects.filter(hash=image_hash)
109 existing = PostImage.objects.filter(hash=image_hash)
110 if len(existing) > 0:
110 if len(existing) > 0:
111 post_image = existing[0]
111 post_image = existing[0]
112 else:
112 else:
113 post_image = PostImage.objects.create(image=image)
113 post_image = PostImage.objects.create(image=image)
114 logger.info('Created new image #{} for post #{}'.format(
114 logger.info('Created new image #{} for post #{}'.format(
115 post_image.id, post.id))
115 post_image.id, post.id))
116 post.images.add(post_image)
116 post.images.add(post_image)
117
117
118 list(map(thread.add_tag, tags))
118 list(map(thread.add_tag, tags))
119
119
120 if new_thread:
120 if new_thread:
121 boards.models.thread.Thread.objects.process_oldest_threads()
121 boards.models.thread.Thread.objects.process_oldest_threads()
122 else:
122 else:
123 thread.bump()
123 thread.bump()
124 thread.last_edit_time = posting_time
124 thread.last_edit_time = posting_time
125 thread.save()
125 thread.save()
126
126
127 self.connect_replies(post)
127 self.connect_replies(post)
128
128
129 return post
129 return post
130
130
131 def delete_posts_by_ip(self, ip):
131 def delete_posts_by_ip(self, ip):
132 """
132 """
133 Deletes all posts of the author with same IP
133 Deletes all posts of the author with same IP
134 """
134 """
135
135
136 posts = self.filter(poster_ip=ip)
136 posts = self.filter(poster_ip=ip)
137 for post in posts:
137 for post in posts:
138 post.delete()
138 post.delete()
139
139
140 def connect_replies(self, post):
140 def connect_replies(self, post):
141 """
141 """
142 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
143 """
143 """
144
144
145 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()):
146 post_id = reply_number.group(1)
146 post_id = reply_number.group(1)
147 ref_post = self.filter(id=post_id)
147 ref_post = self.filter(id=post_id)
148 if ref_post.count() > 0:
148 if ref_post.count() > 0:
149 referenced_post = ref_post[0]
149 referenced_post = ref_post[0]
150 referenced_post.referenced_posts.add(post)
150 referenced_post.referenced_posts.add(post)
151 referenced_post.last_edit_time = post.pub_time
151 referenced_post.last_edit_time = post.pub_time
152 referenced_post.build_refmap()
152 referenced_post.build_refmap()
153 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
153 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
154
154
155 referenced_threads = referenced_post.get_threads().all()
155 referenced_threads = referenced_post.get_threads().all()
156 for thread in referenced_threads:
156 for thread in referenced_threads:
157 thread.last_edit_time = post.pub_time
157 thread.last_edit_time = post.pub_time
158 thread.save(update_fields=['last_edit_time'])
158 thread.save(update_fields=['last_edit_time'])
159
159
160 post.threads.add(thread)
160 post.threads.add(thread)
161
161
162 @cached_result
162 @cached_result
163 def get_posts_per_day(self):
163 def get_posts_per_day(self):
164 """
164 """
165 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
166 """
166 """
167
167
168 day_end = date.today()
168 day_end = date.today()
169 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
169 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
170
170
171 day_time_start = timezone.make_aware(datetime.combine(
171 day_time_start = timezone.make_aware(datetime.combine(
172 day_start, dtime()), timezone.get_current_timezone())
172 day_start, dtime()), timezone.get_current_timezone())
173 day_time_end = timezone.make_aware(datetime.combine(
173 day_time_end = timezone.make_aware(datetime.combine(
174 day_end, dtime()), timezone.get_current_timezone())
174 day_end, dtime()), timezone.get_current_timezone())
175
175
176 posts_per_period = float(self.filter(
176 posts_per_period = float(self.filter(
177 pub_time__lte=day_time_end,
177 pub_time__lte=day_time_end,
178 pub_time__gte=day_time_start).count())
178 pub_time__gte=day_time_start).count())
179
179
180 ppd = posts_per_period / POSTS_PER_DAY_RANGE
180 ppd = posts_per_period / POSTS_PER_DAY_RANGE
181
181
182 return ppd
182 return ppd
183
183
184 def _preparse_text(self, text: str) -> str:
184 def _preparse_text(self, text: str) -> str:
185 """
185 """
186 Preparses text to change patterns like '>>' to a proper bbcode
186 Preparses text to change patterns like '>>' to a proper bbcode
187 tags.
187 tags.
188 """
188 """
189
189
190 for key, value in PREPARSE_PATTERNS.items():
190 for key, value in PREPARSE_PATTERNS.items():
191 text = re.sub(key, value, text, flags=re.MULTILINE)
191 text = re.sub(key, value, text, flags=re.MULTILINE)
192
192
193 for link in REGEX_URL.findall(text):
193 for link in REGEX_URL.findall(text):
194 text = text.replace(link, unquote(link))
194 text = text.replace(link, unquote(link))
195
195
196 return text
196 return text
197
197
198
198
199 class Post(models.Model, Viewable):
199 class Post(models.Model, Viewable):
200 """A post is a message."""
200 """A post is a message."""
201
201
202 objects = PostManager()
202 objects = PostManager()
203
203
204 class Meta:
204 class Meta:
205 app_label = APP_LABEL_BOARDS
205 app_label = APP_LABEL_BOARDS
206 ordering = ('id',)
206 ordering = ('id',)
207
207
208 title = models.CharField(max_length=TITLE_MAX_LENGTH)
208 title = models.CharField(max_length=TITLE_MAX_LENGTH)
209 pub_time = models.DateTimeField()
209 pub_time = models.DateTimeField()
210 text = TextField(blank=True, null=True)
210 text = TextField(blank=True, null=True)
211 _text_rendered = TextField(blank=True, null=True, editable=False)
211 _text_rendered = TextField(blank=True, null=True, editable=False)
212
212
213 images = models.ManyToManyField(PostImage, null=True, blank=True,
213 images = models.ManyToManyField(PostImage, null=True, blank=True,
214 related_name='ip+', db_index=True)
214 related_name='ip+', db_index=True)
215
215
216 poster_ip = models.GenericIPAddressField()
216 poster_ip = models.GenericIPAddressField()
217 poster_user_agent = models.TextField()
217 poster_user_agent = models.TextField()
218
218
219 last_edit_time = models.DateTimeField()
219 last_edit_time = models.DateTimeField()
220
220
221 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
221 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
222 null=True,
222 null=True,
223 blank=True, related_name='rfp+',
223 blank=True, related_name='rfp+',
224 db_index=True)
224 db_index=True)
225 refmap = models.TextField(null=True, blank=True)
225 refmap = models.TextField(null=True, blank=True)
226 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+')
227 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
228
228
229 def __str__(self):
229 def __str__(self):
230 return 'P#{}/{}'.format(self.id, self.title)
230 return 'P#{}/{}'.format(self.id, self.title)
231
231
232 def get_title(self) -> str:
232 def get_title(self) -> str:
233 """
233 """
234 Gets original post title or part of its text.
234 Gets original post title or part of its text.
235 """
235 """
236
236
237 title = self.title
237 title = self.title
238 if not title:
238 if not title:
239 title = self.get_text()
239 title = self.get_text()
240
240
241 return title
241 return title
242
242
243 def build_refmap(self) -> None:
243 def build_refmap(self) -> None:
244 """
244 """
245 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
246 the server from recalculating the map on every post show.
246 the server from recalculating the map on every post show.
247 """
247 """
248 map_string = ''
248 map_string = ''
249
249
250 first = True
250 first = True
251 for refpost in self.referenced_posts.all():
251 for refpost in self.referenced_posts.all():
252 if not first:
252 if not first:
253 map_string += ', '
253 map_string += ', '
254 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(),
255 refpost.id)
255 refpost.id)
256 first = False
256 first = False
257
257
258 self.refmap = map_string
258 self.refmap = map_string
259
259
260 def get_sorted_referenced_posts(self):
260 def get_sorted_referenced_posts(self):
261 return self.refmap
261 return self.refmap
262
262
263 def is_referenced(self) -> bool:
263 def is_referenced(self) -> bool:
264 if not self.refmap:
264 if not self.refmap:
265 return False
265 return False
266 else:
266 else:
267 return len(self.refmap) > 0
267 return len(self.refmap) > 0
268
268
269 def is_opening(self) -> bool:
269 def is_opening(self) -> bool:
270 """
270 """
271 Checks if this is an opening post or just a reply.
271 Checks if this is an opening post or just a reply.
272 """
272 """
273
273
274 return self.get_thread().get_opening_post_id() == self.id
274 return self.get_thread().get_opening_post_id() == self.id
275
275
276 @transaction.atomic
276 @transaction.atomic
277 def add_tag(self, tag):
277 def add_tag(self, tag):
278 edit_time = timezone.now()
278 edit_time = timezone.now()
279
279
280 thread = self.get_thread()
280 thread = self.get_thread()
281 thread.add_tag(tag)
281 thread.add_tag(tag)
282 self.last_edit_time = edit_time
282 self.last_edit_time = edit_time
283 self.save(update_fields=['last_edit_time'])
283 self.save(update_fields=['last_edit_time'])
284
284
285 thread.last_edit_time = edit_time
285 thread.last_edit_time = edit_time
286 thread.save(update_fields=['last_edit_time'])
286 thread.save(update_fields=['last_edit_time'])
287
287
288 @cached_result
288 @cached_result
289 def get_url(self):
289 def get_url(self):
290 """
290 """
291 Gets full url to the post.
291 Gets full url to the post.
292 """
292 """
293
293
294 thread = self.get_thread()
294 thread = self.get_thread()
295
295
296 opening_id = thread.get_opening_post_id()
296 opening_id = thread.get_opening_post_id()
297
297
298 if self.id != opening_id:
298 if self.id != opening_id:
299 link = reverse('thread', kwargs={
299 link = reverse('thread', kwargs={
300 'post_id': opening_id}) + '#' + str(self.id)
300 'post_id': opening_id}) + '#' + str(self.id)
301 else:
301 else:
302 link = reverse('thread', kwargs={'post_id': self.id})
302 link = reverse('thread', kwargs={'post_id': self.id})
303
303
304 return link
304 return link
305
305
306 def get_thread(self):
306 def get_thread(self):
307 return self.thread
307 return self.thread
308
308
309 def get_threads(self):
309 def get_threads(self):
310 """
310 """
311 Gets post's thread.
311 Gets post's thread.
312 """
312 """
313
313
314 return self.threads
314 return self.threads
315
315
316 def get_referenced_posts(self):
316 def get_referenced_posts(self):
317 return self.referenced_posts.only('id', 'threads')
317 return self.referenced_posts.only('id', 'threads')
318
318
319 def get_view(self, moderator=False, need_open_link=False,
319 def get_view(self, moderator=False, need_open_link=False,
320 truncated=False, *args, **kwargs):
320 truncated=False, *args, **kwargs):
321 """
321 """
322 Renders post's HTML view. Some of the post params can be passed over
322 Renders post's HTML view. Some of the post params can be passed over
323 kwargs for the means of caching (if we view the thread, some params
323 kwargs for the means of caching (if we view the thread, some params
324 are same for every post and don't need to be computed over and over.
324 are same for every post and don't need to be computed over and over.
325 """
325 """
326
326
327 thread = self.get_thread()
327 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
328 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
328 thread = kwargs.get(PARAMETER_THREAD, self.get_thread())
329 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
329 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
330
330
331 if is_opening:
331 if is_opening:
332 opening_post_id = self.id
332 opening_post_id = self.id
333 else:
333 else:
334 opening_post_id = thread.get_opening_post_id()
334 opening_post_id = thread.get_opening_post_id()
335
335
336 return render_to_string('boards/post.html', {
336 return render_to_string('boards/post.html', {
337 PARAMETER_POST: self,
337 PARAMETER_POST: self,
338 PARAMETER_MODERATOR: moderator,
338 PARAMETER_MODERATOR: moderator,
339 PARAMETER_IS_OPENING: is_opening,
339 PARAMETER_IS_OPENING: is_opening,
340 PARAMETER_THREAD: thread,
340 PARAMETER_THREAD: thread,
341 PARAMETER_BUMPABLE: can_bump,
341 PARAMETER_BUMPABLE: can_bump,
342 PARAMETER_NEED_OPEN_LINK: need_open_link,
342 PARAMETER_NEED_OPEN_LINK: need_open_link,
343 PARAMETER_TRUNCATED: truncated,
343 PARAMETER_TRUNCATED: truncated,
344 PARAMETER_OP_ID: opening_post_id,
344 PARAMETER_OP_ID: opening_post_id,
345 })
345 })
346
346
347 def get_search_view(self, *args, **kwargs):
347 def get_search_view(self, *args, **kwargs):
348 return self.get_view(args, kwargs)
348 return self.get_view(args, kwargs)
349
349
350 def get_first_image(self) -> PostImage:
350 def get_first_image(self) -> PostImage:
351 return self.images.earliest('id')
351 return self.images.earliest('id')
352
352
353 def delete(self, using=None):
353 def delete(self, using=None):
354 """
354 """
355 Deletes all post images and the post itself.
355 Deletes all post images and the post itself.
356 """
356 """
357
357
358 for image in self.images.all():
358 for image in self.images.all():
359 image_refs_count = Post.objects.filter(images__in=[image]).count()
359 image_refs_count = Post.objects.filter(images__in=[image]).count()
360 if image_refs_count == 1:
360 if image_refs_count == 1:
361 image.delete()
361 image.delete()
362
362
363 thread = self.get_thread()
363 thread = self.get_thread()
364 thread.last_edit_time = timezone.now()
364 thread.last_edit_time = timezone.now()
365 thread.save()
365 thread.save()
366
366
367 super(Post, self).delete(using)
367 super(Post, self).delete(using)
368
368
369 logging.getLogger('boards.post.delete').info(
369 logging.getLogger('boards.post.delete').info(
370 'Deleted post {}'.format(self))
370 'Deleted post {}'.format(self))
371
371
372 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
372 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
373 include_last_update=False):
373 include_last_update=False):
374 """
374 """
375 Gets post HTML or JSON data that can be rendered on a page or used by
375 Gets post HTML or JSON data that can be rendered on a page or used by
376 API.
376 API.
377 """
377 """
378
378
379 if format_type == DIFF_TYPE_HTML:
379 if format_type == DIFF_TYPE_HTML:
380 params = dict()
380 params = dict()
381 params['post'] = self
381 params['post'] = self
382 if PARAMETER_TRUNCATED in request.GET:
382 if PARAMETER_TRUNCATED in request.GET:
383 params[PARAMETER_TRUNCATED] = True
383 params[PARAMETER_TRUNCATED] = True
384
384
385 return render_to_string('boards/api_post.html', params)
385 return render_to_string('boards/api_post.html', params)
386 elif format_type == DIFF_TYPE_JSON:
386 elif format_type == DIFF_TYPE_JSON:
387 post_json = {
387 post_json = {
388 'id': self.id,
388 'id': self.id,
389 'title': self.title,
389 'title': self.title,
390 'text': self._text_rendered,
390 'text': self._text_rendered,
391 }
391 }
392 if self.images.exists():
392 if self.images.exists():
393 post_image = self.get_first_image()
393 post_image = self.get_first_image()
394 post_json['image'] = post_image.image.url
394 post_json['image'] = post_image.image.url
395 post_json['image_preview'] = post_image.image.url_200x150
395 post_json['image_preview'] = post_image.image.url_200x150
396 if include_last_update:
396 if include_last_update:
397 post_json['bump_time'] = datetime_to_epoch(
397 post_json['bump_time'] = datetime_to_epoch(
398 self.get_thread().bump_time)
398 self.get_thread().bump_time)
399 return post_json
399 return post_json
400
400
401 def send_to_websocket(self, request, recursive=True):
401 def send_to_websocket(self, request, recursive=True):
402 """
402 """
403 Sends post HTML data to the thread web socket.
403 Sends post HTML data to the thread web socket.
404 """
404 """
405
405
406 if not settings.WEBSOCKETS_ENABLED:
406 if not settings.WEBSOCKETS_ENABLED:
407 return
407 return
408
408
409 client = Client()
409 client = Client()
410
410
411 thread = self.get_thread()
411 thread = self.get_thread()
412 thread_id = thread.id
412 thread_id = thread.id
413 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
413 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
414 client.publish(channel_name, {
414 client.publish(channel_name, {
415 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
415 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
416 })
416 })
417 client.send()
417 client.send()
418
418
419 logger = logging.getLogger('boards.post.websocket')
419 logger = logging.getLogger('boards.post.websocket')
420
420
421 logger.info('Sent notification from post #{} to channel {}'.format(
421 logger.info('Sent notification from post #{} to channel {}'.format(
422 self.id, channel_name))
422 self.id, channel_name))
423
423
424 if recursive:
424 if recursive:
425 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
425 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
426 post_id = reply_number.group(1)
426 post_id = reply_number.group(1)
427 ref_post = Post.objects.filter(id=post_id)[0]
427 ref_post = Post.objects.filter(id=post_id)[0]
428
428
429 # If post is in this thread, its thread was already notified.
429 # If post is in this thread, its thread was already notified.
430 # Otherwise, notify its thread separately.
430 # Otherwise, notify its thread separately.
431 if ref_post.get_thread().id != thread_id:
431 if ref_post.get_thread().id != thread_id:
432 ref_post.send_to_websocket(request, recursive=False)
432 ref_post.send_to_websocket(request, recursive=False)
433
433
434 def save(self, force_insert=False, force_update=False, using=None,
434 def save(self, force_insert=False, force_update=False, using=None,
435 update_fields=None):
435 update_fields=None):
436 self._text_rendered = bbcode_extended(self.get_raw_text())
436 self._text_rendered = bbcode_extended(self.get_raw_text())
437
437
438 super().save(force_insert, force_update, using, update_fields)
438 super().save(force_insert, force_update, using, update_fields)
439
439
440 def get_text(self) -> str:
440 def get_text(self) -> str:
441 return self._text_rendered
441 return self._text_rendered
442
442
443 def get_raw_text(self) -> str:
443 def get_raw_text(self) -> str:
444 return self.text
444 return self.text
445
445
446 def get_absolute_id(self) -> str:
446 def get_absolute_id(self) -> str:
447 """
447 """
448 If the post has many threads, shows its main thread OP id in the post
448 If the post has many threads, shows its main thread OP id in the post
449 ID.
449 ID.
450 """
450 """
451
451
452 if self.get_threads().count() > 1:
452 if self.get_threads().count() > 1:
453 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
453 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
454 else:
454 else:
455 return str(self.id)
455 return str(self.id)
@@ -1,93 +1,93 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load cache %}
4 {% load cache %}
5 {% load static from staticfiles %}
5 {% load static from staticfiles %}
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
10 - {{ site_name }}</title>
10 - {{ site_name }}</title>
11 {% endblock %}
11 {% endblock %}
12
12
13 {% block content %}
13 {% block content %}
14 {% get_current_language as LANGUAGE_CODE %}
14 {% get_current_language as LANGUAGE_CODE %}
15
15
16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17
17
18 <div class="image-mode-tab">
18 <div class="image-mode-tab">
19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a>
20 <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a>
21 </div>
21 </div>
22
22
23 {% if bumpable %}
23 {% if bumpable %}
24 <div class="bar-bg">
24 <div class="bar-bg">
25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 </div>
26 </div>
27 <div class="bar-text">
27 <div class="bar-text">
28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 </div>
29 </div>
30 </div>
30 </div>
31 {% endif %}
31 {% endif %}
32
32
33 <div class="thread">
33 <div class="thread">
34 {% with can_bump=thread.can_bump %}
34 {% with can_bump=thread.can_bump %}
35 {% for post in thread.get_replies %}
35 {% for post in thread.get_replies %}
36 {% with is_opening=forloop.first %}
36 {% with is_opening=forloop.first %}
37 {% post_view post moderator=moderator is_opening=is_opening thread=thread bumpable=can_bump opening_post_id=opening_post.id %}
37 {% post_view post moderator=moderator is_opening=is_opening bumpable=can_bump opening_post_id=opening_post.id %}
38 {% endwith %}
38 {% endwith %}
39 {% endfor %}
39 {% endfor %}
40 {% endwith %}
40 {% endwith %}
41 </div>
41 </div>
42
42
43 {% if not thread.archived %}
43 {% if not thread.archived %}
44 <div class="post-form-w" id="form">
44 <div class="post-form-w" id="form">
45 <script src="{% static 'js/panel.js' %}"></script>
45 <script src="{% static 'js/panel.js' %}"></script>
46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
47 <div class="post-form" id="compact-form">
47 <div class="post-form" id="compact-form">
48 <div class="swappable-form-full">
48 <div class="swappable-form-full">
49 <form enctype="multipart/form-data" method="post"
49 <form enctype="multipart/form-data" method="post"
50 >{% csrf_token %}
50 >{% csrf_token %}
51 <div class="compact-form-text"></div>
51 <div class="compact-form-text"></div>
52 {{ form.as_div }}
52 {{ form.as_div }}
53 <div class="form-submit">
53 <div class="form-submit">
54 <input type="submit" value="{% trans "Post" %}"/>
54 <input type="submit" value="{% trans "Post" %}"/>
55 </div>
55 </div>
56 </form>
56 </form>
57 </div>
57 </div>
58 <div><a href="{% url "staticpage" name="help" %}">
58 <div><a href="{% url "staticpage" name="help" %}">
59 {% trans 'Text syntax' %}</a></div>
59 {% trans 'Text syntax' %}</a></div>
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <script src="{% static 'js/jquery.form.min.js' %}"></script>
63 <script src="{% static 'js/jquery.form.min.js' %}"></script>
64 <script src="{% static 'js/thread_update.js' %}"></script>
64 <script src="{% static 'js/thread_update.js' %}"></script>
65 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
65 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
66 {% endif %}
66 {% endif %}
67
67
68 <script src="{% static 'js/form.js' %}"></script>
68 <script src="{% static 'js/form.js' %}"></script>
69 <script src="{% static 'js/thread.js' %}"></script>
69 <script src="{% static 'js/thread.js' %}"></script>
70
70
71 {% endcache %}
71 {% endcache %}
72 {% endblock %}
72 {% endblock %}
73
73
74 {% block metapanel %}
74 {% block metapanel %}
75
75
76 {% get_current_language as LANGUAGE_CODE %}
76 {% get_current_language as LANGUAGE_CODE %}
77
77
78 <span class="metapanel"
78 <span class="metapanel"
79 data-last-update="{{ last_update }}"
79 data-last-update="{{ last_update }}"
80 data-ws-token="{{ ws_token }}"
80 data-ws-token="{{ ws_token }}"
81 data-ws-project="{{ ws_project }}"
81 data-ws-project="{{ ws_project }}"
82 data-ws-host="{{ ws_host }}"
82 data-ws-host="{{ ws_host }}"
83 data-ws-port="{{ ws_port }}">
83 data-ws-port="{{ ws_port }}">
84 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
84 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
85 <span id="autoupdate">[-]</span>
85 <span id="autoupdate">[-]</span>
86 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
86 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
87 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
87 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
88 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
88 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
89 [<a href="rss/">RSS</a>]
89 [<a href="rss/">RSS</a>]
90 {% endcache %}
90 {% endcache %}
91 </span>
91 </span>
92
92
93 {% endblock %}
93 {% endblock %}
@@ -1,77 +1,74 b''
1 from django.shortcuts import get_object_or_404
1 from django.shortcuts import get_object_or_404
2 from django import template
2 from django import template
3
3
4
4
5 register = template.Library()
5 register = template.Library()
6
6
7 actions = [
7 actions = [
8 {
8 {
9 'name': 'google',
9 'name': 'google',
10 'link': 'http://google.com/searchbyimage?image_url=%s',
10 'link': 'http://google.com/searchbyimage?image_url=%s',
11 },
11 },
12 {
12 {
13 'name': 'iqdb',
13 'name': 'iqdb',
14 'link': 'http://iqdb.org/?url=%s',
14 'link': 'http://iqdb.org/?url=%s',
15 },
15 },
16 ]
16 ]
17
17
18
18
19 @register.simple_tag(name='post_url')
19 @register.simple_tag(name='post_url')
20 def post_url(*args, **kwargs):
20 def post_url(*args, **kwargs):
21 post_id = args[0]
21 post_id = args[0]
22
22
23 post = get_object_or_404('Post', id=post_id)
23 post = get_object_or_404('Post', id=post_id)
24
24
25 return post.get_url()
25 return post.get_url()
26
26
27
27
28 @register.simple_tag(name='image_actions')
28 @register.simple_tag(name='image_actions')
29 def image_actions(*args, **kwargs):
29 def image_actions(*args, **kwargs):
30 image_link = args[0]
30 image_link = args[0]
31 if len(args) > 1:
31 if len(args) > 1:
32 image_link = 'http://' + args[1] + image_link # TODO https?
32 image_link = 'http://' + args[1] + image_link # TODO https?
33
33
34 result = ''
34 result = ''
35
35
36 for action in actions:
36 for action in actions:
37 result += '[<a href="' + action['link'] % image_link + '">' + \
37 result += '[<a href="' + action['link'] % image_link + '">' + \
38 action['name'] + '</a>]'
38 action['name'] + '</a>]'
39
39
40 return result
40 return result
41
41
42
42
43 # TODO Use get_view of a post instead of this
43 # TODO Use get_view of a post instead of this
44 @register.inclusion_tag('boards/post.html', name='post_view')
44 @register.inclusion_tag('boards/post.html', name='post_view')
45 def post_view(post, moderator=False, need_open_link=False, truncated=False,
45 def post_view(post, moderator=False, need_open_link=False, truncated=False,
46 **kwargs):
46 **kwargs):
47 """
47 """
48 Get post
48 Get post
49 """
49 """
50
50
51 if 'is_opening' in kwargs:
51 if 'is_opening' in kwargs:
52 is_opening = kwargs['is_opening']
52 is_opening = kwargs['is_opening']
53 else:
53 else:
54 is_opening = post.is_opening()
54 is_opening = post.is_opening()
55
55
56 if 'thread' in kwargs:
56 thread = post.get_thread()
57 thread = kwargs['thread']
58 else:
59 thread = post.get_thread()
60
57
61 if 'can_bump' in kwargs:
58 if 'can_bump' in kwargs:
62 can_bump = kwargs['can_bump']
59 can_bump = kwargs['can_bump']
63 else:
60 else:
64 can_bump = thread.can_bump()
61 can_bump = thread.can_bump()
65
62
66 opening_post_id = thread.get_opening_post_id()
63 opening_post_id = thread.get_opening_post_id()
67
64
68 return {
65 return {
69 'post': post,
66 'post': post,
70 'moderator': moderator,
67 'moderator': moderator,
71 'is_opening': is_opening,
68 'is_opening': is_opening,
72 'thread': thread,
69 'thread': thread,
73 'bumpable': can_bump,
70 'bumpable': can_bump,
74 'need_open_link': need_open_link,
71 'need_open_link': need_open_link,
75 'truncated': truncated,
72 'truncated': truncated,
76 'opening_post_id': opening_post_id,
73 'opening_post_id': opening_post_id,
77 }
74 }
General Comments 0
You need to be logged in to leave comments. Login now