##// END OF EJS Templates
Add link to thread in the post feed
neko259 -
r1166:3a3c1fd7 default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
@@ -1,415 +1,418 b''
1 1 # SOME DESCRIPTIVE TITLE.
2 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 3 # This file is distributed under the same license as the PACKAGE package.
4 4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 5 #
6 6 msgid ""
7 7 msgstr ""
8 8 "Project-Id-Version: PACKAGE VERSION\n"
9 9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2015-05-14 12:40+0300\n"
10 "POT-Creation-Date: 2015-05-14 13:12+0300\n"
11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 14 "Language: ru\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20 20
21 21 #: admin.py:22
22 22 msgid "{} posters were banned"
23 23 msgstr ""
24 24
25 25 #: authors.py:9
26 26 msgid "author"
27 27 msgstr "автор"
28 28
29 29 #: authors.py:10
30 30 msgid "developer"
31 31 msgstr "разработчик"
32 32
33 33 #: authors.py:11
34 34 msgid "javascript developer"
35 35 msgstr "разработчик javascript"
36 36
37 37 #: authors.py:12
38 38 msgid "designer"
39 39 msgstr "дизайнер"
40 40
41 41 #: forms.py:35
42 42 msgid "Type message here. Use formatting panel for more advanced usage."
43 43 msgstr ""
44 44 "Вводите сообщение сюда. Используйте панель для более сложного форматирования."
45 45
46 46 #: forms.py:36
47 47 msgid "music images i_dont_like_tags"
48 48 msgstr "музыка картинки теги_не_нужны"
49 49
50 50 #: forms.py:38
51 51 msgid "Title"
52 52 msgstr "Заголовок"
53 53
54 54 #: forms.py:39
55 55 msgid "Text"
56 56 msgstr "Текст"
57 57
58 58 #: forms.py:40
59 59 msgid "Tag"
60 60 msgstr "Метка"
61 61
62 62 #: forms.py:41 templates/boards/base.html:41 templates/search/search.html:7
63 63 msgid "Search"
64 64 msgstr "Поиск"
65 65
66 66 #: forms.py:43
67 67 #, python-format
68 68 msgid "Please wait %s seconds before sending message"
69 69 msgstr "Пожалуйста подождите %s секунд перед отправкой сообщения"
70 70
71 71 #: forms.py:144
72 72 msgid "Image"
73 73 msgstr "Изображение"
74 74
75 75 #: forms.py:147
76 76 msgid "Image URL"
77 77 msgstr "URL изображения"
78 78
79 79 #: forms.py:153
80 80 msgid "e-mail"
81 81 msgstr ""
82 82
83 83 #: forms.py:156
84 84 msgid "Additional threads"
85 85 msgstr "Дополнительные темы"
86 86
87 87 #: forms.py:167
88 88 #, python-format
89 89 msgid "Title must have less than %s characters"
90 90 msgstr "Заголовок должен иметь меньше %s символов"
91 91
92 92 #: forms.py:177
93 93 #, python-format
94 94 msgid "Text must have less than %s characters"
95 95 msgstr "Текст должен быть короче %s символов"
96 96
97 97 #: forms.py:197
98 98 msgid "Invalid URL"
99 99 msgstr "Неверный URL"
100 100
101 101 #: forms.py:218
102 102 msgid "Invalid additional thread list"
103 103 msgstr "Неверный список дополнительных тем"
104 104
105 105 #: forms.py:250
106 106 msgid "Either text or image must be entered."
107 107 msgstr "Текст или картинка должны быть введены."
108 108
109 109 #: forms.py:288
110 110 #, python-format
111 111 msgid "Image must be less than %s bytes"
112 112 msgstr "Изображение должно быть менее %s байт"
113 113
114 114 #: forms.py:335 templates/boards/all_threads.html:129
115 115 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
116 116 msgid "Tags"
117 117 msgstr "Метки"
118 118
119 119 #: forms.py:342
120 120 msgid "Inappropriate characters in tags."
121 121 msgstr "Недопустимые символы в метках."
122 122
123 123 #: forms.py:356
124 124 msgid "Need at least one of the tags: "
125 125 msgstr "Нужна хотя бы одна из меток: "
126 126
127 127 #: forms.py:369
128 128 msgid "Theme"
129 129 msgstr "Тема"
130 130
131 131 #: forms.py:370
132 132 msgid "Image view mode"
133 133 msgstr "Режим просмотра изображений"
134 134
135 135 #: forms.py:371
136 136 msgid "User name"
137 137 msgstr "Имя пользователя"
138 138
139 139 #: forms.py:372
140 140 msgid "Time zone"
141 141 msgstr "Часовой пояс"
142 142
143 143 #: forms.py:378
144 144 msgid "Inappropriate characters."
145 145 msgstr "Недопустимые символы."
146 146
147 147 #: templates/boards/404.html:6
148 148 msgid "Not found"
149 149 msgstr "Не найдено"
150 150
151 151 #: templates/boards/404.html:12
152 152 msgid "This page does not exist"
153 153 msgstr "Этой страницы не существует"
154 154
155 155 #: templates/boards/all_threads.html:35
156 156 msgid "Related message"
157 157 msgstr "Связанное сообщение"
158 158
159 159 #: templates/boards/all_threads.html:60
160 160 msgid "Edit tag"
161 161 msgstr "Изменить метку"
162 162
163 163 #: templates/boards/all_threads.html:63
164 164 #, python-format
165 165 msgid "This tag has %(thread_count)s threads and %(post_count)s posts."
166 166 msgstr "С этой меткой есть %(thread_count)s тем и %(post_count)s сообщений."
167 167
168 168 #: templates/boards/all_threads.html:70 templates/boards/feed.html:30
169 169 #: templates/boards/notifications.html:17 templates/search/search.html:26
170 170 msgid "Previous page"
171 171 msgstr "Предыдущая страница"
172 172
173 173 #: templates/boards/all_threads.html:84
174 174 #, python-format
175 175 msgid "Skipped %(count)s replies. Open thread to see all replies."
176 176 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
177 177
178 178 #: templates/boards/all_threads.html:102 templates/boards/feed.html:40
179 179 #: templates/boards/notifications.html:27 templates/search/search.html:37
180 180 msgid "Next page"
181 181 msgstr "Следующая страница"
182 182
183 183 #: templates/boards/all_threads.html:107
184 184 msgid "No threads exist. Create the first one!"
185 185 msgstr "Нет тем. Создайте первую!"
186 186
187 187 #: templates/boards/all_threads.html:113
188 188 msgid "Create new thread"
189 189 msgstr "Создать новую тему"
190 190
191 191 #: templates/boards/all_threads.html:118 templates/boards/preview.html:16
192 192 #: templates/boards/thread_normal.html:43
193 193 msgid "Post"
194 194 msgstr "Отправить"
195 195
196 196 #: templates/boards/all_threads.html:123
197 197 msgid "Tags must be delimited by spaces. Text or image is required."
198 198 msgstr ""
199 199 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
200 200
201 201 #: templates/boards/all_threads.html:126
202 202 #: templates/boards/thread_normal.html:48
203 203 msgid "Text syntax"
204 204 msgstr "Синтаксис текста"
205 205
206 206 #: templates/boards/all_threads.html:143 templates/boards/feed.html:53
207 207 msgid "Pages:"
208 208 msgstr "Страницы: "
209 209
210 210 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
211 211 msgid "Authors"
212 212 msgstr "Авторы"
213 213
214 214 #: templates/boards/authors.html:26
215 215 msgid "Distributed under the"
216 216 msgstr "Распространяется под"
217 217
218 218 #: templates/boards/authors.html:28
219 219 msgid "license"
220 220 msgstr "лицензией"
221 221
222 222 #: templates/boards/authors.html:30
223 223 msgid "Repository"
224 224 msgstr "Репозиторий"
225 225
226 226 #: templates/boards/base.html:14 templates/boards/base.html.py:42
227 227 msgid "Feed"
228 228 msgstr "Лента"
229 229
230 230 #: templates/boards/base.html:31
231 231 msgid "All threads"
232 232 msgstr "Все темы"
233 233
234 234 #: templates/boards/base.html:37
235 235 msgid "Add tags"
236 236 msgstr "Добавить метки"
237 237
238 238 #: templates/boards/base.html:39
239 239 msgid "Tag management"
240 240 msgstr "Управление метками"
241 241
242 242 #: templates/boards/base.html:45 templates/boards/base.html.py:46
243 243 #: templates/boards/notifications.html:8
244 244 msgid "Notifications"
245 245 msgstr "Уведомления"
246 246
247 247 #: templates/boards/base.html:53 templates/boards/settings.html:8
248 248 msgid "Settings"
249 249 msgstr "Настройки"
250 250
251 251 #: templates/boards/base.html:66
252 252 msgid "Admin"
253 253 msgstr "Администрирование"
254 254
255 255 #: templates/boards/base.html:68
256 256 #, python-format
257 257 msgid "Speed: %(ppd)s posts per day"
258 258 msgstr "Скорость: %(ppd)s сообщений в день"
259 259
260 260 #: templates/boards/base.html:70
261 261 msgid "Up"
262 262 msgstr "Вверх"
263 263
264 264 #: templates/boards/feed.html:11
265 265 msgid "feed"
266 266 msgstr ""
267 267
268 268 #: templates/boards/feed.html:45
269 #| msgid "No threads exist. Create the first one!"
270 269 msgid "No posts exist. Create the first one!"
271 270 msgstr "Нет сообщений. Создайте первое!"
272 271
273 #: templates/boards/post.html:24
272 #: templates/boards/post.html:25
274 273 msgid "Open"
275 274 msgstr "Открыть"
276 275
277 #: templates/boards/post.html:26 templates/boards/post.html.py:30
276 #: templates/boards/post.html:27 templates/boards/post.html.py:38
278 277 msgid "Reply"
279 278 msgstr "Ответить"
280 279
281 #: templates/boards/post.html:35
280 #: templates/boards/post.html:33
281 msgid " in "
282 msgstr " в "
283
284 #: templates/boards/post.html:43
282 285 msgid "Edit"
283 286 msgstr "Изменить"
284 287
285 #: templates/boards/post.html:37
288 #: templates/boards/post.html:45
286 289 msgid "Edit thread"
287 290 msgstr "Изменить тему"
288 291
289 #: templates/boards/post.html:69
292 #: templates/boards/post.html:77
290 293 msgid "Replies"
291 294 msgstr "Ответы"
292 295
293 #: templates/boards/post.html:81 templates/boards/thread.html:26
296 #: templates/boards/post.html:89 templates/boards/thread.html:26
294 297 msgid "messages"
295 298 msgstr "сообщений"
296 299
297 #: templates/boards/post.html:82 templates/boards/thread.html:27
300 #: templates/boards/post.html:90 templates/boards/thread.html:27
298 301 msgid "images"
299 302 msgstr "изображений"
300 303
301 304 #: templates/boards/preview.html:6 templates/boards/staticpages/help.html:20
302 305 msgid "Preview"
303 306 msgstr "Предпросмотр"
304 307
305 308 #: templates/boards/rss/post.html:5
306 309 msgid "Post image"
307 310 msgstr "Изображение сообщения"
308 311
309 312 #: templates/boards/settings.html:16
310 313 msgid "You are moderator."
311 314 msgstr "Вы модератор."
312 315
313 316 #: templates/boards/settings.html:20
314 317 msgid "Hidden tags:"
315 318 msgstr "Скрытые метки:"
316 319
317 320 #: templates/boards/settings.html:28
318 321 msgid "No hidden tags."
319 322 msgstr "Нет скрытых меток."
320 323
321 324 #: templates/boards/settings.html:37
322 325 msgid "Save"
323 326 msgstr "Сохранить"
324 327
325 328 #: templates/boards/staticpages/banned.html:6
326 329 msgid "Banned"
327 330 msgstr "Заблокирован"
328 331
329 332 #: templates/boards/staticpages/banned.html:11
330 333 msgid "Your IP address has been banned. Contact the administrator"
331 334 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
332 335
333 336 #: templates/boards/staticpages/help.html:6
334 337 #: templates/boards/staticpages/help.html:10
335 338 msgid "Syntax"
336 339 msgstr "Синтаксис"
337 340
338 341 #: templates/boards/staticpages/help.html:11
339 342 msgid "Italic text"
340 343 msgstr "Курсивный текст"
341 344
342 345 #: templates/boards/staticpages/help.html:12
343 346 msgid "Bold text"
344 347 msgstr "Полужирный текст"
345 348
346 349 #: templates/boards/staticpages/help.html:13
347 350 msgid "Spoiler"
348 351 msgstr "Спойлер"
349 352
350 353 #: templates/boards/staticpages/help.html:14
351 354 msgid "Link to a post"
352 355 msgstr "Ссылка на сообщение"
353 356
354 357 #: templates/boards/staticpages/help.html:15
355 358 msgid "Strikethrough text"
356 359 msgstr "Зачеркнутый текст"
357 360
358 361 #: templates/boards/staticpages/help.html:16
359 362 msgid "Comment"
360 363 msgstr "Комментарий"
361 364
362 365 #: templates/boards/staticpages/help.html:17
363 366 #: templates/boards/staticpages/help.html:18
364 367 msgid "Quote"
365 368 msgstr "Цитата"
366 369
367 370 #: templates/boards/staticpages/help.html:20
368 371 msgid "You can try pasting the text and previewing the result here:"
369 372 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
370 373
371 374 #: templates/boards/tags.html:21
372 375 msgid "No tags found."
373 376 msgstr "Метки не найдены."
374 377
375 378 #: templates/boards/tags.html:24
376 379 msgid "All tags"
377 380 msgstr "Все метки"
378 381
379 382 #: templates/boards/thread.html:28
380 383 msgid "Last update: "
381 384 msgstr "Последнее обновление: "
382 385
383 386 #: templates/boards/thread_gallery.html:19
384 387 #: templates/boards/thread_normal.html:13
385 388 msgid "Normal mode"
386 389 msgstr "Нормальный режим"
387 390
388 391 #: templates/boards/thread_gallery.html:20
389 392 #: templates/boards/thread_normal.html:14
390 393 msgid "Gallery mode"
391 394 msgstr "Режим галереи"
392 395
393 396 #: templates/boards/thread_gallery.html:41
394 397 msgid "No images."
395 398 msgstr "Нет изображений."
396 399
397 400 #: templates/boards/thread_normal.html:22
398 401 msgid "posts to bumplimit"
399 402 msgstr "сообщений до бамплимита"
400 403
401 404 #: templates/boards/thread_normal.html:36
402 405 msgid "Reply to thread"
403 406 msgstr "Ответить в тему"
404 407
405 408 #: templates/boards/thread_normal.html:49
406 409 msgid "Close form"
407 410 msgstr "Закрыть форму"
408 411
409 412 #: templates/boards/thread_normal.html:63
410 413 msgid "Update"
411 414 msgstr "Обновить"
412 415
413 416 #: templates/search/search.html:17
414 417 msgid "Ok"
415 418 msgstr "Ок"
@@ -1,409 +1,411 b''
1 1 from datetime import datetime, timedelta, date
2 2 from datetime import time as dtime
3 3 import logging
4 4 import re
5 5 import uuid
6 6
7 7 from django.core.exceptions import ObjectDoesNotExist
8 8 from django.core.urlresolvers import reverse
9 9 from django.db import models, transaction
10 10 from django.db.models import TextField
11 11 from django.template.loader import render_to_string
12 12 from django.utils import timezone
13 13
14 14 from boards import settings
15 15 from boards.mdx_neboard import Parser
16 16 from boards.models import PostImage
17 17 from boards.models.base import Viewable
18 18 from boards import utils
19 19 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
20 20 from boards.models.user import Notification, Ban
21 21 import boards.models.thread
22 22
23 23
24 24 APP_LABEL_BOARDS = 'boards'
25 25
26 26 POSTS_PER_DAY_RANGE = 7
27 27
28 28 BAN_REASON_AUTO = 'Auto'
29 29
30 30 IMAGE_THUMB_SIZE = (200, 150)
31 31
32 32 TITLE_MAX_LENGTH = 200
33 33
34 34 # TODO This should be removed
35 35 NO_IP = '0.0.0.0'
36 36
37 37 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
38 38 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
39 39
40 40 PARAMETER_TRUNCATED = 'truncated'
41 41 PARAMETER_TAG = 'tag'
42 42 PARAMETER_OFFSET = 'offset'
43 43 PARAMETER_DIFF_TYPE = 'type'
44 44 PARAMETER_CSS_CLASS = 'css_class'
45 45 PARAMETER_THREAD = 'thread'
46 46 PARAMETER_IS_OPENING = 'is_opening'
47 47 PARAMETER_MODERATOR = 'moderator'
48 48 PARAMETER_POST = 'post'
49 49 PARAMETER_OP_ID = 'opening_post_id'
50 50 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
51 51 PARAMETER_REPLY_LINK = 'reply_link'
52 PARAMETER_NEED_OP_DATA = 'need_op_data'
52 53
53 54 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
54 55
55 56
56 57 class PostManager(models.Manager):
57 58 @transaction.atomic
58 59 def create_post(self, title: str, text: str, image=None, thread=None,
59 60 ip=NO_IP, tags: list=None, threads: list=None):
60 61 """
61 62 Creates new post
62 63 """
63 64
64 65 is_banned = Ban.objects.filter(ip=ip).exists()
65 66
66 67 # TODO Raise specific exception and catch it in the views
67 68 if is_banned:
68 69 raise Exception("This user is banned")
69 70
70 71 if not tags:
71 72 tags = []
72 73 if not threads:
73 74 threads = []
74 75
75 76 posting_time = timezone.now()
76 77 if not thread:
77 78 thread = boards.models.thread.Thread.objects.create(
78 79 bump_time=posting_time, last_edit_time=posting_time)
79 80 new_thread = True
80 81 else:
81 82 new_thread = False
82 83
83 84 pre_text = Parser().preparse(text)
84 85
85 86 post = self.create(title=title,
86 87 text=pre_text,
87 88 pub_time=posting_time,
88 89 poster_ip=ip,
89 90 thread=thread,
90 91 last_edit_time=posting_time)
91 92 post.threads.add(thread)
92 93
93 94 logger = logging.getLogger('boards.post.create')
94 95
95 96 logger.info('Created post {} by {}'.format(post, post.poster_ip))
96 97
97 98 if image:
98 99 post.images.add(PostImage.objects.create_with_hash(image))
99 100
100 101 list(map(thread.add_tag, tags))
101 102
102 103 if new_thread:
103 104 boards.models.thread.Thread.objects.process_oldest_threads()
104 105 else:
105 106 thread.last_edit_time = posting_time
106 107 thread.bump()
107 108 thread.save()
108 109
109 110 post.connect_replies()
110 111 post.connect_threads(threads)
111 112 post.connect_notifications()
112 113
113 114 post.build_url()
114 115
115 116 return post
116 117
117 118 def delete_posts_by_ip(self, ip):
118 119 """
119 120 Deletes all posts of the author with same IP
120 121 """
121 122
122 123 posts = self.filter(poster_ip=ip)
123 124 for post in posts:
124 125 post.delete()
125 126
126 127 @utils.cached_result()
127 128 def get_posts_per_day(self) -> float:
128 129 """
129 130 Gets average count of posts per day for the last 7 days
130 131 """
131 132
132 133 day_end = date.today()
133 134 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
134 135
135 136 day_time_start = timezone.make_aware(datetime.combine(
136 137 day_start, dtime()), timezone.get_current_timezone())
137 138 day_time_end = timezone.make_aware(datetime.combine(
138 139 day_end, dtime()), timezone.get_current_timezone())
139 140
140 141 posts_per_period = float(self.filter(
141 142 pub_time__lte=day_time_end,
142 143 pub_time__gte=day_time_start).count())
143 144
144 145 ppd = posts_per_period / POSTS_PER_DAY_RANGE
145 146
146 147 return ppd
147 148
148 149
149 150 class Post(models.Model, Viewable):
150 151 """A post is a message."""
151 152
152 153 objects = PostManager()
153 154
154 155 class Meta:
155 156 app_label = APP_LABEL_BOARDS
156 157 ordering = ('id',)
157 158
158 159 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
159 160 pub_time = models.DateTimeField()
160 161 text = TextField(blank=True, null=True)
161 162 _text_rendered = TextField(blank=True, null=True, editable=False)
162 163
163 164 images = models.ManyToManyField(PostImage, null=True, blank=True,
164 165 related_name='ip+', db_index=True)
165 166
166 167 poster_ip = models.GenericIPAddressField()
167 168
168 169 # TODO This field can be removed cause UID is used for update now
169 170 last_edit_time = models.DateTimeField()
170 171
171 172 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
172 173 null=True,
173 174 blank=True, related_name='rfp+',
174 175 db_index=True)
175 176 refmap = models.TextField(null=True, blank=True)
176 177 threads = models.ManyToManyField('Thread', db_index=True)
177 178 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
178 179
179 180 url = models.TextField()
180 181 uid = models.TextField(db_index=True)
181 182
182 183 def __str__(self):
183 184 return 'P#{}/{}'.format(self.id, self.title)
184 185
185 186 def get_title(self) -> str:
186 187 """
187 188 Gets original post title or part of its text.
188 189 """
189 190
190 191 title = self.title
191 192 if not title:
192 193 title = self.get_text()
193 194
194 195 return title
195 196
196 197 def build_refmap(self) -> None:
197 198 """
198 199 Builds a replies map string from replies list. This is a cache to stop
199 200 the server from recalculating the map on every post show.
200 201 """
201 202
202 203 post_urls = [REFMAP_STR.format(refpost.get_absolute_url(), refpost.id)
203 204 for refpost in self.referenced_posts.all()]
204 205
205 206 self.refmap = ', '.join(post_urls)
206 207
207 208 def is_referenced(self) -> bool:
208 209 return self.refmap and len(self.refmap) > 0
209 210
210 211 def is_opening(self) -> bool:
211 212 """
212 213 Checks if this is an opening post or just a reply.
213 214 """
214 215
215 216 return self.get_thread().get_opening_post_id() == self.id
216 217
217 218 def get_absolute_url(self):
218 219 return self.url
219 220
220 221 def get_thread(self):
221 222 return self.thread
222 223
223 224 def get_threads(self) -> list:
224 225 """
225 226 Gets post's thread.
226 227 """
227 228
228 229 return self.threads
229 230
230 231 def get_view(self, moderator=False, need_open_link=False,
231 232 truncated=False, reply_link=False, *args, **kwargs) -> str:
232 233 """
233 234 Renders post's HTML view. Some of the post params can be passed over
234 235 kwargs for the means of caching (if we view the thread, some params
235 236 are same for every post and don't need to be computed over and over.
236 237 """
237 238
238 239 thread = self.get_thread()
239 240 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
240 241
241 242 if is_opening:
242 243 opening_post_id = self.id
243 244 else:
244 245 opening_post_id = thread.get_opening_post_id()
245 246
246 247 css_class = 'post'
247 248 if thread.archived:
248 249 css_class += ' archive_post'
249 250 elif not thread.can_bump():
250 251 css_class += ' dead_post'
251 252
252 253 return render_to_string('boards/post.html', {
253 254 PARAMETER_POST: self,
254 255 PARAMETER_MODERATOR: moderator,
255 256 PARAMETER_IS_OPENING: is_opening,
256 257 PARAMETER_THREAD: thread,
257 258 PARAMETER_CSS_CLASS: css_class,
258 259 PARAMETER_NEED_OPEN_LINK: need_open_link,
259 260 PARAMETER_TRUNCATED: truncated,
260 261 PARAMETER_OP_ID: opening_post_id,
261 262 PARAMETER_REPLY_LINK: reply_link,
263 PARAMETER_NEED_OP_DATA: kwargs.get(PARAMETER_NEED_OP_DATA)
262 264 })
263 265
264 266 def get_search_view(self, *args, **kwargs):
265 267 return self.get_view(args, kwargs)
266 268
267 269 def get_first_image(self) -> PostImage:
268 270 return self.images.earliest('id')
269 271
270 272 def delete(self, using=None):
271 273 """
272 274 Deletes all post images and the post itself.
273 275 """
274 276
275 277 for image in self.images.all():
276 278 image_refs_count = Post.objects.filter(images__in=[image]).count()
277 279 if image_refs_count == 1:
278 280 image.delete()
279 281
280 282 thread = self.get_thread()
281 283 thread.last_edit_time = timezone.now()
282 284 thread.save()
283 285
284 286 super(Post, self).delete(using)
285 287
286 288 logging.getLogger('boards.post.delete').info(
287 289 'Deleted post {}'.format(self))
288 290
289 291 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
290 292 include_last_update=False) -> str:
291 293 """
292 294 Gets post HTML or JSON data that can be rendered on a page or used by
293 295 API.
294 296 """
295 297
296 298 return get_exporter(format_type).export(self, request,
297 299 include_last_update)
298 300
299 301 def notify_clients(self, recursive=True):
300 302 """
301 303 Sends post HTML data to the thread web socket.
302 304 """
303 305
304 306 if not settings.get_bool('External', 'WebsocketsEnabled'):
305 307 return
306 308
307 309 thread_ids = list()
308 310 for thread in self.get_threads().all():
309 311 thread_ids.append(thread.id)
310 312
311 313 thread.notify_clients()
312 314
313 315 if recursive:
314 316 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
315 317 post_id = reply_number.group(1)
316 318
317 319 try:
318 320 ref_post = Post.objects.get(id=post_id)
319 321
320 322 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
321 323 # If post is in this thread, its thread was already notified.
322 324 # Otherwise, notify its thread separately.
323 325 ref_post.notify_clients(recursive=False)
324 326 except ObjectDoesNotExist:
325 327 pass
326 328
327 329 def build_url(self):
328 330 thread = self.get_thread()
329 331 opening_id = thread.get_opening_post_id()
330 332 post_url = reverse('thread', kwargs={'post_id': opening_id})
331 333 if self.id != opening_id:
332 334 post_url += '#' + str(self.id)
333 335 self.url = post_url
334 336 self.save(update_fields=['url'])
335 337
336 338 def save(self, force_insert=False, force_update=False, using=None,
337 339 update_fields=None):
338 340 self._text_rendered = Parser().parse(self.get_raw_text())
339 341
340 342 self.uid = str(uuid.uuid4())
341 343 if update_fields is not None and 'uid' not in update_fields:
342 344 update_fields += ['uid']
343 345
344 346 if self.id:
345 347 for thread in self.get_threads().all():
346 348 if thread.can_bump():
347 349 thread.update_bump_status(exclude_posts=[self])
348 350 thread.last_edit_time = self.last_edit_time
349 351
350 352 thread.save(update_fields=['last_edit_time', 'bumpable'])
351 353
352 354 super().save(force_insert, force_update, using, update_fields)
353 355
354 356 def get_text(self) -> str:
355 357 return self._text_rendered
356 358
357 359 def get_raw_text(self) -> str:
358 360 return self.text
359 361
360 362 def get_absolute_id(self) -> str:
361 363 """
362 364 If the post has many threads, shows its main thread OP id in the post
363 365 ID.
364 366 """
365 367
366 368 if self.get_threads().count() > 1:
367 369 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
368 370 else:
369 371 return str(self.id)
370 372
371 373 def connect_notifications(self):
372 374 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
373 375 user_name = reply_number.group(1).lower()
374 376 Notification.objects.get_or_create(name=user_name, post=self)
375 377
376 378 def connect_replies(self):
377 379 """
378 380 Connects replies to a post to show them as a reflink map
379 381 """
380 382
381 383 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
382 384 post_id = reply_number.group(1)
383 385
384 386 try:
385 387 referenced_post = Post.objects.get(id=post_id)
386 388
387 389 referenced_post.referenced_posts.add(self)
388 390 referenced_post.last_edit_time = self.pub_time
389 391 referenced_post.build_refmap()
390 392 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
391 393 except ObjectDoesNotExist:
392 394 pass
393 395
394 396 def connect_threads(self, opening_posts):
395 397 """
396 398 If the referenced post is an OP in another thread,
397 399 make this post multi-thread.
398 400 """
399 401
400 402 for opening_post in opening_posts:
401 403 threads = opening_post.get_threads().all()
402 404 for thread in threads:
403 405 if thread.can_bump():
404 406 thread.update_bump_status()
405 407
406 408 thread.last_edit_time = self.last_edit_time
407 409 thread.save(update_fields=['last_edit_time', 'bumpable'])
408 410
409 411 self.threads.add(thread)
@@ -1,74 +1,74 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load board %}
5 5 {% load static %}
6 6 {% load tz %}
7 7
8 8 {% block head %}
9 9 <meta name="robots" content="noindex">
10 10
11 11 <title>{{ site_name }} - {% trans "feed" %}</title>
12 12
13 13 {% if prev_page_link %}
14 14 <link rel="prev" href="{{ prev_page_link }}" />
15 15 {% endif %}
16 16 {% if next_page_link %}
17 17 <link rel="next" href="{{ next_page_link }}" />
18 18 {% endif %}
19 19
20 20 {% endblock %}
21 21
22 22 {% block content %}
23 23
24 24 {% get_current_language as LANGUAGE_CODE %}
25 25 {% get_current_timezone as TIME_ZONE %}
26 26
27 27 {% if posts %}
28 28 {% if prev_page_link %}
29 29 <div class="page_link">
30 30 <a href="{{ prev_page_link }}">{% trans "Previous page" %}</a>
31 31 </div>
32 32 {% endif %}
33 33
34 34 {% for post in posts %}
35 {% post_view post moderator=moderator truncated=True %}
35 {% post_view post moderator=moderator truncated=True need_op_data=True %}
36 36 {% endfor %}
37 37
38 38 {% if next_page_link %}
39 39 <div class="page_link">
40 40 <a href="{{ next_page_link }}">{% trans "Next page" %}</a>
41 41 </div>
42 42 {% endif %}
43 43 {% else %}
44 44 <div class="post">
45 45 {% trans 'No posts exist. Create the first one!' %}</div>
46 46 {% endif %}
47 47 {% endblock %}
48 48
49 49 {% block metapanel %}
50 50
51 51 <span class="metapanel">
52 52 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
53 53 {% trans "Pages:" %}
54 54 <a href="
55 55 {% url "feed" page=paginator.page_range|first %}
56 56 ">&lt;&lt;</a>
57 57 [
58 58 {% for page in paginator.center_range %}
59 59 <a
60 60 {% ifequal page current_page.number %}
61 61 class="current_page"
62 62 {% endifequal %}
63 63 href="
64 64 {% url "feed" page=page %}
65 65 ">{{ page }}</a>
66 66 {% if not forloop.last %},{% endif %}
67 67 {% endfor %}
68 68 ]
69 69 <a href="
70 70 {% url "feed" page=paginator.page_range|last %}
71 71 ">&gt;&gt;</a>
72 72 </span>
73 73
74 74 {% endblock %}
@@ -1,87 +1,95 b''
1 1 {% load i18n %}
2 2 {% load board %}
3 3
4 4 {% get_current_language as LANGUAGE_CODE %}
5 5
6 6 <div class="{{ css_class }}" id="{{ post.id }}" data-uid="{{ post.uid }}">
7 7 <div class="post-info">
8 8 <a class="post_id" href="{{ post.get_absolute_url }}">({{ post.get_absolute_id }})</a>
9 9 <span class="title">{{ post.title }}</span>
10 10 <span class="pub_time"><time datetime="{{ post.pub_time|date:'c' }}">{{ post.pub_time }}</time></span>
11 11 {% comment %}
12 12 Thread death time needs to be shown only if the thread is alredy archived
13 13 and this is an opening post (thread death time) or a post for popup
14 14 (we don't see OP here so we show the death time in the post itself).
15 15 {% endcomment %}
16 16 {% if thread.archived %}
17 17 {% if is_opening %}
18 18 <time datetime="{{ thread.bump_time|date:'c' }}">{{ thread.bump_time }}</time>
19 19 {% endif %}
20 20 {% endif %}
21 {% if is_opening and need_open_link %}
22 {% if thread.archived %}
23 <a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>
24 {% else %}
25 <a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>
21 {% if is_opening %}
22 {% if need_open_link %}
23 {% if thread.archived %}
24 <a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>
25 {% else %}
26 <a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>
27 {% endif %}
28 {% endif %}
29 {% else %}
30 {% if need_op_data %}
31 {% with thread.get_opening_post as op %}
32 {% trans " in " %}<a href="{{ op.get_absolute_url }}">&gt;&gt;{{ op.id }}</a>
33 {% endwith %}
26 34 {% endif %}
27 35 {% endif %}
28 36 {% if reply_link and not thread.archived %}
29 37 <a href="#form" onclick="addQuickReply('{{ post.id }}'); return false;">{% trans 'Reply' %}</a>
30 38 {% endif %}
31 39
32 40 {% if moderator %}
33 41 <span class="moderator_info">
34 42 <a href="{% url 'admin:boards_post_change' post.id %}">{% trans 'Edit' %}</a>
35 43 {% if is_opening %}
36 44 | <a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>
37 45 {% endif %}
38 46 </span>
39 47 {% endif %}
40 48 </div>
41 49 {% comment %}
42 50 Post images. Currently only 1 image can be posted and shown, but post model
43 51 supports multiple.
44 52 {% endcomment %}
45 53 {% if post.images.exists %}
46 54 {% with post.images.all.0 as image %}
47 55 {% autoescape off %}
48 56 {{ image.get_view }}
49 57 {% endautoescape %}
50 58 {% endwith %}
51 59 {% endif %}
52 60 {% comment %}
53 61 Post message (text)
54 62 {% endcomment %}
55 63 <div class="message">
56 64 {% autoescape off %}
57 65 {% if truncated %}
58 66 {{ post.get_text|truncatewords_html:50 }}
59 67 {% else %}
60 68 {{ post.get_text }}
61 69 {% endif %}
62 70 {% endautoescape %}
63 71 {% if post.is_referenced %}
64 72 <div class="refmap">
65 73 {% autoescape off %}
66 74 {% trans "Replies" %}: {{ post.refmap }}
67 75 {% endautoescape %}
68 76 </div>
69 77 {% endif %}
70 78 </div>
71 79 {% comment %}
72 80 Thread metadata: counters, tags etc
73 81 {% endcomment %}
74 82 {% if is_opening %}
75 83 <div class="metadata">
76 84 {% if is_opening and need_open_link %}
77 85 {{ thread.get_reply_count }} {% trans 'messages' %},
78 86 {{ thread.get_images_count }} {% trans 'images' %}.
79 87 {% endif %}
80 88 <span class="tags">
81 89 {% autoescape off %}
82 90 {{ thread.get_tag_url_list }}
83 91 {% endautoescape %}
84 92 </span>
85 93 </div>
86 94 {% endif %}
87 95 </div>
General Comments 0
You need to be logged in to leave comments. Login now