##// END OF EJS Templates
Show related tags in the tag page
neko259 -
r1269:819f972f default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,440 +1,446 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-08-10 10:22+0300\n"
10 "POT-Creation-Date: 2015-08-12 18:32+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:38
41 #: forms.py:38
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:39
46 #: forms.py:39
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:41
50 #: forms.py:41
51 msgid "Title"
51 msgid "Title"
52 msgstr "Заголовок"
52 msgstr "Заголовок"
53
53
54 #: forms.py:42
54 #: forms.py:42
55 msgid "Text"
55 msgid "Text"
56 msgstr "Текст"
56 msgstr "Текст"
57
57
58 #: forms.py:43
58 #: forms.py:43
59 msgid "Tag"
59 msgid "Tag"
60 msgstr "Метка"
60 msgstr "Метка"
61
61
62 #: forms.py:44 templates/boards/base.html:40 templates/search/search.html:7
62 #: forms.py:44 templates/boards/base.html:40 templates/search/search.html:7
63 msgid "Search"
63 msgid "Search"
64 msgstr "Поиск"
64 msgstr "Поиск"
65
65
66 #: forms.py:46
66 #: forms.py:46
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:147
71 #: forms.py:147
72 msgid "Image"
72 msgid "Image"
73 msgstr "Изображение"
73 msgstr "Изображение"
74
74
75 #: forms.py:150
75 #: forms.py:150
76 msgid "Image URL"
76 msgid "Image URL"
77 msgstr "URL изображения"
77 msgstr "URL изображения"
78
78
79 #: forms.py:156
79 #: forms.py:156
80 msgid "e-mail"
80 msgid "e-mail"
81 msgstr ""
81 msgstr ""
82
82
83 #: forms.py:159
83 #: forms.py:159
84 msgid "Additional threads"
84 msgid "Additional threads"
85 msgstr "Дополнительные темы"
85 msgstr "Дополнительные темы"
86
86
87 #: forms.py:170
87 #: forms.py:170
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:180
92 #: forms.py:180
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:200
97 #: forms.py:200
98 msgid "Invalid URL"
98 msgid "Invalid URL"
99 msgstr "Неверный URL"
99 msgstr "Неверный URL"
100
100
101 #: forms.py:221
101 #: forms.py:221
102 msgid "Invalid additional thread list"
102 msgid "Invalid additional thread list"
103 msgstr "Неверный список дополнительных тем"
103 msgstr "Неверный список дополнительных тем"
104
104
105 #: forms.py:253
105 #: forms.py:253
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:291
109 #: forms.py:291
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:338 templates/boards/all_threads.html:141
114 #: forms.py:338 templates/boards/all_threads.html:154
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:345
119 #: forms.py:345
120 msgid "Inappropriate characters in tags."
120 msgid "Inappropriate characters in tags."
121 msgstr "Недопустимые символы в метках."
121 msgstr "Недопустимые символы в метках."
122
122
123 #: forms.py:359
123 #: forms.py:359
124 msgid "Need at least one section."
124 msgid "Need at least one section."
125 msgstr "Нужен хотя бы один раздел."
125 msgstr "Нужен хотя бы один раздел."
126
126
127 #: forms.py:371
127 #: forms.py:371
128 msgid "Theme"
128 msgid "Theme"
129 msgstr "Тема"
129 msgstr "Тема"
130
130
131 #: forms.py:372
131 #: forms.py:372
132 msgid "Image view mode"
132 msgid "Image view mode"
133 msgstr "Режим просмотра изображений"
133 msgstr "Режим просмотра изображений"
134
134
135 #: forms.py:373
135 #: forms.py:373
136 msgid "User name"
136 msgid "User name"
137 msgstr "Имя пользователя"
137 msgstr "Имя пользователя"
138
138
139 #: forms.py:374
139 #: forms.py:374
140 msgid "Time zone"
140 msgid "Time zone"
141 msgstr "Часовой пояс"
141 msgstr "Часовой пояс"
142
142
143 #: forms.py:380
143 #: forms.py:380
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:68
159 #: templates/boards/all_threads.html:71
160 msgid "Edit tag"
160 msgid "Edit tag"
161 msgstr "Изменить метку"
161 msgstr "Изменить метку"
162
162
163 #: templates/boards/all_threads.html:76
163 #: templates/boards/all_threads.html:79
164 #, python-format
164 #, python-format
165 #| msgid "This tag has %(thread_count)s threads and %(post_count)s posts."
166 msgid ""
165 msgid ""
167 "This tag has %(active_thread_count)s/%(thread_count)s threads and "
166 "This tag has %(active_thread_count)s/%(thread_count)s threads and "
168 "%(post_count)s posts."
167 "%(post_count)s posts."
169 msgstr "С этой меткой есть %(active_thread_count)s/%(thread_count)s тем и %(post_count)s сообщений."
168 msgstr ""
169 "С этой меткой есть %(active_thread_count)s/%(thread_count)s тем и "
170 "%(post_count)s сообщений."
170
171
171 #: templates/boards/all_threads.html:83 templates/boards/feed.html:30
172 #: templates/boards/all_threads.html:81
173 #| msgid "Related message"
174 msgid "Related tags:"
175 msgstr "Похожие метки:"
176
177 #: templates/boards/all_threads.html:96 templates/boards/feed.html:30
172 #: templates/boards/notifications.html:17 templates/search/search.html:26
178 #: templates/boards/notifications.html:17 templates/search/search.html:26
173 msgid "Previous page"
179 msgid "Previous page"
174 msgstr "Предыдущая страница"
180 msgstr "Предыдущая страница"
175
181
176 #: templates/boards/all_threads.html:97
182 #: templates/boards/all_threads.html:110
177 #, python-format
183 #, python-format
178 msgid "Skipped %(count)s replies. Open thread to see all replies."
184 msgid "Skipped %(count)s replies. Open thread to see all replies."
179 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
185 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
180
186
181 #: templates/boards/all_threads.html:115 templates/boards/feed.html:40
187 #: templates/boards/all_threads.html:128 templates/boards/feed.html:40
182 #: templates/boards/notifications.html:27 templates/search/search.html:37
188 #: templates/boards/notifications.html:27 templates/search/search.html:37
183 msgid "Next page"
189 msgid "Next page"
184 msgstr "Следующая страница"
190 msgstr "Следующая страница"
185
191
186 #: templates/boards/all_threads.html:120
192 #: templates/boards/all_threads.html:133
187 msgid "No threads exist. Create the first one!"
193 msgid "No threads exist. Create the first one!"
188 msgstr "Нет тем. Создайте первую!"
194 msgstr "Нет тем. Создайте первую!"
189
195
190 #: templates/boards/all_threads.html:126
196 #: templates/boards/all_threads.html:139
191 msgid "Create new thread"
197 msgid "Create new thread"
192 msgstr "Создать новую тему"
198 msgstr "Создать новую тему"
193
199
194 #: templates/boards/all_threads.html:131 templates/boards/preview.html:16
200 #: templates/boards/all_threads.html:144 templates/boards/preview.html:16
195 #: templates/boards/thread_normal.html:38
201 #: templates/boards/thread_normal.html:38
196 msgid "Post"
202 msgid "Post"
197 msgstr "Отправить"
203 msgstr "Отправить"
198
204
199 #: templates/boards/all_threads.html:136
205 #: templates/boards/all_threads.html:149
200 msgid "Tags must be delimited by spaces. Text or image is required."
206 msgid "Tags must be delimited by spaces. Text or image is required."
201 msgstr ""
207 msgstr ""
202 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
208 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
203
209
204 #: templates/boards/all_threads.html:138 templates/boards/preview.html:6
210 #: templates/boards/all_threads.html:151 templates/boards/preview.html:6
205 #: templates/boards/staticpages/help.html:21
211 #: templates/boards/staticpages/help.html:21
206 #: templates/boards/thread_normal.html:42
212 #: templates/boards/thread_normal.html:42
207 msgid "Preview"
213 msgid "Preview"
208 msgstr "Предпросмотр"
214 msgstr "Предпросмотр"
209
215
210 #: templates/boards/all_threads.html:140 templates/boards/thread_normal.html:45
216 #: templates/boards/all_threads.html:153 templates/boards/thread_normal.html:45
211 msgid "Text syntax"
217 msgid "Text syntax"
212 msgstr "Синтаксис текста"
218 msgstr "Синтаксис текста"
213
219
214 #: templates/boards/all_threads.html:154 templates/boards/feed.html:53
220 #: templates/boards/all_threads.html:167 templates/boards/feed.html:53
215 msgid "Pages:"
221 msgid "Pages:"
216 msgstr "Страницы: "
222 msgstr "Страницы: "
217
223
218 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
224 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
219 msgid "Authors"
225 msgid "Authors"
220 msgstr "Авторы"
226 msgstr "Авторы"
221
227
222 #: templates/boards/authors.html:26
228 #: templates/boards/authors.html:26
223 msgid "Distributed under the"
229 msgid "Distributed under the"
224 msgstr "Распространяется под"
230 msgstr "Распространяется под"
225
231
226 #: templates/boards/authors.html:28
232 #: templates/boards/authors.html:28
227 msgid "license"
233 msgid "license"
228 msgstr "лицензией"
234 msgstr "лицензией"
229
235
230 #: templates/boards/authors.html:30
236 #: templates/boards/authors.html:30
231 msgid "Repository"
237 msgid "Repository"
232 msgstr "Репозиторий"
238 msgstr "Репозиторий"
233
239
234 #: templates/boards/base.html:14 templates/boards/base.html.py:41
240 #: templates/boards/base.html:14 templates/boards/base.html.py:41
235 msgid "Feed"
241 msgid "Feed"
236 msgstr "Лента"
242 msgstr "Лента"
237
243
238 #: templates/boards/base.html:31
244 #: templates/boards/base.html:31
239 msgid "All threads"
245 msgid "All threads"
240 msgstr "Все темы"
246 msgstr "Все темы"
241
247
242 #: templates/boards/base.html:37
248 #: templates/boards/base.html:37
243 msgid "Add tags"
249 msgid "Add tags"
244 msgstr "Добавить метки"
250 msgstr "Добавить метки"
245
251
246 #: templates/boards/base.html:39
252 #: templates/boards/base.html:39
247 msgid "Tag management"
253 msgid "Tag management"
248 msgstr "Управление метками"
254 msgstr "Управление метками"
249
255
250 #: templates/boards/base.html:39
256 #: templates/boards/base.html:39
251 msgid "tags"
257 msgid "tags"
252 msgstr "метки"
258 msgstr "метки"
253
259
254 #: templates/boards/base.html:40
260 #: templates/boards/base.html:40
255 msgid "search"
261 msgid "search"
256 msgstr "поиск"
262 msgstr "поиск"
257
263
258 #: templates/boards/base.html:41 templates/boards/feed.html:11
264 #: templates/boards/base.html:41 templates/boards/feed.html:11
259 msgid "feed"
265 msgid "feed"
260 msgstr "лента"
266 msgstr "лента"
261
267
262 #: templates/boards/base.html:42 templates/boards/random.html:6
268 #: templates/boards/base.html:42 templates/boards/random.html:6
263 msgid "Random images"
269 msgid "Random images"
264 msgstr "Случайные изображения"
270 msgstr "Случайные изображения"
265
271
266 #: templates/boards/base.html:42
272 #: templates/boards/base.html:42
267 msgid "random"
273 msgid "random"
268 msgstr "случайные"
274 msgstr "случайные"
269
275
270 #: templates/boards/base.html:45 templates/boards/base.html.py:46
276 #: templates/boards/base.html:45 templates/boards/base.html.py:46
271 #: templates/boards/notifications.html:8
277 #: templates/boards/notifications.html:8
272 msgid "Notifications"
278 msgid "Notifications"
273 msgstr "Уведомления"
279 msgstr "Уведомления"
274
280
275 #: templates/boards/base.html:53 templates/boards/settings.html:8
281 #: templates/boards/base.html:53 templates/boards/settings.html:8
276 msgid "Settings"
282 msgid "Settings"
277 msgstr "Настройки"
283 msgstr "Настройки"
278
284
279 #: templates/boards/base.html:66
285 #: templates/boards/base.html:66
280 msgid "Admin"
286 msgid "Admin"
281 msgstr "Администрирование"
287 msgstr "Администрирование"
282
288
283 #: templates/boards/base.html:68
289 #: templates/boards/base.html:68
284 #, python-format
290 #, python-format
285 msgid "Speed: %(ppd)s posts per day"
291 msgid "Speed: %(ppd)s posts per day"
286 msgstr "Скорость: %(ppd)s сообщений в день"
292 msgstr "Скорость: %(ppd)s сообщений в день"
287
293
288 #: templates/boards/base.html:70
294 #: templates/boards/base.html:70
289 msgid "Up"
295 msgid "Up"
290 msgstr "Вверх"
296 msgstr "Вверх"
291
297
292 #: templates/boards/feed.html:45
298 #: templates/boards/feed.html:45
293 msgid "No posts exist. Create the first one!"
299 msgid "No posts exist. Create the first one!"
294 msgstr "Нет сообщений. Создайте первое!"
300 msgstr "Нет сообщений. Создайте первое!"
295
301
296 #: templates/boards/post.html:25
302 #: templates/boards/post.html:25
297 msgid "Open"
303 msgid "Open"
298 msgstr "Открыть"
304 msgstr "Открыть"
299
305
300 #: templates/boards/post.html:27 templates/boards/post.html.py:38
306 #: templates/boards/post.html:27 templates/boards/post.html.py:38
301 msgid "Reply"
307 msgid "Reply"
302 msgstr "Ответить"
308 msgstr "Ответить"
303
309
304 #: templates/boards/post.html:33
310 #: templates/boards/post.html:33
305 msgid " in "
311 msgid " in "
306 msgstr " в "
312 msgstr " в "
307
313
308 #: templates/boards/post.html:43
314 #: templates/boards/post.html:43
309 msgid "Edit"
315 msgid "Edit"
310 msgstr "Изменить"
316 msgstr "Изменить"
311
317
312 #: templates/boards/post.html:45
318 #: templates/boards/post.html:45
313 msgid "Edit thread"
319 msgid "Edit thread"
314 msgstr "Изменить тему"
320 msgstr "Изменить тему"
315
321
316 #: templates/boards/post.html:84
322 #: templates/boards/post.html:84
317 msgid "Replies"
323 msgid "Replies"
318 msgstr "Ответы"
324 msgstr "Ответы"
319
325
320 #: templates/boards/post.html:97 templates/boards/thread.html:34
326 #: templates/boards/post.html:97 templates/boards/thread.html:34
321 msgid "messages"
327 msgid "messages"
322 msgstr "сообщений"
328 msgstr "сообщений"
323
329
324 #: templates/boards/post.html:98 templates/boards/thread.html:35
330 #: templates/boards/post.html:98 templates/boards/thread.html:35
325 msgid "images"
331 msgid "images"
326 msgstr "изображений"
332 msgstr "изображений"
327
333
328 #: templates/boards/rss/post.html:5
334 #: templates/boards/rss/post.html:5
329 msgid "Post image"
335 msgid "Post image"
330 msgstr "Изображение сообщения"
336 msgstr "Изображение сообщения"
331
337
332 #: templates/boards/settings.html:16
338 #: templates/boards/settings.html:16
333 msgid "You are moderator."
339 msgid "You are moderator."
334 msgstr "Вы модератор."
340 msgstr "Вы модератор."
335
341
336 #: templates/boards/settings.html:20
342 #: templates/boards/settings.html:20
337 msgid "Hidden tags:"
343 msgid "Hidden tags:"
338 msgstr "Скрытые метки:"
344 msgstr "Скрытые метки:"
339
345
340 #: templates/boards/settings.html:28
346 #: templates/boards/settings.html:28
341 msgid "No hidden tags."
347 msgid "No hidden tags."
342 msgstr "Нет скрытых меток."
348 msgstr "Нет скрытых меток."
343
349
344 #: templates/boards/settings.html:37
350 #: templates/boards/settings.html:37
345 msgid "Save"
351 msgid "Save"
346 msgstr "Сохранить"
352 msgstr "Сохранить"
347
353
348 #: templates/boards/staticpages/banned.html:6
354 #: templates/boards/staticpages/banned.html:6
349 msgid "Banned"
355 msgid "Banned"
350 msgstr "Заблокирован"
356 msgstr "Заблокирован"
351
357
352 #: templates/boards/staticpages/banned.html:11
358 #: templates/boards/staticpages/banned.html:11
353 msgid "Your IP address has been banned. Contact the administrator"
359 msgid "Your IP address has been banned. Contact the administrator"
354 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
360 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
355
361
356 #: templates/boards/staticpages/help.html:6
362 #: templates/boards/staticpages/help.html:6
357 #: templates/boards/staticpages/help.html:10
363 #: templates/boards/staticpages/help.html:10
358 msgid "Syntax"
364 msgid "Syntax"
359 msgstr "Синтаксис"
365 msgstr "Синтаксис"
360
366
361 #: templates/boards/staticpages/help.html:11
367 #: templates/boards/staticpages/help.html:11
362 msgid "Italic text"
368 msgid "Italic text"
363 msgstr "Курсивный текст"
369 msgstr "Курсивный текст"
364
370
365 #: templates/boards/staticpages/help.html:12
371 #: templates/boards/staticpages/help.html:12
366 msgid "Bold text"
372 msgid "Bold text"
367 msgstr "Полужирный текст"
373 msgstr "Полужирный текст"
368
374
369 #: templates/boards/staticpages/help.html:13
375 #: templates/boards/staticpages/help.html:13
370 msgid "Spoiler"
376 msgid "Spoiler"
371 msgstr "Спойлер"
377 msgstr "Спойлер"
372
378
373 #: templates/boards/staticpages/help.html:14
379 #: templates/boards/staticpages/help.html:14
374 msgid "Link to a post"
380 msgid "Link to a post"
375 msgstr "Ссылка на сообщение"
381 msgstr "Ссылка на сообщение"
376
382
377 #: templates/boards/staticpages/help.html:15
383 #: templates/boards/staticpages/help.html:15
378 msgid "Strikethrough text"
384 msgid "Strikethrough text"
379 msgstr "Зачеркнутый текст"
385 msgstr "Зачеркнутый текст"
380
386
381 #: templates/boards/staticpages/help.html:16
387 #: templates/boards/staticpages/help.html:16
382 msgid "Comment"
388 msgid "Comment"
383 msgstr "Комментарий"
389 msgstr "Комментарий"
384
390
385 #: templates/boards/staticpages/help.html:17
391 #: templates/boards/staticpages/help.html:17
386 #: templates/boards/staticpages/help.html:18
392 #: templates/boards/staticpages/help.html:18
387 msgid "Quote"
393 msgid "Quote"
388 msgstr "Цитата"
394 msgstr "Цитата"
389
395
390 #: templates/boards/staticpages/help.html:21
396 #: templates/boards/staticpages/help.html:21
391 msgid "You can try pasting the text and previewing the result here:"
397 msgid "You can try pasting the text and previewing the result here:"
392 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
398 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
393
399
394 #: templates/boards/tags.html:14
400 #: templates/boards/tags.html:17
395 msgid "Sections:"
401 msgid "Sections:"
396 msgstr "Разделы:"
402 msgstr "Разделы:"
397
403
398 #: templates/boards/tags.html:26
404 #: templates/boards/tags.html:30
399 msgid "Other tags:"
405 msgid "Other tags:"
400 msgstr "Другие метки:"
406 msgstr "Другие метки:"
401
407
402 #: templates/boards/tags.html:38
408 #: templates/boards/tags.html:43
403 msgid "All tags..."
409 msgid "All tags..."
404 msgstr "Все метки..."
410 msgstr "Все метки..."
405
411
406 #: templates/boards/thread.html:15
412 #: templates/boards/thread.html:15
407 msgid "Normal"
413 msgid "Normal"
408 msgstr "Нормальный"
414 msgstr "Нормальный"
409
415
410 #: templates/boards/thread.html:16
416 #: templates/boards/thread.html:16
411 msgid "Gallery"
417 msgid "Gallery"
412 msgstr "Галерея"
418 msgstr "Галерея"
413
419
414 #: templates/boards/thread.html:17
420 #: templates/boards/thread.html:17
415 msgid "Tree"
421 msgid "Tree"
416 msgstr "Дерево"
422 msgstr "Дерево"
417
423
418 #: templates/boards/thread.html:36
424 #: templates/boards/thread.html:36
419 msgid "Last update: "
425 msgid "Last update: "
420 msgstr "Последнее обновление: "
426 msgstr "Последнее обновление: "
421
427
422 #: templates/boards/thread_gallery.html:36
428 #: templates/boards/thread_gallery.html:36
423 msgid "No images."
429 msgid "No images."
424 msgstr "Нет изображений."
430 msgstr "Нет изображений."
425
431
426 #: templates/boards/thread_normal.html:17
432 #: templates/boards/thread_normal.html:17
427 msgid "posts to bumplimit"
433 msgid "posts to bumplimit"
428 msgstr "сообщений до бамплимита"
434 msgstr "сообщений до бамплимита"
429
435
430 #: templates/boards/thread_normal.html:31
436 #: templates/boards/thread_normal.html:31
431 msgid "Reply to thread"
437 msgid "Reply to thread"
432 msgstr "Ответить в тему"
438 msgstr "Ответить в тему"
433
439
434 #: templates/boards/thread_normal.html:46
440 #: templates/boards/thread_normal.html:46
435 msgid "Close form"
441 msgid "Close form"
436 msgstr "Закрыть форму"
442 msgstr "Закрыть форму"
437
443
438 #: templates/search/search.html:17
444 #: templates/search/search.html:17
439 msgid "Ok"
445 msgid "Ok"
440 msgstr "Ок"
446 msgstr "Ок"
@@ -1,102 +1,106 b''
1 from django.template.loader import render_to_string
1 from django.template.loader import render_to_string
2 from django.db import models
2 from django.db import models
3 from django.db.models import Count
3 from django.db.models import Count
4 from django.core.urlresolvers import reverse
4 from django.core.urlresolvers import reverse
5
5
6 from boards.models.base import Viewable
6 from boards.models.base import Viewable
7 from boards.utils import cached_result
7 from boards.utils import cached_result
8 import boards
8 import boards
9
9
10 __author__ = 'neko259'
10 __author__ = 'neko259'
11
11
12
12
13 class TagManager(models.Manager):
13 class TagManager(models.Manager):
14
14
15 def get_not_empty_tags(self):
15 def get_not_empty_tags(self):
16 """
16 """
17 Gets tags that have non-archived threads.
17 Gets tags that have non-archived threads.
18 """
18 """
19
19
20 return self.annotate(num_threads=Count('thread')).filter(num_threads__gt=0)\
20 return self.annotate(num_threads=Count('thread_tags')).filter(num_threads__gt=0)\
21 .order_by('-required', 'name')
21 .order_by('-required', 'name')
22
22
23 def get_tag_url_list(self, tags: list) -> str:
23 def get_tag_url_list(self, tags: list) -> str:
24 """
24 """
25 Gets a comma-separated list of tag links.
25 Gets a comma-separated list of tag links.
26 """
26 """
27
27
28 return ', '.join([tag.get_view() for tag in tags])
28 return ', '.join([tag.get_view() for tag in tags])
29
29
30
30
31 class Tag(models.Model, Viewable):
31 class Tag(models.Model, Viewable):
32 """
32 """
33 A tag is a text node assigned to the thread. The tag serves as a board
33 A tag is a text node assigned to the thread. The tag serves as a board
34 section. There can be multiple tags for each thread
34 section. There can be multiple tags for each thread
35 """
35 """
36
36
37 objects = TagManager()
37 objects = TagManager()
38
38
39 class Meta:
39 class Meta:
40 app_label = 'boards'
40 app_label = 'boards'
41 ordering = ('name',)
41 ordering = ('name',)
42
42
43 name = models.CharField(max_length=100, db_index=True, unique=True)
43 name = models.CharField(max_length=100, db_index=True, unique=True)
44 required = models.BooleanField(default=False, db_index=True)
44 required = models.BooleanField(default=False, db_index=True)
45 description = models.TextField(blank=True)
45 description = models.TextField(blank=True)
46
46
47 def __str__(self):
47 def __str__(self):
48 return self.name
48 return self.name
49
49
50 def is_empty(self) -> bool:
50 def is_empty(self) -> bool:
51 """
51 """
52 Checks if the tag has some threads.
52 Checks if the tag has some threads.
53 """
53 """
54
54
55 return self.get_thread_count() == 0
55 return self.get_thread_count() == 0
56
56
57 def get_thread_count(self, archived=None) -> int:
57 def get_thread_count(self, archived=None) -> int:
58 threads = self.get_threads()
58 threads = self.get_threads()
59 if archived is not None:
59 if archived is not None:
60 threads = threads.filter(archived=archived)
60 threads = threads.filter(archived=archived)
61 return threads.count()
61 return threads.count()
62
62
63 def get_active_thread_count(self) -> int:
63 def get_active_thread_count(self) -> int:
64 return self.get_thread_count(archived=False)
64 return self.get_thread_count(archived=False)
65
65
66 def get_absolute_url(self):
66 def get_absolute_url(self):
67 return reverse('tag', kwargs={'tag_name': self.name})
67 return reverse('tag', kwargs={'tag_name': self.name})
68
68
69 def get_threads(self):
69 def get_threads(self):
70 return self.thread_set.order_by('-bump_time')
70 return self.thread_tags.order_by('-bump_time')
71
71
72 def is_required(self):
72 def is_required(self):
73 return self.required
73 return self.required
74
74
75 def get_view(self):
75 def get_view(self):
76 link = '<a class="tag" href="{}">{}</a>'.format(
76 link = '<a class="tag" href="{}">{}</a>'.format(
77 self.get_absolute_url(), self.name)
77 self.get_absolute_url(), self.name)
78 if self.is_required():
78 if self.is_required():
79 link = '<b>{}</b>'.format(link)
79 link = '<b>{}</b>'.format(link)
80 return link
80 return link
81
81
82 def get_search_view(self, *args, **kwargs):
82 def get_search_view(self, *args, **kwargs):
83 return render_to_string('boards/tag.html', {
83 return render_to_string('boards/tag.html', {
84 'tag': self,
84 'tag': self,
85 })
85 })
86
86
87 @cached_result()
87 @cached_result()
88 def get_post_count(self):
88 def get_post_count(self):
89 return self.get_threads().aggregate(num_posts=Count('post'))['num_posts']
89 return self.get_threads().aggregate(num_posts=Count('post'))['num_posts']
90
90
91 def get_description(self):
91 def get_description(self):
92 return self.description
92 return self.description
93
93
94 def get_random_image_post(self, archived=False):
94 def get_random_image_post(self, archived=False):
95 posts = boards.models.Post.objects.annotate(images_count=Count(
95 posts = boards.models.Post.objects.annotate(images_count=Count(
96 'images')).filter(images_count__gt=0, threads__tags__in=[self])
96 'images')).filter(images_count__gt=0, threads__tags__in=[self])
97 if archived is not None:
97 if archived is not None:
98 posts = posts.filter(thread__archived=archived)
98 posts = posts.filter(thread__archived=archived)
99 return posts.order_by('?').first()
99 return posts.order_by('?').first()
100
100
101 def get_first_letter(self):
101 def get_first_letter(self):
102 return self.name and self.name[0] or ''
102 return self.name and self.name[0] or ''
103
104 def get_related_tags(self):
105 return Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
106 id=self.id).distinct()
@@ -1,230 +1,230 b''
1 import logging
1 import logging
2 from adjacent import Client
2 from adjacent import Client
3
3
4 from django.db.models import Count, Sum, QuerySet
4 from django.db.models import Count, Sum, QuerySet
5 from django.utils import timezone
5 from django.utils import timezone
6 from django.db import models
6 from django.db import models
7
7
8 from boards import settings
8 from boards import settings
9 import boards
9 import boards
10 from boards.utils import cached_result, datetime_to_epoch
10 from boards.utils import cached_result, datetime_to_epoch
11 from boards.models.post import Post
11 from boards.models.post import Post
12 from boards.models.tag import Tag
12 from boards.models.tag import Tag
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 logger = logging.getLogger(__name__)
18 logger = logging.getLogger(__name__)
19
19
20
20
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
22 WS_NOTIFICATION_TYPE = 'notification_type'
22 WS_NOTIFICATION_TYPE = 'notification_type'
23
23
24 WS_CHANNEL_THREAD = "thread:"
24 WS_CHANNEL_THREAD = "thread:"
25
25
26
26
27 class ThreadManager(models.Manager):
27 class ThreadManager(models.Manager):
28 def process_oldest_threads(self):
28 def process_oldest_threads(self):
29 """
29 """
30 Preserves maximum thread count. If there are too many threads,
30 Preserves maximum thread count. If there are too many threads,
31 archive or delete the old ones.
31 archive or delete the old ones.
32 """
32 """
33
33
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
35 thread_count = threads.count()
35 thread_count = threads.count()
36
36
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
38 if thread_count > max_thread_count:
38 if thread_count > max_thread_count:
39 num_threads_to_delete = thread_count - max_thread_count
39 num_threads_to_delete = thread_count - max_thread_count
40 old_threads = threads[thread_count - num_threads_to_delete:]
40 old_threads = threads[thread_count - num_threads_to_delete:]
41
41
42 for thread in old_threads:
42 for thread in old_threads:
43 if settings.get_bool('Storage', 'ArchiveThreads'):
43 if settings.get_bool('Storage', 'ArchiveThreads'):
44 self._archive_thread(thread)
44 self._archive_thread(thread)
45 else:
45 else:
46 thread.delete()
46 thread.delete()
47
47
48 logger.info('Processed %d old threads' % num_threads_to_delete)
48 logger.info('Processed %d old threads' % num_threads_to_delete)
49
49
50 def _archive_thread(self, thread):
50 def _archive_thread(self, thread):
51 thread.archived = True
51 thread.archived = True
52 thread.bumpable = False
52 thread.bumpable = False
53 thread.last_edit_time = timezone.now()
53 thread.last_edit_time = timezone.now()
54 thread.update_posts_time()
54 thread.update_posts_time()
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
56
56
57
57
58 def get_thread_max_posts():
58 def get_thread_max_posts():
59 return settings.get_int('Messages', 'MaxPostsPerThread')
59 return settings.get_int('Messages', 'MaxPostsPerThread')
60
60
61
61
62 class Thread(models.Model):
62 class Thread(models.Model):
63 objects = ThreadManager()
63 objects = ThreadManager()
64
64
65 class Meta:
65 class Meta:
66 app_label = 'boards'
66 app_label = 'boards'
67
67
68 tags = models.ManyToManyField('Tag')
68 tags = models.ManyToManyField('Tag', related_name='thread_tags')
69 bump_time = models.DateTimeField(db_index=True)
69 bump_time = models.DateTimeField(db_index=True)
70 last_edit_time = models.DateTimeField()
70 last_edit_time = models.DateTimeField()
71 archived = models.BooleanField(default=False)
71 archived = models.BooleanField(default=False)
72 bumpable = models.BooleanField(default=True)
72 bumpable = models.BooleanField(default=True)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
74
74
75 def get_tags(self) -> QuerySet:
75 def get_tags(self) -> QuerySet:
76 """
76 """
77 Gets a sorted tag list.
77 Gets a sorted tag list.
78 """
78 """
79
79
80 return self.tags.order_by('name')
80 return self.tags.order_by('name')
81
81
82 def bump(self):
82 def bump(self):
83 """
83 """
84 Bumps (moves to up) thread if possible.
84 Bumps (moves to up) thread if possible.
85 """
85 """
86
86
87 if self.can_bump():
87 if self.can_bump():
88 self.bump_time = self.last_edit_time
88 self.bump_time = self.last_edit_time
89
89
90 self.update_bump_status()
90 self.update_bump_status()
91
91
92 logger.info('Bumped thread %d' % self.id)
92 logger.info('Bumped thread %d' % self.id)
93
93
94 def has_post_limit(self) -> bool:
94 def has_post_limit(self) -> bool:
95 return self.max_posts > 0
95 return self.max_posts > 0
96
96
97 def update_bump_status(self, exclude_posts=None):
97 def update_bump_status(self, exclude_posts=None):
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
99 self.bumpable = False
99 self.bumpable = False
100 self.update_posts_time(exclude_posts=exclude_posts)
100 self.update_posts_time(exclude_posts=exclude_posts)
101
101
102 def _get_cache_key(self):
102 def _get_cache_key(self):
103 return [datetime_to_epoch(self.last_edit_time)]
103 return [datetime_to_epoch(self.last_edit_time)]
104
104
105 @cached_result(key_method=_get_cache_key)
105 @cached_result(key_method=_get_cache_key)
106 def get_reply_count(self) -> int:
106 def get_reply_count(self) -> int:
107 return self.get_replies().count()
107 return self.get_replies().count()
108
108
109 @cached_result(key_method=_get_cache_key)
109 @cached_result(key_method=_get_cache_key)
110 def get_images_count(self) -> int:
110 def get_images_count(self) -> int:
111 return self.get_replies().annotate(images_count=Count(
111 return self.get_replies().annotate(images_count=Count(
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
113
113
114 def can_bump(self) -> bool:
114 def can_bump(self) -> bool:
115 """
115 """
116 Checks if the thread can be bumped by replying to it.
116 Checks if the thread can be bumped by replying to it.
117 """
117 """
118
118
119 return self.bumpable and not self.archived
119 return self.bumpable and not self.archived
120
120
121 def get_last_replies(self) -> QuerySet:
121 def get_last_replies(self) -> QuerySet:
122 """
122 """
123 Gets several last replies, not including opening post
123 Gets several last replies, not including opening post
124 """
124 """
125
125
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
127
127
128 if last_replies_count > 0:
128 if last_replies_count > 0:
129 reply_count = self.get_reply_count()
129 reply_count = self.get_reply_count()
130
130
131 if reply_count > 0:
131 if reply_count > 0:
132 reply_count_to_show = min(last_replies_count,
132 reply_count_to_show = min(last_replies_count,
133 reply_count - 1)
133 reply_count - 1)
134 replies = self.get_replies()
134 replies = self.get_replies()
135 last_replies = replies[reply_count - reply_count_to_show:]
135 last_replies = replies[reply_count - reply_count_to_show:]
136
136
137 return last_replies
137 return last_replies
138
138
139 def get_skipped_replies_count(self) -> int:
139 def get_skipped_replies_count(self) -> int:
140 """
140 """
141 Gets number of posts between opening post and last replies.
141 Gets number of posts between opening post and last replies.
142 """
142 """
143 reply_count = self.get_reply_count()
143 reply_count = self.get_reply_count()
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
145 reply_count - 1)
145 reply_count - 1)
146 return reply_count - last_replies_count - 1
146 return reply_count - last_replies_count - 1
147
147
148 def get_replies(self, view_fields_only=False) -> QuerySet:
148 def get_replies(self, view_fields_only=False) -> QuerySet:
149 """
149 """
150 Gets sorted thread posts
150 Gets sorted thread posts
151 """
151 """
152
152
153 query = Post.objects.filter(threads__in=[self])
153 query = Post.objects.filter(threads__in=[self])
154 query = query.order_by('pub_time').prefetch_related('images', 'thread', 'threads')
154 query = query.order_by('pub_time').prefetch_related('images', 'thread', 'threads')
155 if view_fields_only:
155 if view_fields_only:
156 query = query.defer('poster_ip')
156 query = query.defer('poster_ip')
157 return query.all()
157 return query.all()
158
158
159 def get_top_level_replies(self) -> QuerySet:
159 def get_top_level_replies(self) -> QuerySet:
160 return self.get_replies().exclude(refposts__threads__in=[self])
160 return self.get_replies().exclude(refposts__threads__in=[self])
161
161
162 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
162 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
163 """
163 """
164 Gets replies that have at least one image attached
164 Gets replies that have at least one image attached
165 """
165 """
166
166
167 return self.get_replies(view_fields_only).annotate(images_count=Count(
167 return self.get_replies(view_fields_only).annotate(images_count=Count(
168 'images')).filter(images_count__gt=0)
168 'images')).filter(images_count__gt=0)
169
169
170 def get_opening_post(self, only_id=False) -> Post:
170 def get_opening_post(self, only_id=False) -> Post:
171 """
171 """
172 Gets the first post of the thread
172 Gets the first post of the thread
173 """
173 """
174
174
175 query = self.get_replies().order_by('pub_time')
175 query = self.get_replies().order_by('pub_time')
176 if only_id:
176 if only_id:
177 query = query.only('id')
177 query = query.only('id')
178 opening_post = query.first()
178 opening_post = query.first()
179
179
180 return opening_post
180 return opening_post
181
181
182 @cached_result()
182 @cached_result()
183 def get_opening_post_id(self) -> int:
183 def get_opening_post_id(self) -> int:
184 """
184 """
185 Gets ID of the first thread post.
185 Gets ID of the first thread post.
186 """
186 """
187
187
188 return self.get_opening_post(only_id=True).id
188 return self.get_opening_post(only_id=True).id
189
189
190 def get_pub_time(self):
190 def get_pub_time(self):
191 """
191 """
192 Gets opening post's pub time because thread does not have its own one.
192 Gets opening post's pub time because thread does not have its own one.
193 """
193 """
194
194
195 return self.get_opening_post().pub_time
195 return self.get_opening_post().pub_time
196
196
197 def __str__(self):
197 def __str__(self):
198 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
198 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
199
199
200 def get_tag_url_list(self) -> list:
200 def get_tag_url_list(self) -> list:
201 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
201 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
202
202
203 def update_posts_time(self, exclude_posts=None):
203 def update_posts_time(self, exclude_posts=None):
204 last_edit_time = self.last_edit_time
204 last_edit_time = self.last_edit_time
205
205
206 for post in self.post_set.all():
206 for post in self.post_set.all():
207 if exclude_posts is None or post not in exclude_posts:
207 if exclude_posts is None or post not in exclude_posts:
208 # Manual update is required because uids are generated on save
208 # Manual update is required because uids are generated on save
209 post.last_edit_time = last_edit_time
209 post.last_edit_time = last_edit_time
210 post.save(update_fields=['last_edit_time'])
210 post.save(update_fields=['last_edit_time'])
211
211
212 post.get_threads().update(last_edit_time=last_edit_time)
212 post.get_threads().update(last_edit_time=last_edit_time)
213
213
214 def notify_clients(self):
214 def notify_clients(self):
215 if not settings.get_bool('External', 'WebsocketsEnabled'):
215 if not settings.get_bool('External', 'WebsocketsEnabled'):
216 return
216 return
217
217
218 client = Client()
218 client = Client()
219
219
220 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
220 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
221 client.publish(channel_name, {
221 client.publish(channel_name, {
222 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
222 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
223 })
223 })
224 client.send()
224 client.send()
225
225
226 def get_absolute_url(self):
226 def get_absolute_url(self):
227 return self.get_opening_post().get_absolute_url()
227 return self.get_opening_post().get_absolute_url()
228
228
229 def get_required_tags(self):
229 def get_required_tags(self):
230 return self.get_tags().filter(required=True)
230 return self.get_tags().filter(required=True)
@@ -1,183 +1,192 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 {% if tag %}
11 {% if tag %}
12 <title>{{ tag.name }} - {{ site_name }}</title>
12 <title>{{ tag.name }} - {{ site_name }}</title>
13 {% else %}
13 {% else %}
14 <title>{{ site_name }}</title>
14 <title>{{ site_name }}</title>
15 {% endif %}
15 {% endif %}
16
16
17 {% if prev_page_link %}
17 {% if prev_page_link %}
18 <link rel="prev" href="{{ prev_page_link }}" />
18 <link rel="prev" href="{{ prev_page_link }}" />
19 {% endif %}
19 {% endif %}
20 {% if next_page_link %}
20 {% if next_page_link %}
21 <link rel="next" href="{{ next_page_link }}" />
21 <link rel="next" href="{{ next_page_link }}" />
22 {% endif %}
22 {% endif %}
23
23
24 {% endblock %}
24 {% endblock %}
25
25
26 {% block content %}
26 {% block content %}
27
27
28 {% get_current_language as LANGUAGE_CODE %}
28 {% get_current_language as LANGUAGE_CODE %}
29 {% get_current_timezone as TIME_ZONE %}
29 {% get_current_timezone as TIME_ZONE %}
30
30
31 {% for banner in banners %}
31 {% for banner in banners %}
32 <div class="post">
32 <div class="post">
33 <div class="title">{{ banner.title }}</div>
33 <div class="title">{{ banner.title }}</div>
34 <div>{{ banner.text }}</div>
34 <div>{{ banner.text }}</div>
35 <div>{% trans 'Related message' %}: <a href="{{ banner.post.get_absolute_url }}">>>{{ banner.post.id }}</a></div>
35 <div>{% trans 'Related message' %}: <a href="{{ banner.post.get_absolute_url }}">>>{{ banner.post.id }}</a></div>
36 </div>
36 </div>
37 {% endfor %}
37 {% endfor %}
38
38
39 {% if tag %}
39 {% if tag %}
40 <div class="tag_info">
40 <div class="tag_info">
41 {% if random_image_post %}
41 {% if random_image_post %}
42 <div class="tag-image">
42 <div class="tag-image">
43 {% with image=random_image_post.images.first %}
43 {% with image=random_image_post.images.first %}
44 <a href="{{ random_image_post.get_absolute_url }}"><img
44 <a href="{{ random_image_post.get_absolute_url }}"><img
45 src="{{ image.image.url_200x150 }}"
45 src="{{ image.image.url_200x150 }}"
46 width="{{ image.pre_width }}"
46 width="{{ image.pre_width }}"
47 height="{{ image.pre_height }}"/></a>
47 height="{{ image.pre_height }}"/></a>
48 {% endwith %}
48 {% endwith %}
49 </div>
49 </div>
50 {% endif %}
50 {% endif %}
51 <div class="tag-text-data">
51 <div class="tag-text-data">
52 <h2>
52 <h2>
53 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
53 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
54 {% if is_favorite %}
54 {% if is_favorite %}
55 <button name="method" value="unsubscribe" class="fav"></button>
55 <button name="method" value="unsubscribe" class="fav"></button>
56 {% else %}
56 {% else %}
57 <button name="method" value="subscribe" class="not_fav"></button>
57 <button name="method" value="subscribe" class="not_fav"></button>
58 {% endif %}
58 {% endif %}
59 </form>
59 </form>
60 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
60 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
61 {% if is_hidden %}
61 {% if is_hidden %}
62 <button name="method" value="unhide" class="fav">H</button>
62 <button name="method" value="unhide" class="fav">H</button>
63 {% else %}
63 {% else %}
64 <button name="method" value="hide" class="not_fav">H</button>
64 <button name="method" value="hide" class="not_fav">H</button>
65 {% endif %}
65 {% endif %}
66 </form>
66 </form>
67 {% autoescape off %}
67 {% autoescape off %}
68 {{ tag.get_view }}
68 {{ tag.get_view }}
69 {% endautoescape %}
69 {% endautoescape %}
70 {% if moderator %}
70 {% if moderator %}
71 <span class="moderator_info">| <a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a></span>
71 <span class="moderator_info">| <a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a></span>
72 {% endif %}
72 {% endif %}
73 </h2>
73 </h2>
74 {% if tag.get_description %}
74 {% if tag.get_description %}
75 {% autoescape off %}
75 {% autoescape off %}
76 <p>{{ tag.get_description }}</p>
76 <p>{{ tag.get_description }}</p>
77 {% endautoescape %}
77 {% endautoescape %}
78 {% endif %}
78 {% endif %}
79 <p>{% blocktrans with active_thread_count=tag.get_active_thread_count thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ active_thread_count}}/{{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
79 <p>{% blocktrans with active_thread_count=tag.get_active_thread_count thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ active_thread_count}}/{{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
80 {% if related_tags %}
81 <p>{% trans 'Related tags:' %}
82 {% for rel_tag in related_tags %}
83 {% autoescape off %}
84 {{ rel_tag.get_view }}{% if not forloop.last %}, {% endif %}
85 {% endautoescape %}
86 {% endfor %}
87 </p>
88 {% endif %}
80 </div>
89 </div>
81 </div>
90 </div>
82 {% endif %}
91 {% endif %}
83
92
84 {% if threads %}
93 {% if threads %}
85 {% if prev_page_link %}
94 {% if prev_page_link %}
86 <div class="page_link">
95 <div class="page_link">
87 <a href="{{ prev_page_link }}">{% trans "Previous page" %}</a>
96 <a href="{{ prev_page_link }}">{% trans "Previous page" %}</a>
88 </div>
97 </div>
89 {% endif %}
98 {% endif %}
90
99
91 {% for thread in threads %}
100 {% for thread in threads %}
92 <div class="thread">
101 <div class="thread">
93 {% post_view thread.get_opening_post moderator=moderator is_opening=True thread=thread truncated=True need_open_link=True %}
102 {% post_view thread.get_opening_post moderator=moderator is_opening=True thread=thread truncated=True need_open_link=True %}
94 {% if not thread.archived %}
103 {% if not thread.archived %}
95 {% with last_replies=thread.get_last_replies %}
104 {% with last_replies=thread.get_last_replies %}
96 {% if last_replies %}
105 {% if last_replies %}
97 {% with skipped_replies_count=thread.get_skipped_replies_count %}
106 {% with skipped_replies_count=thread.get_skipped_replies_count %}
98 {% if skipped_replies_count %}
107 {% if skipped_replies_count %}
99 <div class="skipped_replies">
108 <div class="skipped_replies">
100 <a href="{% url 'thread' thread.get_opening_post_id %}">
109 <a href="{% url 'thread' thread.get_opening_post_id %}">
101 {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
110 {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
102 </a>
111 </a>
103 </div>
112 </div>
104 {% endif %}
113 {% endif %}
105 {% endwith %}
114 {% endwith %}
106 <div class="last-replies">
115 <div class="last-replies">
107 {% for post in last_replies %}
116 {% for post in last_replies %}
108 {% post_view post is_opening=False moderator=moderator truncated=True %}
117 {% post_view post is_opening=False moderator=moderator truncated=True %}
109 {% endfor %}
118 {% endfor %}
110 </div>
119 </div>
111 {% endif %}
120 {% endif %}
112 {% endwith %}
121 {% endwith %}
113 {% endif %}
122 {% endif %}
114 </div>
123 </div>
115 {% endfor %}
124 {% endfor %}
116
125
117 {% if next_page_link %}
126 {% if next_page_link %}
118 <div class="page_link">
127 <div class="page_link">
119 <a href="{{ next_page_link }}">{% trans "Next page" %}</a>
128 <a href="{{ next_page_link }}">{% trans "Next page" %}</a>
120 </div>
129 </div>
121 {% endif %}
130 {% endif %}
122 {% else %}
131 {% else %}
123 <div class="post">
132 <div class="post">
124 {% trans 'No threads exist. Create the first one!' %}</div>
133 {% trans 'No threads exist. Create the first one!' %}</div>
125 {% endif %}
134 {% endif %}
126
135
127 <div class="post-form-w">
136 <div class="post-form-w">
128 <script src="{% static 'js/panel.js' %}"></script>
137 <script src="{% static 'js/panel.js' %}"></script>
129 <div class="post-form">
138 <div class="post-form">
130 <div class="form-title">{% trans "Create new thread" %}</div>
139 <div class="form-title">{% trans "Create new thread" %}</div>
131 <div class="swappable-form-full">
140 <div class="swappable-form-full">
132 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
141 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
133 {{ form.as_div }}
142 {{ form.as_div }}
134 <div class="form-submit">
143 <div class="form-submit">
135 <input type="submit" value="{% trans "Post" %}"/>
144 <input type="submit" value="{% trans "Post" %}"/>
136 </div>
145 </div>
137 </form>
146 </form>
138 </div>
147 </div>
139 <div>
148 <div>
140 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
149 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
141 </div>
150 </div>
142 <div><button id="preview-button">{% trans 'Preview' %}</button></div>
151 <div><button id="preview-button">{% trans 'Preview' %}</button></div>
143 <div id="preview-text"></div>
152 <div id="preview-text"></div>
144 <div><a href="{% url "staticpage" name="help" %}">{% trans 'Text syntax' %}</a></div>
153 <div><a href="{% url "staticpage" name="help" %}">{% trans 'Text syntax' %}</a></div>
145 <div><a href="{% url "tags" "required" %}">{% trans 'Tags' %}</a></div>
154 <div><a href="{% url "tags" "required" %}">{% trans 'Tags' %}</a></div>
146 </div>
155 </div>
147 </div>
156 </div>
148
157
149 <script src="{% static 'js/form.js' %}"></script>
158 <script src="{% static 'js/form.js' %}"></script>
150 <script src="{% static 'js/thread_create.js' %}"></script>
159 <script src="{% static 'js/thread_create.js' %}"></script>
151
160
152 {% endblock %}
161 {% endblock %}
153
162
154 {% block metapanel %}
163 {% block metapanel %}
155
164
156 <span class="metapanel">
165 <span class="metapanel">
157 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
166 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
158 {% trans "Pages:" %}
167 {% trans "Pages:" %}
159 [
168 [
160 {% with dividers=paginator.get_dividers %}
169 {% with dividers=paginator.get_dividers %}
161 {% for page in paginator.get_divided_range %}
170 {% for page in paginator.get_divided_range %}
162 {% if page in dividers %}
171 {% if page in dividers %}
163 …,
172 …,
164 {% endif %}
173 {% endif %}
165 <a
174 <a
166 {% ifequal page current_page.number %}
175 {% ifequal page current_page.number %}
167 class="current_page"
176 class="current_page"
168 {% endifequal %}
177 {% endifequal %}
169 href="
178 href="
170 {% if tag %}
179 {% if tag %}
171 {% url "tag" tag_name=tag.name %}?page={{ page }}
180 {% url "tag" tag_name=tag.name %}?page={{ page }}
172 {% else %}
181 {% else %}
173 {% url "index" %}?page={{ page }}
182 {% url "index" %}?page={{ page }}
174 {% endif %}
183 {% endif %}
175 ">{{ page }}</a>
184 ">{{ page }}</a>
176 {% if not forloop.last %},{% endif %}
185 {% if not forloop.last %},{% endif %}
177 {% endfor %}
186 {% endfor %}
178 {% endwith %}
187 {% endwith %}
179 ]
188 ]
180 [<a href="rss/">RSS</a>]
189 [<a href="rss/">RSS</a>]
181 </span>
190 </span>
182
191
183 {% endblock %}
192 {% endblock %}
@@ -1,123 +1,126 b''
1 from django.shortcuts import get_object_or_404, redirect
1 from django.shortcuts import get_object_or_404, redirect
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3
3
4 from boards.abstracts.settingsmanager import get_settings_manager, \
4 from boards.abstracts.settingsmanager import get_settings_manager, \
5 SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS
5 SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS
6 from boards.models import Tag, PostImage
6 from boards.models import Tag, PostImage
7 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
7 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
8 from boards.views.mixins import DispatcherMixin
8 from boards.views.mixins import DispatcherMixin
9 from boards.forms import ThreadForm, PlainErrorList
9 from boards.forms import ThreadForm, PlainErrorList
10
10
11 PARAM_HIDDEN_TAGS = 'hidden_tags'
11 PARAM_HIDDEN_TAGS = 'hidden_tags'
12 PARAM_TAG = 'tag'
12 PARAM_TAG = 'tag'
13 PARAM_IS_FAVORITE = 'is_favorite'
13 PARAM_IS_FAVORITE = 'is_favorite'
14 PARAM_IS_HIDDEN = 'is_hidden'
14 PARAM_IS_HIDDEN = 'is_hidden'
15 PARAM_RANDOM_IMAGE_POST = 'random_image_post'
15 PARAM_RANDOM_IMAGE_POST = 'random_image_post'
16 PARAM_RELATED_TAGS = 'related_tags'
17
16
18
17 __author__ = 'neko259'
19 __author__ = 'neko259'
18
20
19
21
20 class TagView(AllThreadsView, DispatcherMixin):
22 class TagView(AllThreadsView, DispatcherMixin):
21
23
22 tag_name = None
24 tag_name = None
23
25
24 def get_threads(self):
26 def get_threads(self):
25 tag = get_object_or_404(Tag, name=self.tag_name)
27 tag = get_object_or_404(Tag, name=self.tag_name)
26
28
27 hidden_tags = self.settings_manager.get_hidden_tags()
29 hidden_tags = self.settings_manager.get_hidden_tags()
28
30
29 try:
31 try:
30 hidden_tags.remove(tag)
32 hidden_tags.remove(tag)
31 except ValueError:
33 except ValueError:
32 pass
34 pass
33
35
34 return tag.get_threads().exclude(
36 return tag.get_threads().exclude(
35 tags__in=hidden_tags)
37 tags__in=hidden_tags)
36
38
37 def get_context_data(self, **kwargs):
39 def get_context_data(self, **kwargs):
38 params = super(TagView, self).get_context_data(**kwargs)
40 params = super(TagView, self).get_context_data(**kwargs)
39
41
40 settings_manager = get_settings_manager(kwargs['request'])
42 settings_manager = get_settings_manager(kwargs['request'])
41
43
42 tag = get_object_or_404(Tag, name=self.tag_name)
44 tag = get_object_or_404(Tag, name=self.tag_name)
43 params[PARAM_TAG] = tag
45 params[PARAM_TAG] = tag
44
46
45 fav_tag_names = settings_manager.get_setting(SETTING_FAVORITE_TAGS)
47 fav_tag_names = settings_manager.get_setting(SETTING_FAVORITE_TAGS)
46 hidden_tag_names = settings_manager.get_setting(SETTING_HIDDEN_TAGS)
48 hidden_tag_names = settings_manager.get_setting(SETTING_HIDDEN_TAGS)
47
49
48 params[PARAM_IS_FAVORITE] = fav_tag_names is not None and tag.name in fav_tag_names
50 params[PARAM_IS_FAVORITE] = fav_tag_names is not None and tag.name in fav_tag_names
49 params[PARAM_IS_HIDDEN] = hidden_tag_names is not None and tag.name in hidden_tag_names
51 params[PARAM_IS_HIDDEN] = hidden_tag_names is not None and tag.name in hidden_tag_names
50
52
51 params[PARAM_RANDOM_IMAGE_POST] = tag.get_random_image_post()
53 params[PARAM_RANDOM_IMAGE_POST] = tag.get_random_image_post()
54 params[PARAM_RELATED_TAGS] = tag.get_related_tags().all()
52
55
53 return params
56 return params
54
57
55 def get_previous_page_link(self, current_page):
58 def get_previous_page_link(self, current_page):
56 return reverse('tag', kwargs={
59 return reverse('tag', kwargs={
57 'tag_name': self.tag_name,
60 'tag_name': self.tag_name,
58 }) + '?page=' + str(current_page.previous_page_number())
61 }) + '?page=' + str(current_page.previous_page_number())
59
62
60 def get_next_page_link(self, current_page):
63 def get_next_page_link(self, current_page):
61 return reverse('tag', kwargs={
64 return reverse('tag', kwargs={
62 'tag_name': self.tag_name,
65 'tag_name': self.tag_name,
63 }) + '?page=' + str(current_page.next_page_number())
66 }) + '?page=' + str(current_page.next_page_number())
64
67
65 def get(self, request, tag_name, form=None):
68 def get(self, request, tag_name, form=None):
66 self.tag_name = tag_name
69 self.tag_name = tag_name
67
70
68 return super(TagView, self).get(request, form)
71 return super(TagView, self).get(request, form)
69
72
70
73
71 def post(self, request, tag_name):
74 def post(self, request, tag_name):
72 self.tag_name = tag_name
75 self.tag_name = tag_name
73
76
74 if 'method' in request.POST:
77 if 'method' in request.POST:
75 self.dispatch_method(request)
78 self.dispatch_method(request)
76 form = None
79 form = None
77
80
78 return redirect('tag', tag_name)
81 return redirect('tag', tag_name)
79 else:
82 else:
80 form = ThreadForm(request.POST, request.FILES,
83 form = ThreadForm(request.POST, request.FILES,
81 error_class=PlainErrorList)
84 error_class=PlainErrorList)
82 form.session = request.session
85 form.session = request.session
83
86
84 if form.is_valid():
87 if form.is_valid():
85 return self.create_thread(request, form)
88 return self.create_thread(request, form)
86 if form.need_to_ban:
89 if form.need_to_ban:
87 # Ban user because he is suspected to be a bot
90 # Ban user because he is suspected to be a bot
88 self._ban_current_user(request)
91 self._ban_current_user(request)
89
92
90 return self.get(request, tag_name, page, form)
93 return self.get(request, tag_name, page, form)
91
94
92 def subscribe(self, request):
95 def subscribe(self, request):
93 tag = get_object_or_404(Tag, name=self.tag_name)
96 tag = get_object_or_404(Tag, name=self.tag_name)
94
97
95 settings_manager = get_settings_manager(request)
98 settings_manager = get_settings_manager(request)
96 settings_manager.add_fav_tag(tag)
99 settings_manager.add_fav_tag(tag)
97
100
98 def unsubscribe(self, request):
101 def unsubscribe(self, request):
99 tag = get_object_or_404(Tag, name=self.tag_name)
102 tag = get_object_or_404(Tag, name=self.tag_name)
100
103
101 settings_manager = get_settings_manager(request)
104 settings_manager = get_settings_manager(request)
102 settings_manager.del_fav_tag(tag)
105 settings_manager.del_fav_tag(tag)
103
106
104 def hide(self, request):
107 def hide(self, request):
105 """
108 """
106 Adds tag to user's hidden tags. Threads with this tag will not be
109 Adds tag to user's hidden tags. Threads with this tag will not be
107 shown.
110 shown.
108 """
111 """
109
112
110 tag = get_object_or_404(Tag, name=self.tag_name)
113 tag = get_object_or_404(Tag, name=self.tag_name)
111
114
112 settings_manager = get_settings_manager(request)
115 settings_manager = get_settings_manager(request)
113 settings_manager.add_hidden_tag(tag)
116 settings_manager.add_hidden_tag(tag)
114
117
115 def unhide(self, request):
118 def unhide(self, request):
116 """
119 """
117 Removed tag from user's hidden tags.
120 Removed tag from user's hidden tags.
118 """
121 """
119
122
120 tag = get_object_or_404(Tag, name=self.tag_name)
123 tag = get_object_or_404(Tag, name=self.tag_name)
121
124
122 settings_manager = get_settings_manager(request)
125 settings_manager = get_settings_manager(request)
123 settings_manager.del_hidden_tag(tag)
126 settings_manager.del_hidden_tag(tag)
General Comments 0
You need to be logged in to leave comments. Login now