##// END OF EJS Templates
Updating last update time of the thread when archiving it
neko259 -
r492:62014008 1.6-dev
parent child Browse files
Show More
@@ -1,392 +1,393 b''
1 from datetime import datetime, timedelta
1 from datetime import datetime, timedelta
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 math
6 import math
7 import re
7 import re
8 from django.core.cache import cache
8 from django.core.cache import cache
9
9
10 from django.db import models
10 from django.db import models
11 from django.http import Http404
11 from django.http import Http404
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 MAX_TITLE_LENGTH = 50
18 MAX_TITLE_LENGTH = 50
19
19
20 APP_LABEL_BOARDS = 'boards'
20 APP_LABEL_BOARDS = 'boards'
21
21
22 CACHE_KEY_PPD = 'ppd'
22 CACHE_KEY_PPD = 'ppd'
23
23
24 POSTS_PER_DAY_RANGE = range(7)
24 POSTS_PER_DAY_RANGE = range(7)
25
25
26 BAN_REASON_AUTO = 'Auto'
26 BAN_REASON_AUTO = 'Auto'
27
27
28 IMAGE_THUMB_SIZE = (200, 150)
28 IMAGE_THUMB_SIZE = (200, 150)
29
29
30 TITLE_MAX_LENGTH = 50
30 TITLE_MAX_LENGTH = 50
31
31
32 DEFAULT_MARKUP_TYPE = 'markdown'
32 DEFAULT_MARKUP_TYPE = 'markdown'
33
33
34 NO_PARENT = -1
34 NO_PARENT = -1
35 NO_IP = '0.0.0.0'
35 NO_IP = '0.0.0.0'
36 UNKNOWN_UA = ''
36 UNKNOWN_UA = ''
37 ALL_PAGES = -1
37 ALL_PAGES = -1
38 IMAGES_DIRECTORY = 'images/'
38 IMAGES_DIRECTORY = 'images/'
39 FILE_EXTENSION_DELIMITER = '.'
39 FILE_EXTENSION_DELIMITER = '.'
40
40
41 SETTING_MODERATE = "moderate"
41 SETTING_MODERATE = "moderate"
42
42
43 REGEX_REPLY = re.compile('>>(\d+)')
43 REGEX_REPLY = re.compile('>>(\d+)')
44
44
45
45
46 class PostManager(models.Manager):
46 class PostManager(models.Manager):
47
47
48 def create_post(self, title, text, image=None, thread=None,
48 def create_post(self, title, text, image=None, thread=None,
49 ip=NO_IP, tags=None, user=None):
49 ip=NO_IP, tags=None, user=None):
50 """
50 """
51 Create new post
51 Create new post
52 """
52 """
53
53
54 posting_time = timezone.now()
54 posting_time = timezone.now()
55 if not thread:
55 if not thread:
56 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
57 last_edit_time=posting_time)
57 last_edit_time=posting_time)
58 else:
58 else:
59 thread.bump()
59 thread.bump()
60 thread.last_edit_time = posting_time
60 thread.last_edit_time = posting_time
61 thread.save()
61 thread.save()
62
62
63 post = self.create(title=title,
63 post = self.create(title=title,
64 text=text,
64 text=text,
65 pub_time=posting_time,
65 pub_time=posting_time,
66 thread_new=thread,
66 thread_new=thread,
67 image=image,
67 image=image,
68 poster_ip=ip,
68 poster_ip=ip,
69 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
69 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 # last!
70 # last!
71 last_edit_time=posting_time,
71 last_edit_time=posting_time,
72 user=user)
72 user=user)
73
73
74 thread.replies.add(post)
74 thread.replies.add(post)
75 if tags:
75 if tags:
76 linked_tags = []
76 linked_tags = []
77 for tag in tags:
77 for tag in tags:
78 tag_linked_tags = tag.get_linked_tags()
78 tag_linked_tags = tag.get_linked_tags()
79 if len(tag_linked_tags) > 0:
79 if len(tag_linked_tags) > 0:
80 linked_tags.extend(tag_linked_tags)
80 linked_tags.extend(tag_linked_tags)
81
81
82 tags.extend(linked_tags)
82 tags.extend(linked_tags)
83 map(thread.add_tag, tags)
83 map(thread.add_tag, tags)
84
84
85 self._delete_old_threads()
85 self._delete_old_threads()
86 self.connect_replies(post)
86 self.connect_replies(post)
87
87
88 return post
88 return post
89
89
90 def delete_post(self, post):
90 def delete_post(self, post):
91 """
91 """
92 Delete post and update or delete its thread
92 Delete post and update or delete its thread
93 """
93 """
94
94
95 thread = post.thread_new
95 thread = post.thread_new
96
96
97 if thread.get_opening_post() == self:
97 if thread.get_opening_post() == self:
98 thread.replies.delete()
98 thread.replies.delete()
99
99
100 thread.delete()
100 thread.delete()
101 else:
101 else:
102 thread.last_edit_time = timezone.now()
102 thread.last_edit_time = timezone.now()
103 thread.save()
103 thread.save()
104
104
105 post.delete()
105 post.delete()
106
106
107 def delete_posts_by_ip(self, ip):
107 def delete_posts_by_ip(self, ip):
108 """
108 """
109 Delete all posts of the author with same IP
109 Delete all posts of the author with same IP
110 """
110 """
111
111
112 posts = self.filter(poster_ip=ip)
112 posts = self.filter(poster_ip=ip)
113 map(self.delete_post, posts)
113 map(self.delete_post, posts)
114
114
115 # TODO Move this method to thread manager
115 # TODO Move this method to thread manager
116 def get_threads(self, tag=None, page=ALL_PAGES,
116 def get_threads(self, tag=None, page=ALL_PAGES,
117 order_by='-bump_time', archived=False):
117 order_by='-bump_time', archived=False):
118 if tag:
118 if tag:
119 threads = tag.threads
119 threads = tag.threads
120
120
121 if not threads.exists():
121 if not threads.exists():
122 raise Http404
122 raise Http404
123 else:
123 else:
124 threads = Thread.objects.all()
124 threads = Thread.objects.all()
125
125
126 threads = threads.filter(archived=archived).order_by(order_by)
126 threads = threads.filter(archived=archived).order_by(order_by)
127
127
128 if page != ALL_PAGES:
128 if page != ALL_PAGES:
129 thread_count = threads.count()
129 thread_count = threads.count()
130
130
131 if page < self._get_page_count(thread_count):
131 if page < self._get_page_count(thread_count):
132 start_thread = page * settings.THREADS_PER_PAGE
132 start_thread = page * settings.THREADS_PER_PAGE
133 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
133 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
134 thread_count)
134 thread_count)
135 threads = threads[start_thread:end_thread]
135 threads = threads[start_thread:end_thread]
136
136
137 return threads
137 return threads
138
138
139 # TODO Move this method to thread manager
139 # TODO Move this method to thread manager
140 def get_thread_page_count(self, tag=None, archived=False):
140 def get_thread_page_count(self, tag=None, archived=False):
141 if tag:
141 if tag:
142 threads = Thread.objects.filter(tags=tag)
142 threads = Thread.objects.filter(tags=tag)
143 else:
143 else:
144 threads = Thread.objects.all()
144 threads = Thread.objects.all()
145
145
146 threads = threads.filter(archived=archived)
146 threads = threads.filter(archived=archived)
147
147
148 return self._get_page_count(threads.count())
148 return self._get_page_count(threads.count())
149
149
150 # TODO Move this method to thread manager
150 # TODO Move this method to thread manager
151 def _delete_old_threads(self):
151 def _delete_old_threads(self):
152 """
152 """
153 Preserves maximum thread count. If there are too many threads,
153 Preserves maximum thread count. If there are too many threads,
154 archive the old ones.
154 archive the old ones.
155 """
155 """
156
156
157 threads = self.get_threads()
157 threads = self.get_threads()
158 thread_count = threads.count()
158 thread_count = threads.count()
159
159
160 if thread_count > settings.MAX_THREAD_COUNT:
160 if thread_count > settings.MAX_THREAD_COUNT:
161 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
161 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
162 old_threads = threads[thread_count - num_threads_to_delete:]
162 old_threads = threads[thread_count - num_threads_to_delete:]
163
163
164 for thread in old_threads:
164 for thread in old_threads:
165 thread.archived = True
165 thread.archived = True
166 thread.last_edit_time = timezone.now()
166 thread.save()
167 thread.save()
167
168
168 def connect_replies(self, post):
169 def connect_replies(self, post):
169 """
170 """
170 Connect replies to a post to show them as a reflink map
171 Connect replies to a post to show them as a reflink map
171 """
172 """
172
173
173 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
174 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
174 post_id = reply_number.group(1)
175 post_id = reply_number.group(1)
175 ref_post = self.filter(id=post_id)
176 ref_post = self.filter(id=post_id)
176 if ref_post.count() > 0:
177 if ref_post.count() > 0:
177 referenced_post = ref_post[0]
178 referenced_post = ref_post[0]
178 referenced_post.referenced_posts.add(post)
179 referenced_post.referenced_posts.add(post)
179 referenced_post.last_edit_time = post.pub_time
180 referenced_post.last_edit_time = post.pub_time
180 referenced_post.save()
181 referenced_post.save()
181
182
182 def _get_page_count(self, thread_count):
183 def _get_page_count(self, thread_count):
183 """
184 """
184 Get number of pages that will be needed for all threads
185 Get number of pages that will be needed for all threads
185 """
186 """
186
187
187 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
188 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
188
189
189 def get_posts_per_day(self):
190 def get_posts_per_day(self):
190 """
191 """
191 Get average count of posts per day for the last 7 days
192 Get average count of posts per day for the last 7 days
192 """
193 """
193
194
194 today = datetime.now().date()
195 today = datetime.now().date()
195 ppd = cache.get(CACHE_KEY_PPD + str(today))
196 ppd = cache.get(CACHE_KEY_PPD + str(today))
196 if ppd:
197 if ppd:
197 return ppd
198 return ppd
198
199
199 posts_per_days = []
200 posts_per_days = []
200 for i in POSTS_PER_DAY_RANGE:
201 for i in POSTS_PER_DAY_RANGE:
201 day_end = today - timedelta(i + 1)
202 day_end = today - timedelta(i + 1)
202 day_start = today - timedelta(i + 2)
203 day_start = today - timedelta(i + 2)
203
204
204 day_time_start = timezone.make_aware(datetime.combine(day_start,
205 day_time_start = timezone.make_aware(datetime.combine(day_start,
205 dtime()), timezone.get_current_timezone())
206 dtime()), timezone.get_current_timezone())
206 day_time_end = timezone.make_aware(datetime.combine(day_end,
207 day_time_end = timezone.make_aware(datetime.combine(day_end,
207 dtime()), timezone.get_current_timezone())
208 dtime()), timezone.get_current_timezone())
208
209
209 posts_per_days.append(float(self.filter(
210 posts_per_days.append(float(self.filter(
210 pub_time__lte=day_time_end,
211 pub_time__lte=day_time_end,
211 pub_time__gte=day_time_start).count()))
212 pub_time__gte=day_time_start).count()))
212
213
213 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
214 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
214 len(posts_per_days))
215 len(posts_per_days))
215 cache.set(CACHE_KEY_PPD, ppd)
216 cache.set(CACHE_KEY_PPD, ppd)
216 return ppd
217 return ppd
217
218
218
219
219 class Post(models.Model):
220 class Post(models.Model):
220 """A post is a message."""
221 """A post is a message."""
221
222
222 objects = PostManager()
223 objects = PostManager()
223
224
224 class Meta:
225 class Meta:
225 app_label = APP_LABEL_BOARDS
226 app_label = APP_LABEL_BOARDS
226
227
227 # TODO Save original file name to some field
228 # TODO Save original file name to some field
228 def _update_image_filename(self, filename):
229 def _update_image_filename(self, filename):
229 """Get unique image filename"""
230 """Get unique image filename"""
230
231
231 path = IMAGES_DIRECTORY
232 path = IMAGES_DIRECTORY
232 new_name = str(int(time.mktime(time.gmtime())))
233 new_name = str(int(time.mktime(time.gmtime())))
233 new_name += str(int(random() * 1000))
234 new_name += str(int(random() * 1000))
234 new_name += FILE_EXTENSION_DELIMITER
235 new_name += FILE_EXTENSION_DELIMITER
235 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
236 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
236
237
237 return os.path.join(path, new_name)
238 return os.path.join(path, new_name)
238
239
239 title = models.CharField(max_length=TITLE_MAX_LENGTH)
240 title = models.CharField(max_length=TITLE_MAX_LENGTH)
240 pub_time = models.DateTimeField()
241 pub_time = models.DateTimeField()
241 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
242 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
242 escape_html=False)
243 escape_html=False)
243
244
244 image_width = models.IntegerField(default=0)
245 image_width = models.IntegerField(default=0)
245 image_height = models.IntegerField(default=0)
246 image_height = models.IntegerField(default=0)
246
247
247 image_pre_width = models.IntegerField(default=0)
248 image_pre_width = models.IntegerField(default=0)
248 image_pre_height = models.IntegerField(default=0)
249 image_pre_height = models.IntegerField(default=0)
249
250
250 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
251 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
251 blank=True, sizes=(IMAGE_THUMB_SIZE,),
252 blank=True, sizes=(IMAGE_THUMB_SIZE,),
252 width_field='image_width',
253 width_field='image_width',
253 height_field='image_height',
254 height_field='image_height',
254 preview_width_field='image_pre_width',
255 preview_width_field='image_pre_width',
255 preview_height_field='image_pre_height')
256 preview_height_field='image_pre_height')
256
257
257 poster_ip = models.GenericIPAddressField()
258 poster_ip = models.GenericIPAddressField()
258 poster_user_agent = models.TextField()
259 poster_user_agent = models.TextField()
259
260
260 thread = models.ForeignKey('Post', null=True, default=None)
261 thread = models.ForeignKey('Post', null=True, default=None)
261 thread_new = models.ForeignKey('Thread', null=True, default=None)
262 thread_new = models.ForeignKey('Thread', null=True, default=None)
262 last_edit_time = models.DateTimeField()
263 last_edit_time = models.DateTimeField()
263 user = models.ForeignKey('User', null=True, default=None)
264 user = models.ForeignKey('User', null=True, default=None)
264
265
265 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
266 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
266 null=True,
267 null=True,
267 blank=True, related_name='rfp+')
268 blank=True, related_name='rfp+')
268
269
269 def __unicode__(self):
270 def __unicode__(self):
270 return '#' + str(self.id) + ' ' + self.title + ' (' + \
271 return '#' + str(self.id) + ' ' + self.title + ' (' + \
271 self.text.raw[:50] + ')'
272 self.text.raw[:50] + ')'
272
273
273 def get_title(self):
274 def get_title(self):
274 title = self.title
275 title = self.title
275 if len(title) == 0:
276 if len(title) == 0:
276 title = self.text.rendered[:MAX_TITLE_LENGTH]
277 title = self.text.rendered[:MAX_TITLE_LENGTH]
277
278
278 return title
279 return title
279
280
280 def get_sorted_referenced_posts(self):
281 def get_sorted_referenced_posts(self):
281 return self.referenced_posts.order_by('id')
282 return self.referenced_posts.order_by('id')
282
283
283 def is_referenced(self):
284 def is_referenced(self):
284 return self.referenced_posts.all().exists()
285 return self.referenced_posts.all().exists()
285
286
286 def is_opening(self):
287 def is_opening(self):
287 return self.thread_new.get_replies()[0] == self
288 return self.thread_new.get_replies()[0] == self
288
289
289
290
290 class Thread(models.Model):
291 class Thread(models.Model):
291
292
292 class Meta:
293 class Meta:
293 app_label = APP_LABEL_BOARDS
294 app_label = APP_LABEL_BOARDS
294
295
295 tags = models.ManyToManyField('Tag')
296 tags = models.ManyToManyField('Tag')
296 bump_time = models.DateTimeField()
297 bump_time = models.DateTimeField()
297 last_edit_time = models.DateTimeField()
298 last_edit_time = models.DateTimeField()
298 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
299 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
299 blank=True, related_name='tre+')
300 blank=True, related_name='tre+')
300 archived = models.BooleanField(default=False)
301 archived = models.BooleanField(default=False)
301
302
302 def get_tags(self):
303 def get_tags(self):
303 """
304 """
304 Get a sorted tag list
305 Get a sorted tag list
305 """
306 """
306
307
307 return self.tags.order_by('name')
308 return self.tags.order_by('name')
308
309
309 def bump(self):
310 def bump(self):
310 """
311 """
311 Bump (move to up) thread
312 Bump (move to up) thread
312 """
313 """
313
314
314 if self.can_bump():
315 if self.can_bump():
315 self.bump_time = timezone.now()
316 self.bump_time = timezone.now()
316
317
317 def get_reply_count(self):
318 def get_reply_count(self):
318 return self.replies.count()
319 return self.replies.count()
319
320
320 def get_images_count(self):
321 def get_images_count(self):
321 return self.replies.filter(image_width__gt=0).count()
322 return self.replies.filter(image_width__gt=0).count()
322
323
323 def can_bump(self):
324 def can_bump(self):
324 """
325 """
325 Check if the thread can be bumped by replying
326 Check if the thread can be bumped by replying
326 """
327 """
327
328
328 if self.archived:
329 if self.archived:
329 return False
330 return False
330
331
331 post_count = self.get_reply_count()
332 post_count = self.get_reply_count()
332
333
333 return post_count < settings.MAX_POSTS_PER_THREAD
334 return post_count < settings.MAX_POSTS_PER_THREAD
334
335
335 def delete_with_posts(self):
336 def delete_with_posts(self):
336 """
337 """
337 Completely delete thread and all its posts
338 Completely delete thread and all its posts
338 """
339 """
339
340
340 if self.replies.count() > 0:
341 if self.replies.count() > 0:
341 self.replies.all().delete()
342 self.replies.all().delete()
342
343
343 self.delete()
344 self.delete()
344
345
345 def get_last_replies(self):
346 def get_last_replies(self):
346 """
347 """
347 Get last replies, not including opening post
348 Get last replies, not including opening post
348 """
349 """
349
350
350 if settings.LAST_REPLIES_COUNT > 0:
351 if settings.LAST_REPLIES_COUNT > 0:
351 reply_count = self.get_reply_count()
352 reply_count = self.get_reply_count()
352
353
353 if reply_count > 0:
354 if reply_count > 0:
354 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
355 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
355 reply_count - 1)
356 reply_count - 1)
356 last_replies = self.replies.all().order_by('pub_time')[
357 last_replies = self.replies.all().order_by('pub_time')[
357 reply_count - reply_count_to_show:]
358 reply_count - reply_count_to_show:]
358
359
359 return last_replies
360 return last_replies
360
361
361 def get_replies(self):
362 def get_replies(self):
362 """
363 """
363 Get sorted thread posts
364 Get sorted thread posts
364 """
365 """
365
366
366 return self.replies.all().order_by('pub_time')
367 return self.replies.all().order_by('pub_time')
367
368
368 def add_tag(self, tag):
369 def add_tag(self, tag):
369 """
370 """
370 Connect thread to a tag and tag to a thread
371 Connect thread to a tag and tag to a thread
371 """
372 """
372
373
373 self.tags.add(tag)
374 self.tags.add(tag)
374 tag.threads.add(self)
375 tag.threads.add(self)
375
376
376 def get_opening_post(self):
377 def get_opening_post(self):
377 """
378 """
378 Get first post of the thread
379 Get first post of the thread
379 """
380 """
380
381
381 return self.get_replies()[0]
382 return self.get_replies()[0]
382
383
383 def __unicode__(self):
384 def __unicode__(self):
384 return str(self.get_replies()[0].id)
385 return str(self.get_replies()[0].id)
385
386
386 def get_pub_time(self):
387 def get_pub_time(self):
387 """
388 """
388 Thread does not have its own pub time, so we need to get it from
389 Thread does not have its own pub time, so we need to get it from
389 the opening post
390 the opening post
390 """
391 """
391
392
392 return self.get_opening_post().pub_time
393 return self.get_opening_post().pub_time
@@ -1,145 +1,145 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 board %}
5 {% load board %}
6 {% load static %}
6 {% load static %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>Neboard</title>
9 <title>Neboard</title>
10
10
11 {% if prev_page %}
11 {% if prev_page %}
12 <link rel="next" href="
12 <link rel="next" href="
13 {% if tag %}
13 {% if tag %}
14 {% url "tag" tag_name=tag page=prev_page %}
14 {% url "tag" tag_name=tag page=prev_page %}
15 {% else %}
15 {% else %}
16 {% url "index" page=prev_page %}
16 {% url "index" page=prev_page %}
17 {% endif %}
17 {% endif %}
18 " />
18 " />
19 {% endif %}
19 {% endif %}
20 {% if next_page %}
20 {% if next_page %}
21 <link rel="next" href="
21 <link rel="next" href="
22 {% if tag %}
22 {% if tag %}
23 {% url "tag" tag_name=tag page=next_page %}
23 {% url "tag" tag_name=tag page=next_page %}
24 {% else %}
24 {% else %}
25 {% url "index" page=next_page %}
25 {% url "index" page=next_page %}
26 {% endif %}
26 {% endif %}
27 " />
27 " />
28 {% endif %}
28 {% endif %}
29
29
30 {% endblock %}
30 {% endblock %}
31
31
32 {% block content %}
32 {% block content %}
33
33
34 {% get_current_language as LANGUAGE_CODE %}
34 {% get_current_language as LANGUAGE_CODE %}
35
35
36 {% if threads %}
36 {% if threads %}
37 {% if prev_page %}
37 {% if prev_page %}
38 <div class="page_link">
38 <div class="page_link">
39 <a href="{% url "archive" page=prev_page %}">{% trans "Previous page" %}</a>
39 <a href="{% url "archive" page=prev_page %}">{% trans "Previous page" %}</a>
40 </div>
40 </div>
41 {% endif %}
41 {% endif %}
42
42
43 {% for thread in threads %}
43 {% for thread in threads %}
44 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
44 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
45 <div class="thread">
45 <div class="thread">
46 <div class="post archive_post" id="{{ thread.op.id }}">
46 <div class="post archive_post" id="{{ thread.op.id }}">
47 {% if thread.op.image %}
47 {% if thread.op.image %}
48 <div class="image">
48 <div class="image">
49 <a class="thumb"
49 <a class="thumb"
50 href="{{ thread.op.image.url }}"><img
50 href="{{ thread.op.image.url }}"><img
51 src="{{ thread.op.image.url_200x150 }}"
51 src="{{ thread.op.image.url_200x150 }}"
52 alt="{{ thread.op.id }}"
52 alt="{{ thread.op.id }}"
53 width="{{ thread.op.image_pre_width }}"
53 width="{{ thread.op.image_pre_width }}"
54 height="{{ thread.op.image_pre_height }}"
54 height="{{ thread.op.image_pre_height }}"
55 data-width="{{ thread.op.image_width }}"
55 data-width="{{ thread.op.image_width }}"
56 data-height="{{ thread.op.image_height }}"/>
56 data-height="{{ thread.op.image_height }}"/>
57 </a>
57 </a>
58 </div>
58 </div>
59 {% endif %}
59 {% endif %}
60 <div class="message">
60 <div class="message">
61 <div class="post-info">
61 <div class="post-info">
62 <span class="title">{{ thread.op.title }}</span>
62 <span class="title">{{ thread.op.title }}</span>
63 <a class="post_id" href="{% url 'thread' thread.op.id %}"
63 <a class="post_id" href="{% url 'thread' thread.op.id %}"
64 > ({{ thread.op.id }})</a>
64 > ({{ thread.op.id }})</a>
65 [{{ thread.op.pub_time }}] β€” [{{ thread.thread.bump_time }}]
65 [{{ thread.op.pub_time }}] β€” [{{ thread.thread.last_edit_time }}]
66
66
67 [<a class="link" href="
67 [<a class="link" href="
68 {% url 'thread' thread.op.id %}">{% trans "Open" %}</a>]
68 {% url 'thread' thread.op.id %}">{% trans "Open" %}</a>]
69
69
70 {% if moderator %}
70 {% if moderator %}
71 <span class="moderator_info">
71 <span class="moderator_info">
72 [<a href="
72 [<a href="
73 {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}"
73 {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}"
74 >{% trans 'Delete' %}</a>]
74 >{% trans 'Delete' %}</a>]
75 ({{ thread.op.poster_ip }})
75 ({{ thread.op.poster_ip }})
76 [<a href="
76 [<a href="
77 {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}"
77 {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}"
78 >{% trans 'Ban IP' %}</a>]
78 >{% trans 'Ban IP' %}</a>]
79 </span>
79 </span>
80 {% endif %}
80 {% endif %}
81 </div>
81 </div>
82 {% autoescape off %}
82 {% autoescape off %}
83 {{ thread.op.text.rendered|truncatewords_html:50 }}
83 {{ thread.op.text.rendered|truncatewords_html:50 }}
84 {% endautoescape %}
84 {% endautoescape %}
85 {% if thread.op.is_referenced %}
85 {% if thread.op.is_referenced %}
86 <div class="refmap">
86 <div class="refmap">
87 {% trans "Replies" %}:
87 {% trans "Replies" %}:
88 {% for ref_post in thread.op.get_sorted_referenced_posts %}
88 {% for ref_post in thread.op.get_sorted_referenced_posts %}
89 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
89 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
90 >{% if not forloop.last %},{% endif %}
90 >{% if not forloop.last %},{% endif %}
91 {% endfor %}
91 {% endfor %}
92 </div>
92 </div>
93 {% endif %}
93 {% endif %}
94 </div>
94 </div>
95 <div class="metadata">
95 <div class="metadata">
96 {{ thread.thread.get_images_count }} {% trans 'images' %},
96 {{ thread.thread.get_images_count }} {% trans 'images' %},
97 {{ thread.thread.get_reply_count }} {% trans 'replies' %}.
97 {{ thread.thread.get_reply_count }} {% trans 'replies' %}.
98 {% if thread.thread.tags %}
98 {% if thread.thread.tags %}
99 <span class="tags">
99 <span class="tags">
100 {% for tag in thread.thread.get_tags %}
100 {% for tag in thread.thread.get_tags %}
101 <a class="tag" href="
101 <a class="tag" href="
102 {% url 'tag' tag_name=tag.name %}">
102 {% url 'tag' tag_name=tag.name %}">
103 #{{ tag.name }}</a
103 #{{ tag.name }}</a
104 >{% if not forloop.last %},{% endif %}
104 >{% if not forloop.last %},{% endif %}
105 {% endfor %}
105 {% endfor %}
106 </span>
106 </span>
107 {% endif %}
107 {% endif %}
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 {% endcache %}
111 {% endcache %}
112 {% endfor %}
112 {% endfor %}
113
113
114 {% if next_page %}
114 {% if next_page %}
115 <div class="page_link">
115 <div class="page_link">
116 <a href="{% url "archive" page=next_page %}">{% trans "Next page" %}</a>
116 <a href="{% url "archive" page=next_page %}">{% trans "Next page" %}</a>
117 </div>
117 </div>
118 {% endif %}
118 {% endif %}
119 {% else %}
119 {% else %}
120 <div class="post">
120 <div class="post">
121 {% trans 'No threads exist. Create the first one!' %}</div>
121 {% trans 'No threads exist. Create the first one!' %}</div>
122 {% endif %}
122 {% endif %}
123
123
124 {% endblock %}
124 {% endblock %}
125
125
126 {% block metapanel %}
126 {% block metapanel %}
127
127
128 <span class="metapanel">
128 <span class="metapanel">
129 <b><a href="{% url "authors" %}">Neboard</a> 1.5 Aker</b>
129 <b><a href="{% url "authors" %}">Neboard</a> 1.5 Aker</b>
130 {% trans "Pages:" %}[
130 {% trans "Pages:" %}[
131 {% for page in pages %}
131 {% for page in pages %}
132 <a
132 <a
133 {% ifequal page current_page %}
133 {% ifequal page current_page %}
134 class="current_page"
134 class="current_page"
135 {% endifequal %}
135 {% endifequal %}
136 href="
136 href="
137 {% url "archive" page=page %}
137 {% url "archive" page=page %}
138 ">{{ page }}</a>
138 ">{{ page }}</a>
139 {% if not forloop.last %},{% endif %}
139 {% if not forloop.last %},{% endif %}
140 {% endfor %}
140 {% endfor %}
141 ]
141 ]
142 [<a href="rss/">RSS</a>]
142 [<a href="rss/">RSS</a>]
143 </span>
143 </span>
144
144
145 {% endblock %}
145 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now