##// END OF EJS Templates
Some more speedups to the post view
neko259 -
r625:2acaa774 default
parent child Browse files
Show More
@@ -1,458 +1,461 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 os
3 import os
4 from random import random
4 from random import random
5 import time
5 import time
6 import re
6 import re
7 import hashlib
7 import hashlib
8
8
9 from django.core.cache import cache
9 from django.core.cache import cache
10 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
11 from django.db import models, transaction
11 from django.db import models, transaction
12 from django.utils import timezone
12 from django.utils import timezone
13 from markupfield.fields import MarkupField
13 from markupfield.fields import MarkupField
14
14
15 from neboard import settings
15 from neboard import settings
16 from boards import thumbs
16 from boards import thumbs
17
17
18
18
19 APP_LABEL_BOARDS = 'boards'
19 APP_LABEL_BOARDS = 'boards'
20
20
21 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_PPD = 'ppd'
22 CACHE_KEY_POST_URL = 'post_url'
22 CACHE_KEY_POST_URL = 'post_url'
23 CACHE_KEY_OPENING_POST = 'opening_post_id'
23 CACHE_KEY_OPENING_POST = 'opening_post_id'
24
24
25 POSTS_PER_DAY_RANGE = range(7)
25 POSTS_PER_DAY_RANGE = range(7)
26
26
27 BAN_REASON_AUTO = 'Auto'
27 BAN_REASON_AUTO = 'Auto'
28
28
29 IMAGE_THUMB_SIZE = (200, 150)
29 IMAGE_THUMB_SIZE = (200, 150)
30
30
31 TITLE_MAX_LENGTH = 200
31 TITLE_MAX_LENGTH = 200
32
32
33 DEFAULT_MARKUP_TYPE = 'markdown'
33 DEFAULT_MARKUP_TYPE = 'markdown'
34
34
35 NO_PARENT = -1
35 NO_PARENT = -1
36 NO_IP = '0.0.0.0'
36 NO_IP = '0.0.0.0'
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38 ALL_PAGES = -1
38 ALL_PAGES = -1
39 IMAGES_DIRECTORY = 'images/'
39 IMAGES_DIRECTORY = 'images/'
40 FILE_EXTENSION_DELIMITER = '.'
40 FILE_EXTENSION_DELIMITER = '.'
41
41
42 SETTING_MODERATE = "moderate"
42 SETTING_MODERATE = "moderate"
43
43
44 REGEX_REPLY = re.compile('>>(\d+)')
44 REGEX_REPLY = re.compile('>>(\d+)')
45
45
46
46
47 class PostManager(models.Manager):
47 class PostManager(models.Manager):
48
48
49 def create_post(self, title, text, image=None, thread=None,
49 def create_post(self, title, text, image=None, thread=None,
50 ip=NO_IP, tags=None, user=None):
50 ip=NO_IP, tags=None, user=None):
51 """
51 """
52 Creates new post
52 Creates new post
53 """
53 """
54
54
55 posting_time = timezone.now()
55 posting_time = timezone.now()
56 if not thread:
56 if not thread:
57 thread = Thread.objects.create(bump_time=posting_time,
57 thread = Thread.objects.create(bump_time=posting_time,
58 last_edit_time=posting_time)
58 last_edit_time=posting_time)
59 new_thread = True
59 new_thread = True
60 else:
60 else:
61 thread.bump()
61 thread.bump()
62 thread.last_edit_time = posting_time
62 thread.last_edit_time = posting_time
63 thread.save()
63 thread.save()
64 new_thread = False
64 new_thread = False
65
65
66 post = self.create(title=title,
66 post = self.create(title=title,
67 text=text,
67 text=text,
68 pub_time=posting_time,
68 pub_time=posting_time,
69 thread_new=thread,
69 thread_new=thread,
70 image=image,
70 image=image,
71 poster_ip=ip,
71 poster_ip=ip,
72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 # last!
73 # last!
74 last_edit_time=posting_time,
74 last_edit_time=posting_time,
75 user=user)
75 user=user)
76
76
77 thread.replies.add(post)
77 thread.replies.add(post)
78 if tags:
78 if tags:
79 linked_tags = []
79 linked_tags = []
80 for tag in tags:
80 for tag in tags:
81 tag_linked_tags = tag.get_linked_tags()
81 tag_linked_tags = tag.get_linked_tags()
82 if len(tag_linked_tags) > 0:
82 if len(tag_linked_tags) > 0:
83 linked_tags.extend(tag_linked_tags)
83 linked_tags.extend(tag_linked_tags)
84
84
85 tags.extend(linked_tags)
85 tags.extend(linked_tags)
86 map(thread.add_tag, tags)
86 map(thread.add_tag, tags)
87
87
88 if new_thread:
88 if new_thread:
89 self._delete_old_threads()
89 self._delete_old_threads()
90 self.connect_replies(post)
90 self.connect_replies(post)
91
91
92 return post
92 return post
93
93
94 def delete_post(self, post):
94 def delete_post(self, post):
95 """
95 """
96 Deletes post and update or delete its thread
96 Deletes post and update or delete its thread
97 """
97 """
98
98
99 thread = post.get_thread()
99 thread = post.get_thread()
100
100
101 if post.is_opening():
101 if post.is_opening():
102 thread.delete_with_posts()
102 thread.delete_with_posts()
103 else:
103 else:
104 thread.last_edit_time = timezone.now()
104 thread.last_edit_time = timezone.now()
105 thread.save()
105 thread.save()
106
106
107 post.delete()
107 post.delete()
108
108
109 def delete_posts_by_ip(self, ip):
109 def delete_posts_by_ip(self, ip):
110 """
110 """
111 Deletes all posts of the author with same IP
111 Deletes all posts of the author with same IP
112 """
112 """
113
113
114 posts = self.filter(poster_ip=ip)
114 posts = self.filter(poster_ip=ip)
115 map(self.delete_post, posts)
115 map(self.delete_post, posts)
116
116
117 # TODO Move this method to thread manager
117 # TODO Move this method to thread manager
118 def _delete_old_threads(self):
118 def _delete_old_threads(self):
119 """
119 """
120 Preserves maximum thread count. If there are too many threads,
120 Preserves maximum thread count. If there are too many threads,
121 archive the old ones.
121 archive the old ones.
122 """
122 """
123
123
124 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
124 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
125 thread_count = threads.count()
125 thread_count = threads.count()
126
126
127 if thread_count > settings.MAX_THREAD_COUNT:
127 if thread_count > settings.MAX_THREAD_COUNT:
128 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
128 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
129 old_threads = threads[thread_count - num_threads_to_delete:]
129 old_threads = threads[thread_count - num_threads_to_delete:]
130
130
131 for thread in old_threads:
131 for thread in old_threads:
132 thread.archived = True
132 thread.archived = True
133 thread.last_edit_time = timezone.now()
133 thread.last_edit_time = timezone.now()
134 thread.save()
134 thread.save()
135
135
136 def connect_replies(self, post):
136 def connect_replies(self, post):
137 """
137 """
138 Connects replies to a post to show them as a reflink map
138 Connects replies to a post to show them as a reflink map
139 """
139 """
140
140
141 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
141 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
142 post_id = reply_number.group(1)
142 post_id = reply_number.group(1)
143 ref_post = self.filter(id=post_id)
143 ref_post = self.filter(id=post_id)
144 if ref_post.count() > 0:
144 if ref_post.count() > 0:
145 referenced_post = ref_post[0]
145 referenced_post = ref_post[0]
146 referenced_post.referenced_posts.add(post)
146 referenced_post.referenced_posts.add(post)
147 referenced_post.last_edit_time = post.pub_time
147 referenced_post.last_edit_time = post.pub_time
148 referenced_post.save()
148 referenced_post.save()
149
149
150 referenced_thread = referenced_post.get_thread()
150 referenced_thread = referenced_post.get_thread()
151 referenced_thread.last_edit_time = post.pub_time
151 referenced_thread.last_edit_time = post.pub_time
152 referenced_thread.save()
152 referenced_thread.save()
153
153
154 def get_posts_per_day(self):
154 def get_posts_per_day(self):
155 """
155 """
156 Gets average count of posts per day for the last 7 days
156 Gets average count of posts per day for the last 7 days
157 """
157 """
158
158
159 today = date.today()
159 today = date.today()
160 ppd = cache.get(CACHE_KEY_PPD + str(today))
160 ppd = cache.get(CACHE_KEY_PPD + str(today))
161 if ppd:
161 if ppd:
162 return ppd
162 return ppd
163
163
164 posts_per_days = []
164 posts_per_days = []
165 for i in POSTS_PER_DAY_RANGE:
165 for i in POSTS_PER_DAY_RANGE:
166 day_end = today - timedelta(i + 1)
166 day_end = today - timedelta(i + 1)
167 day_start = today - timedelta(i + 2)
167 day_start = today - timedelta(i + 2)
168
168
169 day_time_start = timezone.make_aware(datetime.combine(
169 day_time_start = timezone.make_aware(datetime.combine(
170 day_start, dtime()), timezone.get_current_timezone())
170 day_start, dtime()), timezone.get_current_timezone())
171 day_time_end = timezone.make_aware(datetime.combine(
171 day_time_end = timezone.make_aware(datetime.combine(
172 day_end, dtime()), timezone.get_current_timezone())
172 day_end, dtime()), timezone.get_current_timezone())
173
173
174 posts_per_days.append(float(self.filter(
174 posts_per_days.append(float(self.filter(
175 pub_time__lte=day_time_end,
175 pub_time__lte=day_time_end,
176 pub_time__gte=day_time_start).count()))
176 pub_time__gte=day_time_start).count()))
177
177
178 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
178 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
179 len(posts_per_days))
179 len(posts_per_days))
180 cache.set(CACHE_KEY_PPD + str(today), ppd)
180 cache.set(CACHE_KEY_PPD + str(today), ppd)
181 return ppd
181 return ppd
182
182
183
183
184 class Post(models.Model):
184 class Post(models.Model):
185 """A post is a message."""
185 """A post is a message."""
186
186
187 objects = PostManager()
187 objects = PostManager()
188
188
189 class Meta:
189 class Meta:
190 app_label = APP_LABEL_BOARDS
190 app_label = APP_LABEL_BOARDS
191
191
192 # TODO Save original file name to some field
192 # TODO Save original file name to some field
193 def _update_image_filename(self, filename):
193 def _update_image_filename(self, filename):
194 """
194 """
195 Gets unique image filename
195 Gets unique image filename
196 """
196 """
197
197
198 path = IMAGES_DIRECTORY
198 path = IMAGES_DIRECTORY
199 new_name = str(int(time.mktime(time.gmtime())))
199 new_name = str(int(time.mktime(time.gmtime())))
200 new_name += str(int(random() * 1000))
200 new_name += str(int(random() * 1000))
201 new_name += FILE_EXTENSION_DELIMITER
201 new_name += FILE_EXTENSION_DELIMITER
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
203
203
204 return os.path.join(path, new_name)
204 return os.path.join(path, new_name)
205
205
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
207 pub_time = models.DateTimeField()
207 pub_time = models.DateTimeField()
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
209 escape_html=False)
209 escape_html=False)
210
210
211 image_width = models.IntegerField(default=0)
211 image_width = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
213
213
214 image_pre_width = models.IntegerField(default=0)
214 image_pre_width = models.IntegerField(default=0)
215 image_pre_height = models.IntegerField(default=0)
215 image_pre_height = models.IntegerField(default=0)
216
216
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 width_field='image_width',
219 width_field='image_width',
220 height_field='image_height',
220 height_field='image_height',
221 preview_width_field='image_pre_width',
221 preview_width_field='image_pre_width',
222 preview_height_field='image_pre_height')
222 preview_height_field='image_pre_height')
223 image_hash = models.CharField(max_length=36)
223 image_hash = models.CharField(max_length=36)
224
224
225 poster_ip = models.GenericIPAddressField()
225 poster_ip = models.GenericIPAddressField()
226 poster_user_agent = models.TextField()
226 poster_user_agent = models.TextField()
227
227
228 thread = models.ForeignKey('Post', null=True, default=None)
228 thread = models.ForeignKey('Post', null=True, default=None)
229 thread_new = models.ForeignKey('Thread', null=True, default=None)
229 thread_new = models.ForeignKey('Thread', null=True, default=None)
230 last_edit_time = models.DateTimeField()
230 last_edit_time = models.DateTimeField()
231 user = models.ForeignKey('User', null=True, default=None)
231 user = models.ForeignKey('User', null=True, default=None)
232
232
233 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
233 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
234 null=True,
234 null=True,
235 blank=True, related_name='rfp+')
235 blank=True, related_name='rfp+')
236
236
237 def __unicode__(self):
237 def __unicode__(self):
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
239 self.text.raw[:50] + ')'
239 self.text.raw[:50] + ')'
240
240
241 def get_title(self):
241 def get_title(self):
242 """
242 """
243 Gets original post title or part of its text.
243 Gets original post title or part of its text.
244 """
244 """
245
245
246 title = self.title
246 title = self.title
247 if not title:
247 if not title:
248 title = self.text.rendered
248 title = self.text.rendered
249
249
250 return title
250 return title
251
251
252 def get_sorted_referenced_posts(self):
252 def get_sorted_referenced_posts(self):
253 return self.referenced_posts.order_by('id')
253 return self.referenced_posts.order_by('id')
254
254
255 def is_referenced(self):
255 def is_referenced(self):
256 return self.referenced_posts.exists()
256 return self.referenced_posts.exists()
257
257
258 def is_opening(self):
258 def is_opening(self):
259 """
259 """
260 Checks if this is an opening post or just a reply.
260 Checks if this is an opening post or just a reply.
261 """
261 """
262
262
263 return self.get_thread().get_opening_post_id() == self.id
263 return self.get_thread().get_opening_post_id() == self.id
264
264
265 def save(self, *args, **kwargs):
265 def save(self, *args, **kwargs):
266 """
266 """
267 Saves the model and computes the image hash for deduplication purposes.
267 Saves the model and computes the image hash for deduplication purposes.
268 """
268 """
269
269
270 if not self.pk and self.image:
270 if not self.pk and self.image:
271 md5 = hashlib.md5()
271 md5 = hashlib.md5()
272 for chunk in self.image.chunks():
272 for chunk in self.image.chunks():
273 md5.update(chunk)
273 md5.update(chunk)
274 self.image_hash = md5.hexdigest()
274 self.image_hash = md5.hexdigest()
275 super(Post, self).save(*args, **kwargs)
275 super(Post, self).save(*args, **kwargs)
276
276
277 @transaction.atomic
277 @transaction.atomic
278 def add_tag(self, tag):
278 def add_tag(self, tag):
279 edit_time = timezone.now()
279 edit_time = timezone.now()
280
280
281 thread = self.get_thread()
281 thread = self.get_thread()
282 thread.add_tag(tag)
282 thread.add_tag(tag)
283 self.last_edit_time = edit_time
283 self.last_edit_time = edit_time
284 self.save()
284 self.save()
285
285
286 thread.last_edit_time = edit_time
286 thread.last_edit_time = edit_time
287 thread.save()
287 thread.save()
288
288
289 @transaction.atomic
289 @transaction.atomic
290 def remove_tag(self, tag):
290 def remove_tag(self, tag):
291 edit_time = timezone.now()
291 edit_time = timezone.now()
292
292
293 thread = self.get_thread()
293 thread = self.get_thread()
294 thread.remove_tag(tag)
294 thread.remove_tag(tag)
295 self.last_edit_time = edit_time
295 self.last_edit_time = edit_time
296 self.save()
296 self.save()
297
297
298 thread.last_edit_time = edit_time
298 thread.last_edit_time = edit_time
299 thread.save()
299 thread.save()
300
300
301 def get_url(self):
301 def get_url(self, thread=None):
302 """
302 """
303 Gets full url to the post.
303 Gets full url to the post.
304 """
304 """
305
305
306 cache_key = CACHE_KEY_POST_URL + str(self.id)
306 cache_key = CACHE_KEY_POST_URL + str(self.id)
307 link = cache.get(cache_key)
307 link = cache.get(cache_key)
308
308
309 if not link:
309 if not link:
310 opening_id = self.get_thread().get_opening_post_id()
310 if not thread:
311 thread = self.get_thread()
312
313 opening_id = thread.get_opening_post_id()
311
314
312 if self.id != opening_id:
315 if self.id != opening_id:
313 link = reverse('thread', kwargs={
316 link = reverse('thread', kwargs={
314 'post_id': opening_id}) + '#' + str(self.id)
317 'post_id': opening_id}) + '#' + str(self.id)
315 else:
318 else:
316 link = reverse('thread', kwargs={'post_id': self.id})
319 link = reverse('thread', kwargs={'post_id': self.id})
317
320
318 cache.set(cache_key, link)
321 cache.set(cache_key, link)
319
322
320 return link
323 return link
321
324
322 def get_thread(self):
325 def get_thread(self):
323 """
326 """
324 Gets post's thread.
327 Gets post's thread.
325 """
328 """
326
329
327 return self.thread_new
330 return self.thread_new
328
331
329
332
330 class Thread(models.Model):
333 class Thread(models.Model):
331
334
332 class Meta:
335 class Meta:
333 app_label = APP_LABEL_BOARDS
336 app_label = APP_LABEL_BOARDS
334
337
335 tags = models.ManyToManyField('Tag')
338 tags = models.ManyToManyField('Tag')
336 bump_time = models.DateTimeField()
339 bump_time = models.DateTimeField()
337 last_edit_time = models.DateTimeField()
340 last_edit_time = models.DateTimeField()
338 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
341 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
339 blank=True, related_name='tre+')
342 blank=True, related_name='tre+')
340 archived = models.BooleanField(default=False)
343 archived = models.BooleanField(default=False)
341
344
342 def get_tags(self):
345 def get_tags(self):
343 """
346 """
344 Gets a sorted tag list.
347 Gets a sorted tag list.
345 """
348 """
346
349
347 return self.tags.order_by('name')
350 return self.tags.order_by('name')
348
351
349 def bump(self):
352 def bump(self):
350 """
353 """
351 Bumps (moves to up) thread if possible.
354 Bumps (moves to up) thread if possible.
352 """
355 """
353
356
354 if self.can_bump():
357 if self.can_bump():
355 self.bump_time = timezone.now()
358 self.bump_time = timezone.now()
356
359
357 def get_reply_count(self):
360 def get_reply_count(self):
358 return self.replies.count()
361 return self.replies.count()
359
362
360 def get_images_count(self):
363 def get_images_count(self):
361 return self.replies.filter(image_width__gt=0).count()
364 return self.replies.filter(image_width__gt=0).count()
362
365
363 def can_bump(self):
366 def can_bump(self):
364 """
367 """
365 Checks if the thread can be bumped by replying to it.
368 Checks if the thread can be bumped by replying to it.
366 """
369 """
367
370
368 if self.archived:
371 if self.archived:
369 return False
372 return False
370
373
371 post_count = self.get_reply_count()
374 post_count = self.get_reply_count()
372
375
373 return post_count < settings.MAX_POSTS_PER_THREAD
376 return post_count < settings.MAX_POSTS_PER_THREAD
374
377
375 def delete_with_posts(self):
378 def delete_with_posts(self):
376 """
379 """
377 Completely deletes thread and all its posts
380 Completely deletes thread and all its posts
378 """
381 """
379
382
380 if self.replies.exists():
383 if self.replies.exists():
381 self.replies.all().delete()
384 self.replies.all().delete()
382
385
383 self.delete()
386 self.delete()
384
387
385 def get_last_replies(self):
388 def get_last_replies(self):
386 """
389 """
387 Gets several last replies, not including opening post
390 Gets several last replies, not including opening post
388 """
391 """
389
392
390 if settings.LAST_REPLIES_COUNT > 0:
393 if settings.LAST_REPLIES_COUNT > 0:
391 reply_count = self.get_reply_count()
394 reply_count = self.get_reply_count()
392
395
393 if reply_count > 0:
396 if reply_count > 0:
394 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
397 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
395 reply_count - 1)
398 reply_count - 1)
396 last_replies = self.replies.order_by(
399 last_replies = self.replies.order_by(
397 'pub_time')[reply_count - reply_count_to_show:]
400 'pub_time')[reply_count - reply_count_to_show:]
398
401
399 return last_replies
402 return last_replies
400
403
401 def get_skipped_replies_count(self):
404 def get_skipped_replies_count(self):
402 """
405 """
403 Gets number of posts between opening post and last replies.
406 Gets number of posts between opening post and last replies.
404 """
407 """
405
408
406 last_replies = self.get_last_replies()
409 last_replies = self.get_last_replies()
407 return self.get_reply_count() - len(last_replies) - 1
410 return self.get_reply_count() - len(last_replies) - 1
408
411
409 def get_replies(self):
412 def get_replies(self):
410 """
413 """
411 Gets sorted thread posts
414 Gets sorted thread posts
412 """
415 """
413
416
414 return self.replies.all().order_by('pub_time')
417 return self.replies.all().order_by('pub_time')
415
418
416 def add_tag(self, tag):
419 def add_tag(self, tag):
417 """
420 """
418 Connects thread to a tag and tag to a thread
421 Connects thread to a tag and tag to a thread
419 """
422 """
420
423
421 self.tags.add(tag)
424 self.tags.add(tag)
422 tag.threads.add(self)
425 tag.threads.add(self)
423
426
424 def remove_tag(self, tag):
427 def remove_tag(self, tag):
425 self.tags.remove(tag)
428 self.tags.remove(tag)
426 tag.threads.remove(self)
429 tag.threads.remove(self)
427
430
428 def get_opening_post(self):
431 def get_opening_post(self):
429 """
432 """
430 Gets the first post of the thread
433 Gets the first post of the thread
431 """
434 """
432
435
433 opening_post = self.get_replies()[0]
436 opening_post = self.get_replies()[0]
434
437
435 return opening_post
438 return opening_post
436
439
437 def get_opening_post_id(self):
440 def get_opening_post_id(self):
438 """
441 """
439 Gets ID of the first thread post.
442 Gets ID of the first thread post.
440 """
443 """
441
444
442 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
445 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
443 opening_post_id = cache.get(cache_key)
446 opening_post_id = cache.get(cache_key)
444 if not opening_post_id:
447 if not opening_post_id:
445 opening_post_id = self.get_opening_post().id
448 opening_post_id = self.get_opening_post().id
446 cache.set(cache_key, opening_post_id)
449 cache.set(cache_key, opening_post_id)
447
450
448 return opening_post_id
451 return opening_post_id
449
452
450 def __unicode__(self):
453 def __unicode__(self):
451 return str(self.id)
454 return str(self.id)
452
455
453 def get_pub_time(self):
456 def get_pub_time(self):
454 """
457 """
455 Gets opening post's pub time because thread does not have its own one.
458 Gets opening post's pub time because thread does not have its own one.
456 """
459 """
457
460
458 return self.get_opening_post().pub_time
461 return self.get_opening_post().pub_time
@@ -1,100 +1,99 b''
1 {% load i18n %}
1 {% load i18n %}
2 {% load board %}
2 {% load board %}
3 {% load cache %}
3 {% load cache %}
4
4
5 {% get_current_language as LANGUAGE_CODE %}
5 {% get_current_language as LANGUAGE_CODE %}
6
6
7 {% spaceless %}
7 {% spaceless %}
8 {% cache 600 post post.id post.last_edit_time thread.archived bumpable truncated moderator LANGUAGE_CODE need_open_link %}
8 {% cache 600 post post.id post.last_edit_time thread.archived bumpable truncated moderator LANGUAGE_CODE need_open_link %}
9 {% if thread.archived %}
9 {% if thread.archived %}
10 <div class="post archive_post" id="{{ post.id }}">
10 <div class="post archive_post" id="{{ post.id }}">
11 {% elif bumpable %}
11 {% elif bumpable %}
12 <div class="post" id="{{ post.id }}">
12 <div class="post" id="{{ post.id }}">
13 {% else %}
13 {% else %}
14 <div class="post dead_post" id="{{ post.id }}">
14 <div class="post dead_post" id="{{ post.id }}">
15 {% endif %}
15 {% endif %}
16
16
17 {% if post.image %}
17 {% if post.image %}
18 <div class="image">
18 <div class="image">
19 <a
19 <a
20 class="thumb"
20 class="thumb"
21 href="{{ post.image.url }}"><img
21 href="{{ post.image.url }}"><img
22 src="{{ post.image.url_200x150 }}"
22 src="{{ post.image.url_200x150 }}"
23 alt="{{ post.id }}"
23 alt="{{ post.id }}"
24 width="{{ post.image_pre_width }}"
24 width="{{ post.image_pre_width }}"
25 height="{{ post.image_pre_height }}"
25 height="{{ post.image_pre_height }}"
26 data-width="{{ post.image_width }}"
26 data-width="{{ post.image_width }}"
27 data-height="{{ post.image_height }}"/>
27 data-height="{{ post.image_height }}"/>
28 </a>
28 </a>
29 </div>
29 </div>
30 {% endif %}
30 {% endif %}
31 <div class="message">
31 <div class="message">
32 <div class="post-info">
32 <div class="post-info">
33 <span class="title">{{ post.title }}</span>
33 <span class="title">{{ post.title }}</span>
34 <a class="post_id" href="{% post_object_url post %}">
34 <a class="post_id" href="{% post_object_url post thread=thread %}">
35 ({{ post.id }}) </a>
35 ({{ post.id }}) </a>
36 [<span class="pub_time">{{ post.pub_time }}</span>]
36 [<span class="pub_time">{{ post.pub_time }}</span>]
37 {% if thread.archived %}
37 {% if thread.archived %}
38 β€” [{{ thread.bump_time }}]
38 β€” [{{ thread.bump_time }}]
39 {% endif %}
39 {% endif %}
40 {% if not truncated and not thread.archived %}
40 {% if not truncated and not thread.archived %}
41 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
41 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
42 ; return false;">&gt;&gt;</a>]
42 ; return false;">&gt;&gt;</a>]
43 {% endif %}
43 {% endif %}
44 {% if is_opening and need_open_link %}
44 {% if is_opening and need_open_link %}
45 {% if thread.archived %}
45 {% if thread.archived %}
46 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
46 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
47 {% else %}
47 {% else %}
48 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
48 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
49 {% endif %}
49 {% endif %}
50 {% endif %}
50 {% endif %}
51
51
52 {% if moderator %}
52 {% if moderator %}
53 <span class="moderator_info">
53 <span class="moderator_info">
54 [<a href="{% url 'post_admin' post_id=post.id %}"
54 [<a href="{% url 'post_admin' post_id=post.id %}"
55 >{% trans 'Edit' %}</a>]
55 >{% trans 'Edit' %}</a>]
56 [<a href="{% url 'delete' post_id=post.id %}"
56 [<a href="{% url 'delete' post_id=post.id %}"
57 >{% trans 'Delete' %}</a>]
57 >{% trans 'Delete' %}</a>]
58 ({{ post.poster_ip }})
58 ({{ post.poster_ip }})
59 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
59 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
60 >{% trans 'Ban IP' %}</a>]
60 >{% trans 'Ban IP' %}</a>]
61 </span>
61 </span>
62 {% endif %}
62 {% endif %}
63 </div>
63 </div>
64 {% autoescape off %}
64 {% autoescape off %}
65 {% if truncated %}
65 {% if truncated %}
66 {{ post.text.rendered|truncatewords_html:50 }}
66 {{ post.text.rendered|truncatewords_html:50 }}
67 {% else %}
67 {% else %}
68 {{ post.text.rendered }}
68 {{ post.text.rendered }}
69 {% endif %}
69 {% endif %}
70 {% endautoescape %}
70 {% endautoescape %}
71 {% with refposts=post.get_sorted_referenced_posts %}
71 {% with refposts=post.get_sorted_referenced_posts %}
72 {% if refposts %}
72 {% if refposts %}
73 <div class="refmap">
73 <div class="refmap">
74 {% trans "Replies" %}:
74 {% trans "Replies" %}:
75 {% for ref_post in refposts %}
75 {% for ref_post in refposts %}
76 <a href="{% post_object_url ref_post %}">&gt;&gt;{{ ref_post.id }}</a
76 <a href="{% post_object_url ref_post thread=thread %}">&gt;&gt;{{ ref_post.id }}</a>{% if not forloop.last %},{% endif %}
77 >{% if not forloop.last %},{% endif %}
78 {% endfor %}
77 {% endfor %}
79 </div>
78 </div>
80 {% endif %}
79 {% endif %}
81 {% endwith %}
80 {% endwith %}
82 </div>
81 </div>
83 {% endcache %}
82 {% endcache %}
84 {% if is_opening %}
83 {% if is_opening %}
85 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
84 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
86 <div class="metadata">
85 <div class="metadata">
87 {% if is_opening and need_open_link %}
86 {% if is_opening and need_open_link %}
88 {{ thread.get_images_count }} {% trans 'images' %}.
87 {{ thread.get_images_count }} {% trans 'images' %}.
89 {% endif %}
88 {% endif %}
90 <span class="tags">
89 <span class="tags">
91 {% for tag in thread.get_tags %}
90 {% for tag in thread.get_tags %}
92 <a class="tag" href="{% url 'tag' tag.name %}">
91 <a class="tag" href="{% url 'tag' tag.name %}">
93 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
92 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
94 {% endfor %}
93 {% endfor %}
95 </span>
94 </span>
96 </div>
95 </div>
97 {% endcache %}
96 {% endcache %}
98 {% endif %}
97 {% endif %}
99 </div>
98 </div>
100 {% endspaceless %}
99 {% endspaceless %}
@@ -1,90 +1,88 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 {% spaceless %}
14 {% spaceless %}
15 {% get_current_language as LANGUAGE_CODE %}
15 {% get_current_language as LANGUAGE_CODE %}
16
16
17 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
18
18
19 <div class="image-mode-tab">
19 <div class="image-mode-tab">
20 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
21 <a href="{% url 'thread_mode' opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
21 <a href="{% url 'thread_mode' opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
22 </div>
22 </div>
23
23
24 {% if bumpable %}
24 {% if bumpable %}
25 <div class="bar-bg">
25 <div class="bar-bg">
26 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
27 </div>
27 </div>
28 <div class="bar-text">
28 <div class="bar-text">
29 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
30 </div>
30 </div>
31 </div>
31 </div>
32 {% endif %}
32 {% endif %}
33
33
34 <div class="thread">
34 <div class="thread">
35 {% with can_bump=thread.can_bump %}
35 {% with can_bump=thread.can_bump %}
36 {% with opening_post_id=thread.get_opening_post.id %}
37 {% for post in thread.get_replies %}
36 {% for post in thread.get_replies %}
38 {% if forloop.first %}
37 {% if forloop.first %}
39 {% post_view post moderator=moderator is_opening=True thread=thread can_bump=can_bump opening_post_id=opening_post_id %}
38 {% post_view post moderator=moderator is_opening=True thread=thread can_bump=can_bump opening_post_id=opening_post.id %}
40 {% else %}
39 {% else %}
41 {% post_view post moderator=moderator is_opening=False thread=thread can_bump=can_bump opening_post_id=opening_post_id %}
40 {% post_view post moderator=moderator is_opening=False thread=thread can_bump=can_bump opening_post_id=opening_post.id %}
42 {% endif %}
41 {% endif %}
43 {% endfor %}
42 {% endfor %}
44 {% endwith %}
43 {% endwith %}
45 {% endwith %}
46 </div>
44 </div>
47
45
48 {% if not thread.archived %}
46 {% if not thread.archived %}
49
47
50 <div class="post-form-w">
48 <div class="post-form-w">
51 <script src="{% static 'js/panel.js' %}"></script>
49 <script src="{% static 'js/panel.js' %}"></script>
52 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
50 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
53 <div class="post-form">
51 <div class="post-form">
54 <form id="form" enctype="multipart/form-data" method="post"
52 <form id="form" enctype="multipart/form-data" method="post"
55 >{% csrf_token %}
53 >{% csrf_token %}
56 {{ form.as_div }}
54 {{ form.as_div }}
57 <div class="form-submit">
55 <div class="form-submit">
58 <input type="submit" value="{% trans "Post" %}"/>
56 <input type="submit" value="{% trans "Post" %}"/>
59 </div>
57 </div>
60 </form>
58 </form>
61 <div><a href="{% url "staticpage" name="help" %}">
59 <div><a href="{% url "staticpage" name="help" %}">
62 {% trans 'Text syntax' %}</a></div>
60 {% trans 'Text syntax' %}</a></div>
63 </div>
61 </div>
64 </div>
62 </div>
65
63
66 <script src="{% static 'js/jquery.form.min.js' %}"></script>
64 <script src="{% static 'js/jquery.form.min.js' %}"></script>
67 <script src="{% static 'js/thread_update.js' %}"></script>
65 <script src="{% static 'js/thread_update.js' %}"></script>
68 {% endif %}
66 {% endif %}
69
67
70 <script src="{% static 'js/thread.js' %}"></script>
68 <script src="{% static 'js/thread.js' %}"></script>
71
69
72 {% endcache %}
70 {% endcache %}
73
71
74 {% endspaceless %}
72 {% endspaceless %}
75 {% endblock %}
73 {% endblock %}
76
74
77 {% block metapanel %}
75 {% block metapanel %}
78
76
79 {% get_current_language as LANGUAGE_CODE %}
77 {% get_current_language as LANGUAGE_CODE %}
80
78
81 <span class="metapanel" data-last-update="{{ last_update }}">
79 <span class="metapanel" data-last-update="{{ last_update }}">
82 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
80 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
83 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
81 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
84 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
82 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
85 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
83 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
86 [<a href="rss/">RSS</a>]
84 [<a href="rss/">RSS</a>]
87 {% endcache %}
85 {% endcache %}
88 </span>
86 </span>
89
87
90 {% endblock %}
88 {% endblock %}
@@ -1,84 +1,89 b''
1 from django.shortcuts import get_object_or_404
1 from django.shortcuts import get_object_or_404
2 from boards.models import Post
2 from boards.models import Post
3 from boards.views import thread, api
3 from boards.views import thread, api
4 from django import template
4 from django import template
5
5
6 register = template.Library()
6 register = template.Library()
7
7
8 actions = [
8 actions = [
9 {
9 {
10 'name': 'google',
10 'name': 'google',
11 'link': 'http://google.com/searchbyimage?image_url=%s',
11 'link': 'http://google.com/searchbyimage?image_url=%s',
12 },
12 },
13 {
13 {
14 'name': 'iqdb',
14 'name': 'iqdb',
15 'link': 'http://iqdb.org/?url=%s',
15 'link': 'http://iqdb.org/?url=%s',
16 },
16 },
17 ]
17 ]
18
18
19
19
20 @register.simple_tag(name='post_url')
20 @register.simple_tag(name='post_url')
21 def post_url(*args, **kwargs):
21 def post_url(*args, **kwargs):
22 post_id = args[0]
22 post_id = args[0]
23
23
24 post = get_object_or_404(Post, id=post_id)
24 post = get_object_or_404(Post, id=post_id)
25
25
26 return post.get_url()
26 return post.get_url()
27
27
28
28
29 @register.simple_tag(name='post_object_url')
29 @register.simple_tag(name='post_object_url')
30 def post_object_url(*args, **kwargs):
30 def post_object_url(*args, **kwargs):
31 post = args[0]
31 post = args[0]
32
32
33 return post.get_url()
33 if 'thread' in kwargs:
34 post_thread = kwargs['thread']
35 else:
36 post_thread = None
37
38 return post.get_url(thread=post_thread)
34
39
35
40
36 @register.simple_tag(name='image_actions')
41 @register.simple_tag(name='image_actions')
37 def image_actions(*args, **kwargs):
42 def image_actions(*args, **kwargs):
38 image_link = args[0]
43 image_link = args[0]
39 if len(args) > 1:
44 if len(args) > 1:
40 image_link = 'http://' + args[1] + image_link # TODO https?
45 image_link = 'http://' + args[1] + image_link # TODO https?
41
46
42 result = ''
47 result = ''
43
48
44 for action in actions:
49 for action in actions:
45 result += '[<a href="' + action['link'] % image_link + '">' + \
50 result += '[<a href="' + action['link'] % image_link + '">' + \
46 action['name'] + '</a>]'
51 action['name'] + '</a>]'
47
52
48 return result
53 return result
49
54
50
55
51 @register.inclusion_tag('boards/post.html', name='post_view')
56 @register.inclusion_tag('boards/post.html', name='post_view')
52 def post_view(post, moderator=False, need_open_link=False, truncated=False,
57 def post_view(post, moderator=False, need_open_link=False, truncated=False,
53 **kwargs):
58 **kwargs):
54 """
59 """
55 Get post
60 Get post
56 """
61 """
57
62
58 if 'is_opening' in kwargs:
63 if 'is_opening' in kwargs:
59 is_opening = kwargs['is_opening']
64 is_opening = kwargs['is_opening']
60 else:
65 else:
61 is_opening = post.is_opening()
66 is_opening = post.is_opening()
62
67
63 if 'thread' in kwargs:
68 if 'thread' in kwargs:
64 thread = kwargs['thread']
69 thread = kwargs['thread']
65 else:
70 else:
66 thread = post.get_thread()
71 thread = post.get_thread()
67
72
68 if 'can_bump' in kwargs:
73 if 'can_bump' in kwargs:
69 can_bump = kwargs['can_bump']
74 can_bump = kwargs['can_bump']
70 else:
75 else:
71 can_bump = thread.can_bump()
76 can_bump = thread.can_bump()
72
77
73 opening_post_id = thread.get_opening_post_id()
78 opening_post_id = thread.get_opening_post_id()
74
79
75 return {
80 return {
76 'post': post,
81 'post': post,
77 'moderator': moderator,
82 'moderator': moderator,
78 'is_opening': is_opening,
83 'is_opening': is_opening,
79 'thread': thread,
84 'thread': thread,
80 'bumpable': can_bump,
85 'bumpable': can_bump,
81 'need_open_link': need_open_link,
86 'need_open_link': need_open_link,
82 'truncated': truncated,
87 'truncated': truncated,
83 'opening_post_id': opening_post_id,
88 'opening_post_id': opening_post_id,
84 }
89 }
@@ -1,121 +1,121 b''
1 import string
1 import string
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.db import transaction
3 from django.db import transaction
4 from django.http import Http404
4 from django.http import Http404
5 from django.shortcuts import get_object_or_404, render, redirect
5 from django.shortcuts import get_object_or_404, render, redirect
6 from boards import utils
6 from boards import utils
7 from boards.forms import PostForm, PlainErrorList
7 from boards.forms import PostForm, PlainErrorList
8 from boards.models import Post, Ban, Tag
8 from boards.models import Post, Ban, Tag
9 from boards.views.banned import BannedView
9 from boards.views.banned import BannedView
10 from boards.views.base import BaseBoardView, PARAMETER_FORM
10 from boards.views.base import BaseBoardView, PARAMETER_FORM
11 from boards.views.posting_mixin import PostMixin
11 from boards.views.posting_mixin import PostMixin
12 import neboard
12 import neboard
13
13
14 MODE_GALLERY = 'gallery'
14 MODE_GALLERY = 'gallery'
15 MODE_NORMAL = 'normal'
15 MODE_NORMAL = 'normal'
16
16
17
17
18 class ThreadView(BaseBoardView, PostMixin):
18 class ThreadView(BaseBoardView, PostMixin):
19
19
20 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
20 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
21 opening_post = get_object_or_404(Post, id=post_id)
21 opening_post = get_object_or_404(Post, id=post_id)
22
22
23 # If this is not OP, don't show it as it is
23 # If this is not OP, don't show it as it is
24 if not opening_post.is_opening():
24 if not opening_post.is_opening():
25 raise Http404
25 raise Http404
26
26
27 if not form:
27 if not form:
28 form = PostForm(error_class=PlainErrorList)
28 form = PostForm(error_class=PlainErrorList)
29
29
30 thread_to_show = opening_post.get_thread()
30 thread_to_show = opening_post.get_thread()
31
31
32 context = self.get_context_data(request=request)
32 context = self.get_context_data(request=request)
33
33
34 context[PARAMETER_FORM] = form
34 context[PARAMETER_FORM] = form
35 context["last_update"] = utils.datetime_to_epoch(
35 context["last_update"] = utils.datetime_to_epoch(
36 thread_to_show.last_edit_time)
36 thread_to_show.last_edit_time)
37 context["thread"] = thread_to_show
37 context["thread"] = thread_to_show
38
38
39 if MODE_NORMAL == mode:
39 if MODE_NORMAL == mode:
40 context['bumpable'] = thread_to_show.can_bump()
40 context['bumpable'] = thread_to_show.can_bump()
41 if context['bumpable']:
41 if context['bumpable']:
42 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD \
42 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD \
43 - thread_to_show.get_reply_count()
43 - thread_to_show.get_reply_count()
44 context['bumplimit_progress'] = str(
44 context['bumplimit_progress'] = str(
45 float(context['posts_left']) /
45 float(context['posts_left']) /
46 neboard.settings.MAX_POSTS_PER_THREAD * 100)
46 neboard.settings.MAX_POSTS_PER_THREAD * 100)
47
47
48 context['opening_post'] = thread_to_show.get_opening_post()
48 context['opening_post'] = opening_post
49
49
50 document = 'boards/thread.html'
50 document = 'boards/thread.html'
51 elif MODE_GALLERY == mode:
51 elif MODE_GALLERY == mode:
52 posts = thread_to_show.get_replies()
52 posts = thread_to_show.get_replies()
53 context['posts'] = posts.filter(image_width__gt=0)
53 context['posts'] = posts.filter(image_width__gt=0)
54
54
55 document = 'boards/thread_gallery.html'
55 document = 'boards/thread_gallery.html'
56 else:
56 else:
57 raise Http404
57 raise Http404
58
58
59 return render(request, document, context)
59 return render(request, document, context)
60
60
61 def post(self, request, post_id, mode=MODE_NORMAL):
61 def post(self, request, post_id, mode=MODE_NORMAL):
62 opening_post = get_object_or_404(Post, id=post_id)
62 opening_post = get_object_or_404(Post, id=post_id)
63
63
64 # If this is not OP, don't show it as it is
64 # If this is not OP, don't show it as it is
65 if not opening_post.is_opening():
65 if not opening_post.is_opening():
66 raise Http404
66 raise Http404
67
67
68 if not opening_post.get_thread().archived:
68 if not opening_post.get_thread().archived:
69 form = PostForm(request.POST, request.FILES,
69 form = PostForm(request.POST, request.FILES,
70 error_class=PlainErrorList)
70 error_class=PlainErrorList)
71 form.session = request.session
71 form.session = request.session
72
72
73 if form.is_valid():
73 if form.is_valid():
74 return self.new_post(request, form, opening_post)
74 return self.new_post(request, form, opening_post)
75 if form.need_to_ban:
75 if form.need_to_ban:
76 # Ban user because he is suspected to be a bot
76 # Ban user because he is suspected to be a bot
77 self._ban_current_user(request)
77 self._ban_current_user(request)
78
78
79 return self.get(request, post_id, mode, form)
79 return self.get(request, post_id, mode, form)
80
80
81 @transaction.atomic
81 @transaction.atomic
82 def new_post(self, request, form, opening_post=None, html_response=True):
82 def new_post(self, request, form, opening_post=None, html_response=True):
83 """Add a new post (in thread or as a reply)."""
83 """Add a new post (in thread or as a reply)."""
84
84
85 ip = utils.get_client_ip(request)
85 ip = utils.get_client_ip(request)
86 is_banned = Ban.objects.filter(ip=ip).exists()
86 is_banned = Ban.objects.filter(ip=ip).exists()
87
87
88 if is_banned:
88 if is_banned:
89 if html_response:
89 if html_response:
90 return redirect(BannedView().as_view())
90 return redirect(BannedView().as_view())
91 else:
91 else:
92 return
92 return
93
93
94 data = form.cleaned_data
94 data = form.cleaned_data
95
95
96 title = data['title']
96 title = data['title']
97 text = data['text']
97 text = data['text']
98
98
99 text = self._remove_invalid_links(text)
99 text = self._remove_invalid_links(text)
100
100
101 if 'image' in data.keys():
101 if 'image' in data.keys():
102 image = data['image']
102 image = data['image']
103 else:
103 else:
104 image = None
104 image = None
105
105
106 tags = []
106 tags = []
107
107
108 post_thread = opening_post.get_thread()
108 post_thread = opening_post.get_thread()
109
109
110 post = Post.objects.create_post(title=title, text=text, ip=ip,
110 post = Post.objects.create_post(title=title, text=text, ip=ip,
111 thread=post_thread, image=image,
111 thread=post_thread, image=image,
112 tags=tags,
112 tags=tags,
113 user=self._get_user(request))
113 user=self._get_user(request))
114
114
115 thread_to_show = (opening_post.id if opening_post else post.id)
115 thread_to_show = (opening_post.id if opening_post else post.id)
116
116
117 if html_response:
117 if html_response:
118 if opening_post:
118 if opening_post:
119 return redirect(reverse(
119 return redirect(reverse(
120 'thread',
120 'thread',
121 kwargs={'post_id': thread_to_show}) + '#' + str(post.id))
121 kwargs={'post_id': thread_to_show}) + '#' + str(post.id))
General Comments 0
You need to be logged in to leave comments. Login now