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