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