##// END OF EJS Templates
Fixed PPD precision
neko259 -
r409:fcac6fa0 default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,376 +1,376 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: 2013-11-24 16:57+0200\n"
10 "POT-Creation-Date: 2013-11-24 16:57+0200\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 #: authors.py:5
21 #: authors.py:5
22 msgid "author"
22 msgid "author"
23 msgstr "автор"
23 msgstr "автор"
24
24
25 #: authors.py:6
25 #: authors.py:6
26 msgid "developer"
26 msgid "developer"
27 msgstr "разработчик"
27 msgstr "разработчик"
28
28
29 #: authors.py:7
29 #: authors.py:7
30 msgid "javascript developer"
30 msgid "javascript developer"
31 msgstr "разработчик javascript"
31 msgstr "разработчик javascript"
32
32
33 #: authors.py:8
33 #: authors.py:8
34 msgid "designer"
34 msgid "designer"
35 msgstr "дизайнер"
35 msgstr "дизайнер"
36
36
37 #: forms.py:48 templates/boards/posting_general.html:206
37 #: forms.py:48 templates/boards/posting_general.html:206
38 #: templates/boards/thread.html:101
38 #: templates/boards/thread.html:101
39 msgid "Title"
39 msgid "Title"
40 msgstr "Заголовок"
40 msgstr "Заголовок"
41
41
42 #: forms.py:50 templates/boards/posting_general.html:221
42 #: forms.py:50 templates/boards/posting_general.html:221
43 #: templates/boards/thread.html:116
43 #: templates/boards/thread.html:116
44 msgid "Text"
44 msgid "Text"
45 msgstr "Текст"
45 msgstr "Текст"
46
46
47 #: forms.py:51 templates/boards/posting_general.html:226
47 #: forms.py:51 templates/boards/posting_general.html:226
48 #: templates/boards/thread.html:121
48 #: templates/boards/thread.html:121
49 msgid "Image"
49 msgid "Image"
50 msgstr "Изображение"
50 msgstr "Изображение"
51
51
52 #: forms.py:54 templates/boards/posting_general.html:236
52 #: forms.py:54 templates/boards/posting_general.html:236
53 #: templates/boards/thread.html:126
53 #: templates/boards/thread.html:126
54 msgid "e-mail"
54 msgid "e-mail"
55 msgstr ""
55 msgstr ""
56
56
57 #: forms.py:65
57 #: forms.py:65
58 #, python-format
58 #, python-format
59 msgid "Title must have less than %s characters"
59 msgid "Title must have less than %s characters"
60 msgstr "Заголовок должен иметь меньше %s символов"
60 msgstr "Заголовок должен иметь меньше %s символов"
61
61
62 #: forms.py:74
62 #: forms.py:74
63 #, python-format
63 #, python-format
64 msgid "Text must have less than %s characters"
64 msgid "Text must have less than %s characters"
65 msgstr "Текст должен быть короче %s символов"
65 msgstr "Текст должен быть короче %s символов"
66
66
67 #: forms.py:85
67 #: forms.py:85
68 #, python-format
68 #, python-format
69 msgid "Image must be less than %s bytes"
69 msgid "Image must be less than %s bytes"
70 msgstr "Изображение должно быть менее %s байт"
70 msgstr "Изображение должно быть менее %s байт"
71
71
72 #: forms.py:112
72 #: forms.py:112
73 msgid "Either text or image must be entered."
73 msgid "Either text or image must be entered."
74 msgstr "Текст или картинка должны быть введены."
74 msgstr "Текст или картинка должны быть введены."
75
75
76 #: forms.py:125
76 #: forms.py:125
77 #, python-format
77 #, python-format
78 msgid "Wait %s seconds after last posting"
78 msgid "Wait %s seconds after last posting"
79 msgstr "Подождите %s секунд после последнего постинга"
79 msgstr "Подождите %s секунд после последнего постинга"
80
80
81 #: forms.py:139 templates/boards/post.html:60
81 #: forms.py:139 templates/boards/post.html:60
82 #: templates/boards/posting_general.html:231 templates/boards/tags.html:6
82 #: templates/boards/posting_general.html:231 templates/boards/tags.html:6
83 #: templates/boards/rss/post.html:10
83 #: templates/boards/rss/post.html:10
84 msgid "Tags"
84 msgid "Tags"
85 msgstr "Теги"
85 msgstr "Теги"
86
86
87 #: forms.py:147
87 #: forms.py:147
88 msgid "Inappropriate characters in tags."
88 msgid "Inappropriate characters in tags."
89 msgstr "Недопустимые символы в тегах."
89 msgstr "Недопустимые символы в тегах."
90
90
91 #: forms.py:175 forms.py:196
91 #: forms.py:175 forms.py:196
92 msgid "Captcha validation failed"
92 msgid "Captcha validation failed"
93 msgstr "Проверка капчи провалена"
93 msgstr "Проверка капчи провалена"
94
94
95 #: forms.py:202
95 #: forms.py:202
96 msgid "Theme"
96 msgid "Theme"
97 msgstr "Тема"
97 msgstr "Тема"
98
98
99 #: forms.py:207
99 #: forms.py:207
100 msgid "Enable moderation panel"
100 msgid "Enable moderation panel"
101 msgstr "Включить панель модерации"
101 msgstr "Включить панель модерации"
102
102
103 #: forms.py:222
103 #: forms.py:222
104 msgid "No such user found"
104 msgid "No such user found"
105 msgstr "Данный пользователь не найден"
105 msgstr "Данный пользователь не найден"
106
106
107 #: forms.py:236
107 #: forms.py:236
108 #, python-format
108 #, python-format
109 msgid "Wait %s minutes after last login"
109 msgid "Wait %s minutes after last login"
110 msgstr "Подождите %s минут после последнего входа"
110 msgstr "Подождите %s минут после последнего входа"
111
111
112 #: templates/boards/404.html:6
112 #: templates/boards/404.html:6
113 msgid "Not found"
113 msgid "Not found"
114 msgstr "Не найдено"
114 msgstr "Не найдено"
115
115
116 #: templates/boards/404.html:12
116 #: templates/boards/404.html:12
117 msgid "This page does not exist"
117 msgid "This page does not exist"
118 msgstr "Этой страницы не существует"
118 msgstr "Этой страницы не существует"
119
119
120 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
120 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
121 msgid "Authors"
121 msgid "Authors"
122 msgstr "Авторы"
122 msgstr "Авторы"
123
123
124 #: templates/boards/authors.html:25
124 #: templates/boards/authors.html:25
125 msgid "Distributed under the"
125 msgid "Distributed under the"
126 msgstr "Распространяется под"
126 msgstr "Распространяется под"
127
127
128 #: templates/boards/authors.html:27
128 #: templates/boards/authors.html:27
129 msgid "license"
129 msgid "license"
130 msgstr "лицензией"
130 msgstr "лицензией"
131
131
132 #: templates/boards/authors.html:29
132 #: templates/boards/authors.html:29
133 msgid "Repository"
133 msgid "Repository"
134 msgstr "Репозиторий"
134 msgstr "Репозиторий"
135
135
136 #: templates/boards/base.html:13
136 #: templates/boards/base.html:13
137 msgid "Feed"
137 msgid "Feed"
138 msgstr "Лента"
138 msgstr "Лента"
139
139
140 #: templates/boards/base.html:35
140 #: templates/boards/base.html:35
141 msgid "All threads"
141 msgid "All threads"
142 msgstr "Все темы"
142 msgstr "Все темы"
143
143
144 #: templates/boards/base.html:40
144 #: templates/boards/base.html:40
145 msgid "Tag management"
145 msgid "Tag management"
146 msgstr "Управление тегами"
146 msgstr "Управление тегами"
147
147
148 #: templates/boards/base.html:42
148 #: templates/boards/base.html:42
149 msgid "Settings"
149 msgid "Settings"
150 msgstr "Настройки"
150 msgstr "Настройки"
151
151
152 #: templates/boards/base.html:49 templates/boards/login.html:6
152 #: templates/boards/base.html:49 templates/boards/login.html:6
153 #: templates/boards/login.html.py:21
153 #: templates/boards/login.html.py:21
154 msgid "Login"
154 msgid "Login"
155 msgstr "Вход"
155 msgstr "Вход"
156
156
157 #: templates/boards/base.html:50
157 #: templates/boards/base.html:50
158 #, python-format
158 #, python-format
159 msgid "Speed: %(posts_per_day)s posts per day"
159 msgid "Speed: %(ppd)s posts per day"
160 msgstr "Скорость: %(posts_per_day)s сообщений в день"
160 msgstr "Скорость: %(ppd)s сообщений в день"
161
161
162 #: templates/boards/base.html:51
162 #: templates/boards/base.html:51
163 msgid "Up"
163 msgid "Up"
164 msgstr "Вверх"
164 msgstr "Вверх"
165
165
166 #: templates/boards/login.html:15
166 #: templates/boards/login.html:15
167 msgid "User ID"
167 msgid "User ID"
168 msgstr "ID пользователя"
168 msgstr "ID пользователя"
169
169
170 #: templates/boards/login.html:24
170 #: templates/boards/login.html:24
171 msgid "Insert your user id above"
171 msgid "Insert your user id above"
172 msgstr "Вставьте свой ID пользователя выше"
172 msgstr "Вставьте свой ID пользователя выше"
173
173
174 #: templates/boards/post.html:34 templates/boards/posting_general.html:100
174 #: templates/boards/post.html:34 templates/boards/posting_general.html:100
175 #: templates/boards/thread.html:59
175 #: templates/boards/thread.html:59
176 msgid "Delete"
176 msgid "Delete"
177 msgstr "Удалить"
177 msgstr "Удалить"
178
178
179 #: templates/boards/post.html:37 templates/boards/posting_general.html:104
179 #: templates/boards/post.html:37 templates/boards/posting_general.html:104
180 #: templates/boards/thread.html:62
180 #: templates/boards/thread.html:62
181 msgid "Ban IP"
181 msgid "Ban IP"
182 msgstr "Заблокировать IP"
182 msgstr "Заблокировать IP"
183
183
184 #: templates/boards/post.html:50 templates/boards/posting_general.html:113
184 #: templates/boards/post.html:50 templates/boards/posting_general.html:113
185 #: templates/boards/posting_general.html:169 templates/boards/thread.html:71
185 #: templates/boards/posting_general.html:169 templates/boards/thread.html:71
186 msgid "Replies"
186 msgid "Replies"
187 msgstr "Ответы"
187 msgstr "Ответы"
188
188
189 #: templates/boards/posting_general.html:63
189 #: templates/boards/posting_general.html:63
190 msgid "Previous page"
190 msgid "Previous page"
191 msgstr "Предыдущая страница"
191 msgstr "Предыдущая страница"
192
192
193 #: templates/boards/posting_general.html:94
193 #: templates/boards/posting_general.html:94
194 msgid "Reply"
194 msgid "Reply"
195 msgstr "Ответ"
195 msgstr "Ответ"
196
196
197 #: templates/boards/posting_general.html:122 templates/boards/thread.html:154
197 #: templates/boards/posting_general.html:122 templates/boards/thread.html:154
198 msgid "replies"
198 msgid "replies"
199 msgstr "ответов"
199 msgstr "ответов"
200
200
201 #: templates/boards/posting_general.html:123 templates/boards/thread.html:155
201 #: templates/boards/posting_general.html:123 templates/boards/thread.html:155
202 msgid "images"
202 msgid "images"
203 msgstr "изображений"
203 msgstr "изображений"
204
204
205 #: templates/boards/posting_general.html:192
205 #: templates/boards/posting_general.html:192
206 msgid "Next page"
206 msgid "Next page"
207 msgstr "Следующая страница"
207 msgstr "Следующая страница"
208
208
209 #: templates/boards/posting_general.html:197
209 #: templates/boards/posting_general.html:197
210 msgid "No threads exist. Create the first one!"
210 msgid "No threads exist. Create the first one!"
211 msgstr "Нет тем. Создайте первую!"
211 msgstr "Нет тем. Создайте первую!"
212
212
213 #: templates/boards/posting_general.html:203
213 #: templates/boards/posting_general.html:203
214 msgid "Create new thread"
214 msgid "Create new thread"
215 msgstr "Создать новую тему"
215 msgstr "Создать новую тему"
216
216
217 #: templates/boards/posting_general.html:211 templates/boards/thread.html:106
217 #: templates/boards/posting_general.html:211 templates/boards/thread.html:106
218 msgid "Formatting"
218 msgid "Formatting"
219 msgstr "Форматирование"
219 msgstr "Форматирование"
220
220
221 #: templates/boards/posting_general.html:213 templates/boards/thread.html:108
221 #: templates/boards/posting_general.html:213 templates/boards/thread.html:108
222 msgid "quote"
222 msgid "quote"
223 msgstr "цитата"
223 msgstr "цитата"
224
224
225 #: templates/boards/posting_general.html:214 templates/boards/thread.html:109
225 #: templates/boards/posting_general.html:214 templates/boards/thread.html:109
226 msgid "italic"
226 msgid "italic"
227 msgstr "курсив"
227 msgstr "курсив"
228
228
229 #: templates/boards/posting_general.html:215 templates/boards/thread.html:110
229 #: templates/boards/posting_general.html:215 templates/boards/thread.html:110
230 msgid "bold"
230 msgid "bold"
231 msgstr "полужирный"
231 msgstr "полужирный"
232
232
233 #: templates/boards/posting_general.html:216 templates/boards/thread.html:111
233 #: templates/boards/posting_general.html:216 templates/boards/thread.html:111
234 msgid "spoiler"
234 msgid "spoiler"
235 msgstr "спойлер"
235 msgstr "спойлер"
236
236
237 #: templates/boards/posting_general.html:217 templates/boards/thread.html:112
237 #: templates/boards/posting_general.html:217 templates/boards/thread.html:112
238 msgid "comment"
238 msgid "comment"
239 msgstr "комментарий"
239 msgstr "комментарий"
240
240
241 #: templates/boards/posting_general.html:249 templates/boards/thread.html:140
241 #: templates/boards/posting_general.html:249 templates/boards/thread.html:140
242 msgid "Post"
242 msgid "Post"
243 msgstr "Отправить"
243 msgstr "Отправить"
244
244
245 #: templates/boards/posting_general.html:251
245 #: templates/boards/posting_general.html:251
246 msgid "Tags must be delimited by spaces. Text or image is required."
246 msgid "Tags must be delimited by spaces. Text or image is required."
247 msgstr ""
247 msgstr ""
248 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
248 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
249
249
250 #: templates/boards/posting_general.html:254 templates/boards/thread.html:142
250 #: templates/boards/posting_general.html:254 templates/boards/thread.html:142
251 msgid "Text syntax"
251 msgid "Text syntax"
252 msgstr "Синтаксис текста"
252 msgstr "Синтаксис текста"
253
253
254 #: templates/boards/posting_general.html:264
254 #: templates/boards/posting_general.html:264
255 msgid "Pages:"
255 msgid "Pages:"
256 msgstr "Страницы: "
256 msgstr "Страницы: "
257
257
258 #: templates/boards/settings.html:14
258 #: templates/boards/settings.html:14
259 msgid "User:"
259 msgid "User:"
260 msgstr "Пользователь:"
260 msgstr "Пользователь:"
261
261
262 #: templates/boards/settings.html:16
262 #: templates/boards/settings.html:16
263 msgid "You are moderator."
263 msgid "You are moderator."
264 msgstr "Вы модератор."
264 msgstr "Вы модератор."
265
265
266 #: templates/boards/settings.html:19
266 #: templates/boards/settings.html:19
267 msgid "Posts:"
267 msgid "Posts:"
268 msgstr "Сообщений:"
268 msgstr "Сообщений:"
269
269
270 #: templates/boards/settings.html:20
270 #: templates/boards/settings.html:20
271 msgid "First access:"
271 msgid "First access:"
272 msgstr "Первый доступ:"
272 msgstr "Первый доступ:"
273
273
274 #: templates/boards/settings.html:22
274 #: templates/boards/settings.html:22
275 msgid "Last access:"
275 msgid "Last access:"
276 msgstr "Последний доступ: "
276 msgstr "Последний доступ: "
277
277
278 #: templates/boards/settings.html:31
278 #: templates/boards/settings.html:31
279 msgid "Save"
279 msgid "Save"
280 msgstr "Сохранить"
280 msgstr "Сохранить"
281
281
282 #: templates/boards/tags.html:24
282 #: templates/boards/tags.html:24
283 msgid "threads"
283 msgid "threads"
284 msgstr "тем"
284 msgstr "тем"
285
285
286 #: templates/boards/tags.html:37
286 #: templates/boards/tags.html:37
287 msgid "No tags found."
287 msgid "No tags found."
288 msgstr "Теги не найдены."
288 msgstr "Теги не найдены."
289
289
290 #: templates/boards/thread.html:24
290 #: templates/boards/thread.html:24
291 msgid "posts to bumplimit"
291 msgid "posts to bumplimit"
292 msgstr "сообщений до бамплимита"
292 msgstr "сообщений до бамплимита"
293
293
294 #: templates/boards/thread.html:98
294 #: templates/boards/thread.html:98
295 msgid "Reply to thread"
295 msgid "Reply to thread"
296 msgstr "Ответить в тему"
296 msgstr "Ответить в тему"
297
297
298 #: templates/boards/thread.html:156
298 #: templates/boards/thread.html:156
299 msgid "Last update: "
299 msgid "Last update: "
300 msgstr "Последнее обновление: "
300 msgstr "Последнее обновление: "
301
301
302 #: templates/boards/rss/post.html:5
302 #: templates/boards/rss/post.html:5
303 msgid "Post image"
303 msgid "Post image"
304 msgstr "Изображение сообщения"
304 msgstr "Изображение сообщения"
305
305
306 #: templates/boards/staticpages/banned.html:6
306 #: templates/boards/staticpages/banned.html:6
307 msgid "Banned"
307 msgid "Banned"
308 msgstr "Заблокирован"
308 msgstr "Заблокирован"
309
309
310 #: templates/boards/staticpages/banned.html:11
310 #: templates/boards/staticpages/banned.html:11
311 msgid "Your IP address has been banned. Contact the administrator"
311 msgid "Your IP address has been banned. Contact the administrator"
312 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
312 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
313
313
314 #: templates/boards/staticpages/help.html:6
314 #: templates/boards/staticpages/help.html:6
315 #: templates/boards/staticpages/help.html:10
315 #: templates/boards/staticpages/help.html:10
316 msgid "Syntax"
316 msgid "Syntax"
317 msgstr "Синтаксис"
317 msgstr "Синтаксис"
318
318
319 #: templates/boards/staticpages/help.html:11
319 #: templates/boards/staticpages/help.html:11
320 msgid "2 line breaks for a new line."
320 msgid "2 line breaks for a new line."
321 msgstr "2 перевода строки создают новый абзац."
321 msgstr "2 перевода строки создают новый абзац."
322
322
323 #: templates/boards/staticpages/help.html:12
323 #: templates/boards/staticpages/help.html:12
324 msgid "Italic text"
324 msgid "Italic text"
325 msgstr "Курсивный текст"
325 msgstr "Курсивный текст"
326
326
327 #: templates/boards/staticpages/help.html:13
327 #: templates/boards/staticpages/help.html:13
328 msgid "Bold text"
328 msgid "Bold text"
329 msgstr "Полужирный текст"
329 msgstr "Полужирный текст"
330
330
331 #: templates/boards/staticpages/help.html:14
331 #: templates/boards/staticpages/help.html:14
332 msgid "Spoiler"
332 msgid "Spoiler"
333 msgstr "Спойлер"
333 msgstr "Спойлер"
334
334
335 #: templates/boards/staticpages/help.html:15
335 #: templates/boards/staticpages/help.html:15
336 msgid "Comment"
336 msgid "Comment"
337 msgstr "Комментарий"
337 msgstr "Комментарий"
338
338
339 #: templates/boards/staticpages/help.html:16
339 #: templates/boards/staticpages/help.html:16
340 msgid "Quote"
340 msgid "Quote"
341 msgstr "Цитата"
341 msgstr "Цитата"
342
342
343 #: templates/boards/staticpages/help.html:17
343 #: templates/boards/staticpages/help.html:17
344 msgid "Link to a post"
344 msgid "Link to a post"
345 msgstr "Ссылка на сообщение"
345 msgstr "Ссылка на сообщение"
346
346
347 #: templates/boards/staticpages/help.html:18
347 #: templates/boards/staticpages/help.html:18
348 msgid "Strikethrough text"
348 msgid "Strikethrough text"
349 msgstr "Зачеркнутый текст"
349 msgstr "Зачеркнутый текст"
350
350
351 #~ msgid "Tag: "
351 #~ msgid "Tag: "
352 #~ msgstr "Тег: "
352 #~ msgstr "Тег: "
353
353
354 #~ msgid "Remove"
354 #~ msgid "Remove"
355 #~ msgstr "Удалить"
355 #~ msgstr "Удалить"
356
356
357 #~ msgid "Add"
357 #~ msgid "Add"
358 #~ msgstr "Добавить"
358 #~ msgstr "Добавить"
359
359
360 #~ msgid "Basic markdown syntax."
360 #~ msgid "Basic markdown syntax."
361 #~ msgstr "Базовый синтаксис markdown."
361 #~ msgstr "Базовый синтаксис markdown."
362
362
363 #~ msgid "Example: "
363 #~ msgid "Example: "
364 #~ msgstr "Пример: "
364 #~ msgstr "Пример: "
365
365
366 #~ msgid "tags"
366 #~ msgid "tags"
367 #~ msgstr "тегов"
367 #~ msgstr "тегов"
368
368
369 #~ msgid "Get!"
369 #~ msgid "Get!"
370 #~ msgstr "Гет!"
370 #~ msgstr "Гет!"
371
371
372 #~ msgid "View"
372 #~ msgid "View"
373 #~ msgstr "Просмотр"
373 #~ msgstr "Просмотр"
374
374
375 #~ msgid "gets"
375 #~ msgid "gets"
376 #~ msgstr "гетов"
376 #~ msgstr "гетов"
@@ -1,59 +1,62 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3 {% load l10n %}
3 {% load static from staticfiles %}
4 {% load static from staticfiles %}
4
5
5 <!DOCTYPE html>
6 <!DOCTYPE html>
6 <html>
7 <html>
7 <head>
8 <head>
8 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css"
9 href="{% static 'css/base.css' %}" media="all"/>
10 href="{% static 'css/base.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css"
11 <link rel="stylesheet" type="text/css"
11 href="{% static theme_css %}" media="all"/>
12 href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title=
13 <link rel="alternate" type="application/rss+xml" href="rss/" title=
13 "{% trans 'Feed' %}"/>
14 "{% trans 'Feed' %}"/>
14
15
15 <link rel="icon" type="image/png"
16 <link rel="icon" type="image/png"
16 href="{% static 'favicon.png' %}">
17 href="{% static 'favicon.png' %}">
17
18
18 <meta name="viewport" content="width=device-width, initial-scale=1"/>
19 <meta name="viewport" content="width=device-width, initial-scale=1"/>
19 <meta charset="utf-8"/>
20 <meta charset="utf-8"/>
20
21
21 {% block head %}{% endblock %}
22 {% block head %}{% endblock %}
22 </head>
23 </head>
23 <body>
24 <body>
24 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
25 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
25 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
26 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
26 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
27 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
27 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
28 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
28 <script src="{% static 'js/panel.js' %}"></script>
29 <script src="{% static 'js/panel.js' %}"></script>
29 <script src="{% static 'js/popup.js' %}"></script>
30 <script src="{% static 'js/popup.js' %}"></script>
30 <script src="{% static 'js/image.js' %}"></script>
31 <script src="{% static 'js/image.js' %}"></script>
31 <script src="{% static 'js/refpopup.js' %}"></script>
32 <script src="{% static 'js/refpopup.js' %}"></script>
32 <script src="{% static 'js/main.js' %}"></script>
33 <script src="{% static 'js/main.js' %}"></script>
33
34
34 <div class="navigation_panel">
35 <div class="navigation_panel">
35 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
36 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
36 {% for tag in tags %}
37 {% for tag in tags %}
37 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
38 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
38 >#{{ tag.name }}</a>,
39 >#{{ tag.name }}</a>,
39 {% endfor %}
40 {% endfor %}
40 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
41 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
41 >[...]</a>
42 >[...]</a>
42 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
43 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
43 </div>
44 </div>
44
45
45 {% block content %}{% endblock %}
46 {% block content %}{% endblock %}
46
47
47 <div class="navigation_panel">
48 <div class="navigation_panel">
48 {% block metapanel %}{% endblock %}
49 {% block metapanel %}{% endblock %}
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 {% blocktrans %}Speed: {{ posts_per_day }} posts per day{% endblocktrans %}
51 {% with ppd=posts_per_day|floatformat:2 %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 {% endwith %}
51 <a class="link" href="#top">{% trans 'Up' %}</a>
54 <a class="link" href="#top">{% trans 'Up' %}</a>
52 </div>
55 </div>
53
56
54 <div class="footer">
57 <div class="footer">
55 <!-- Put your banners here -->
58 <!-- Put your banners here -->
56 </div>
59 </div>
57
60
58 </body>
61 </body>
59 </html>
62 </html>
@@ -1,566 +1,566 b''
1 import hashlib
1 import hashlib
2 import json
2 import json
3 import string
3 import string
4 import time
4 import time
5 from datetime import datetime
5 from datetime import datetime
6 import re
6 import re
7
7
8 from django.core import serializers
8 from django.core import serializers
9 from django.core.urlresolvers import reverse
9 from django.core.urlresolvers import reverse
10 from django.http import HttpResponseRedirect
10 from django.http import HttpResponseRedirect
11 from django.http.response import HttpResponse
11 from django.http.response import HttpResponse
12 from django.template import RequestContext
12 from django.template import RequestContext
13 from django.shortcuts import render, redirect, get_object_or_404
13 from django.shortcuts import render, redirect, get_object_or_404
14 from django.utils import timezone
14 from django.utils import timezone
15 from django.db import transaction
15 from django.db import transaction
16
16
17 from boards import forms
17 from boards import forms
18 import boards
18 import boards
19 from boards import utils
19 from boards import utils
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
22 from boards.models import Post, Tag, Ban, User
22 from boards.models import Post, Tag, Ban, User
23 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
23 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
24 from boards.models.user import RANK_USER
24 from boards.models.user import RANK_USER
25 from boards import authors
25 from boards import authors
26 from boards.utils import get_client_ip
26 from boards.utils import get_client_ip
27 import neboard
27 import neboard
28
28
29
29
30 BAN_REASON_SPAM = 'Autoban: spam bot'
30 BAN_REASON_SPAM = 'Autoban: spam bot'
31
31
32
32
33 def index(request, page=0):
33 def index(request, page=0):
34 context = _init_default_context(request)
34 context = _init_default_context(request)
35
35
36 if utils.need_include_captcha(request):
36 if utils.need_include_captcha(request):
37 threadFormClass = ThreadCaptchaForm
37 threadFormClass = ThreadCaptchaForm
38 kwargs = {'request': request}
38 kwargs = {'request': request}
39 else:
39 else:
40 threadFormClass = ThreadForm
40 threadFormClass = ThreadForm
41 kwargs = {}
41 kwargs = {}
42
42
43 if request.method == 'POST':
43 if request.method == 'POST':
44 form = threadFormClass(request.POST, request.FILES,
44 form = threadFormClass(request.POST, request.FILES,
45 error_class=PlainErrorList, **kwargs)
45 error_class=PlainErrorList, **kwargs)
46 form.session = request.session
46 form.session = request.session
47
47
48 if form.is_valid():
48 if form.is_valid():
49 return _new_post(request, form)
49 return _new_post(request, form)
50 if form.need_to_ban:
50 if form.need_to_ban:
51 # Ban user because he is suspected to be a bot
51 # Ban user because he is suspected to be a bot
52 _ban_current_user(request)
52 _ban_current_user(request)
53 else:
53 else:
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
55
55
56 threads = []
56 threads = []
57 for thread_to_show in Post.objects.get_threads(page=int(page)):
57 for thread_to_show in Post.objects.get_threads(page=int(page)):
58 threads.append({
58 threads.append({
59 'thread': thread_to_show,
59 'thread': thread_to_show,
60 'op': thread_to_show.get_replies()[0],
60 'op': thread_to_show.get_replies()[0],
61 'bumpable': thread_to_show.can_bump(),
61 'bumpable': thread_to_show.can_bump(),
62 'last_replies': thread_to_show.get_last_replies(),
62 'last_replies': thread_to_show.get_last_replies(),
63 })
63 })
64
64
65 # TODO Make this generic for tag and threads list pages
65 # TODO Make this generic for tag and threads list pages
66 context['threads'] = None if len(threads) == 0 else threads
66 context['threads'] = None if len(threads) == 0 else threads
67 context['form'] = form
67 context['form'] = form
68
68
69 page_count = Post.objects.get_thread_page_count()
69 page_count = Post.objects.get_thread_page_count()
70 context['pages'] = range(page_count)
70 context['pages'] = range(page_count)
71 page = int(page)
71 page = int(page)
72 if page < page_count - 1:
72 if page < page_count - 1:
73 context['next_page'] = str(page + 1)
73 context['next_page'] = str(page + 1)
74 if page > 0:
74 if page > 0:
75 context['prev_page'] = str(page - 1)
75 context['prev_page'] = str(page - 1)
76
76
77 return render(request, 'boards/posting_general.html',
77 return render(request, 'boards/posting_general.html',
78 context)
78 context)
79
79
80
80
81 @transaction.atomic
81 @transaction.atomic
82 def _new_post(request, form, opening_post=None):
82 def _new_post(request, form, opening_post=None):
83 """Add a new post (in thread or as a reply)."""
83 """Add a new post (in thread or as a reply)."""
84
84
85 ip = get_client_ip(request)
85 ip = get_client_ip(request)
86 is_banned = Ban.objects.filter(ip=ip).exists()
86 is_banned = Ban.objects.filter(ip=ip).exists()
87
87
88 if is_banned:
88 if is_banned:
89 return redirect(you_are_banned)
89 return redirect(you_are_banned)
90
90
91 data = form.cleaned_data
91 data = form.cleaned_data
92
92
93 title = data['title']
93 title = data['title']
94 text = data['text']
94 text = data['text']
95
95
96 text = _remove_invalid_links(text)
96 text = _remove_invalid_links(text)
97
97
98 if 'image' in data.keys():
98 if 'image' in data.keys():
99 image = data['image']
99 image = data['image']
100 else:
100 else:
101 image = None
101 image = None
102
102
103 tags = []
103 tags = []
104
104
105 if not opening_post:
105 if not opening_post:
106 tag_strings = data['tags']
106 tag_strings = data['tags']
107
107
108 if tag_strings:
108 if tag_strings:
109 tag_strings = tag_strings.split(' ')
109 tag_strings = tag_strings.split(' ')
110 for tag_name in tag_strings:
110 for tag_name in tag_strings:
111 tag_name = string.lower(tag_name.strip())
111 tag_name = string.lower(tag_name.strip())
112 if len(tag_name) > 0:
112 if len(tag_name) > 0:
113 tag, created = Tag.objects.get_or_create(name=tag_name)
113 tag, created = Tag.objects.get_or_create(name=tag_name)
114 tags.append(tag)
114 tags.append(tag)
115 post_thread = None
115 post_thread = None
116 else:
116 else:
117 post_thread = opening_post.thread_new
117 post_thread = opening_post.thread_new
118
118
119 post = Post.objects.create_post(title=title, text=text, ip=ip,
119 post = Post.objects.create_post(title=title, text=text, ip=ip,
120 thread=post_thread, image=image,
120 thread=post_thread, image=image,
121 tags=tags, user=_get_user(request))
121 tags=tags, user=_get_user(request))
122
122
123 thread_to_show = (opening_post.id if opening_post else post.id)
123 thread_to_show = (opening_post.id if opening_post else post.id)
124
124
125 if opening_post:
125 if opening_post:
126 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
126 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
127 '#' + str(post.id))
127 '#' + str(post.id))
128 else:
128 else:
129 return redirect(thread, post_id=thread_to_show)
129 return redirect(thread, post_id=thread_to_show)
130
130
131
131
132 def tag(request, tag_name, page=0):
132 def tag(request, tag_name, page=0):
133 """
133 """
134 Get all tag threads. Threads are split in pages, so some page is
134 Get all tag threads. Threads are split in pages, so some page is
135 requested. Default page is 0.
135 requested. Default page is 0.
136 """
136 """
137
137
138 tag = get_object_or_404(Tag, name=tag_name)
138 tag = get_object_or_404(Tag, name=tag_name)
139 threads = []
139 threads = []
140 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
140 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
141 threads.append({
141 threads.append({
142 'thread': thread_to_show,
142 'thread': thread_to_show,
143 'op': thread_to_show.get_replies()[0],
143 'op': thread_to_show.get_replies()[0],
144 'bumpable': thread_to_show.can_bump(),
144 'bumpable': thread_to_show.can_bump(),
145 'last_replies': thread_to_show.get_last_replies(),
145 'last_replies': thread_to_show.get_last_replies(),
146 })
146 })
147
147
148 if request.method == 'POST':
148 if request.method == 'POST':
149 form = ThreadForm(request.POST, request.FILES,
149 form = ThreadForm(request.POST, request.FILES,
150 error_class=PlainErrorList)
150 error_class=PlainErrorList)
151 form.session = request.session
151 form.session = request.session
152
152
153 if form.is_valid():
153 if form.is_valid():
154 return _new_post(request, form)
154 return _new_post(request, form)
155 if form.need_to_ban:
155 if form.need_to_ban:
156 # Ban user because he is suspected to be a bot
156 # Ban user because he is suspected to be a bot
157 _ban_current_user(request)
157 _ban_current_user(request)
158 else:
158 else:
159 form = forms.ThreadForm(initial={'tags': tag_name},
159 form = forms.ThreadForm(initial={'tags': tag_name},
160 error_class=PlainErrorList)
160 error_class=PlainErrorList)
161
161
162 context = _init_default_context(request)
162 context = _init_default_context(request)
163 context['threads'] = None if len(threads) == 0 else threads
163 context['threads'] = None if len(threads) == 0 else threads
164 context['tag'] = tag
164 context['tag'] = tag
165
165
166 page_count = Post.objects.get_thread_page_count(tag=tag)
166 page_count = Post.objects.get_thread_page_count(tag=tag)
167 context['pages'] = range(page_count)
167 context['pages'] = range(page_count)
168 page = int(page)
168 page = int(page)
169 if page < page_count - 1:
169 if page < page_count - 1:
170 context['next_page'] = str(page + 1)
170 context['next_page'] = str(page + 1)
171 if page > 0:
171 if page > 0:
172 context['prev_page'] = str(page - 1)
172 context['prev_page'] = str(page - 1)
173
173
174 context['form'] = form
174 context['form'] = form
175
175
176 return render(request, 'boards/posting_general.html',
176 return render(request, 'boards/posting_general.html',
177 context)
177 context)
178
178
179
179
180 def thread(request, post_id):
180 def thread(request, post_id):
181 """Get all thread posts"""
181 """Get all thread posts"""
182
182
183 if utils.need_include_captcha(request):
183 if utils.need_include_captcha(request):
184 postFormClass = PostCaptchaForm
184 postFormClass = PostCaptchaForm
185 kwargs = {'request': request}
185 kwargs = {'request': request}
186 else:
186 else:
187 postFormClass = PostForm
187 postFormClass = PostForm
188 kwargs = {}
188 kwargs = {}
189
189
190 if request.method == 'POST':
190 if request.method == 'POST':
191 form = postFormClass(request.POST, request.FILES,
191 form = postFormClass(request.POST, request.FILES,
192 error_class=PlainErrorList, **kwargs)
192 error_class=PlainErrorList, **kwargs)
193 form.session = request.session
193 form.session = request.session
194
194
195 opening_post = get_object_or_404(Post, id=post_id)
195 opening_post = get_object_or_404(Post, id=post_id)
196 if form.is_valid():
196 if form.is_valid():
197 return _new_post(request, form, opening_post)
197 return _new_post(request, form, opening_post)
198 if form.need_to_ban:
198 if form.need_to_ban:
199 # Ban user because he is suspected to be a bot
199 # Ban user because he is suspected to be a bot
200 _ban_current_user(request)
200 _ban_current_user(request)
201 else:
201 else:
202 form = postFormClass(error_class=PlainErrorList, **kwargs)
202 form = postFormClass(error_class=PlainErrorList, **kwargs)
203
203
204 thread_to_show = get_object_or_404(Post, id=post_id).thread_new
204 thread_to_show = get_object_or_404(Post, id=post_id).thread_new
205
205
206 context = _init_default_context(request)
206 context = _init_default_context(request)
207
207
208 posts = thread_to_show.get_replies()
208 posts = thread_to_show.get_replies()
209 context['form'] = form
209 context['form'] = form
210 context['bumpable'] = thread_to_show.can_bump()
210 context['bumpable'] = thread_to_show.can_bump()
211 if context['bumpable']:
211 if context['bumpable']:
212 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\
212 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\
213 .count()
213 .count()
214 context['bumplimit_progress'] = str(
214 context['bumplimit_progress'] = str(
215 float(context['posts_left']) /
215 float(context['posts_left']) /
216 neboard.settings.MAX_POSTS_PER_THREAD * 100)
216 neboard.settings.MAX_POSTS_PER_THREAD * 100)
217 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
217 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
218 context["thread"] = thread_to_show
218 context["thread"] = thread_to_show
219
219
220 return render(request, 'boards/thread.html', context)
220 return render(request, 'boards/thread.html', context)
221
221
222
222
223 def login(request):
223 def login(request):
224 """Log in with user id"""
224 """Log in with user id"""
225
225
226 context = _init_default_context(request)
226 context = _init_default_context(request)
227
227
228 if request.method == 'POST':
228 if request.method == 'POST':
229 form = LoginForm(request.POST, request.FILES,
229 form = LoginForm(request.POST, request.FILES,
230 error_class=PlainErrorList)
230 error_class=PlainErrorList)
231 form.session = request.session
231 form.session = request.session
232
232
233 if form.is_valid():
233 if form.is_valid():
234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
235 request.session['user_id'] = user.id
235 request.session['user_id'] = user.id
236 return redirect(index)
236 return redirect(index)
237
237
238 else:
238 else:
239 form = LoginForm()
239 form = LoginForm()
240
240
241 context['form'] = form
241 context['form'] = form
242
242
243 return render(request, 'boards/login.html', context)
243 return render(request, 'boards/login.html', context)
244
244
245
245
246 def settings(request):
246 def settings(request):
247 """User's settings"""
247 """User's settings"""
248
248
249 context = _init_default_context(request)
249 context = _init_default_context(request)
250 user = _get_user(request)
250 user = _get_user(request)
251 is_moderator = user.is_moderator()
251 is_moderator = user.is_moderator()
252
252
253 if request.method == 'POST':
253 if request.method == 'POST':
254 with transaction.commit_on_success():
254 with transaction.commit_on_success():
255 if is_moderator:
255 if is_moderator:
256 form = ModeratorSettingsForm(request.POST,
256 form = ModeratorSettingsForm(request.POST,
257 error_class=PlainErrorList)
257 error_class=PlainErrorList)
258 else:
258 else:
259 form = SettingsForm(request.POST, error_class=PlainErrorList)
259 form = SettingsForm(request.POST, error_class=PlainErrorList)
260
260
261 if form.is_valid():
261 if form.is_valid():
262 selected_theme = form.cleaned_data['theme']
262 selected_theme = form.cleaned_data['theme']
263
263
264 user.save_setting('theme', selected_theme)
264 user.save_setting('theme', selected_theme)
265
265
266 if is_moderator:
266 if is_moderator:
267 moderate = form.cleaned_data['moderate']
267 moderate = form.cleaned_data['moderate']
268 user.save_setting(SETTING_MODERATE, moderate)
268 user.save_setting(SETTING_MODERATE, moderate)
269
269
270 return redirect(settings)
270 return redirect(settings)
271 else:
271 else:
272 selected_theme = _get_theme(request)
272 selected_theme = _get_theme(request)
273
273
274 if is_moderator:
274 if is_moderator:
275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
276 'moderate': context['moderator']},
276 'moderate': context['moderator']},
277 error_class=PlainErrorList)
277 error_class=PlainErrorList)
278 else:
278 else:
279 form = SettingsForm(initial={'theme': selected_theme},
279 form = SettingsForm(initial={'theme': selected_theme},
280 error_class=PlainErrorList)
280 error_class=PlainErrorList)
281
281
282 context['form'] = form
282 context['form'] = form
283
283
284 return render(request, 'boards/settings.html', context)
284 return render(request, 'boards/settings.html', context)
285
285
286
286
287 def all_tags(request):
287 def all_tags(request):
288 """All tags list"""
288 """All tags list"""
289
289
290 context = _init_default_context(request)
290 context = _init_default_context(request)
291 context['all_tags'] = Tag.objects.get_not_empty_tags()
291 context['all_tags'] = Tag.objects.get_not_empty_tags()
292
292
293 return render(request, 'boards/tags.html', context)
293 return render(request, 'boards/tags.html', context)
294
294
295
295
296 def jump_to_post(request, post_id):
296 def jump_to_post(request, post_id):
297 """Determine thread in which the requested post is and open it's page"""
297 """Determine thread in which the requested post is and open it's page"""
298
298
299 post = get_object_or_404(Post, id=post_id)
299 post = get_object_or_404(Post, id=post_id)
300
300
301 if not post.thread:
301 if not post.thread:
302 return redirect(thread, post_id=post.id)
302 return redirect(thread, post_id=post.id)
303 else:
303 else:
304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
305 + '#' + str(post.id))
305 + '#' + str(post.id))
306
306
307
307
308 def authors(request):
308 def authors(request):
309 """Show authors list"""
309 """Show authors list"""
310
310
311 context = _init_default_context(request)
311 context = _init_default_context(request)
312 context['authors'] = boards.authors.authors
312 context['authors'] = boards.authors.authors
313
313
314 return render(request, 'boards/authors.html', context)
314 return render(request, 'boards/authors.html', context)
315
315
316
316
317 @transaction.atomic
317 @transaction.atomic
318 def delete(request, post_id):
318 def delete(request, post_id):
319 """Delete post"""
319 """Delete post"""
320
320
321 user = _get_user(request)
321 user = _get_user(request)
322 post = get_object_or_404(Post, id=post_id)
322 post = get_object_or_404(Post, id=post_id)
323
323
324 if user.is_moderator():
324 if user.is_moderator():
325 # TODO Show confirmation page before deletion
325 # TODO Show confirmation page before deletion
326 Post.objects.delete_post(post)
326 Post.objects.delete_post(post)
327
327
328 if not post.thread:
328 if not post.thread:
329 return _redirect_to_next(request)
329 return _redirect_to_next(request)
330 else:
330 else:
331 return redirect(thread, post_id=post.thread.id)
331 return redirect(thread, post_id=post.thread.id)
332
332
333
333
334 @transaction.atomic
334 @transaction.atomic
335 def ban(request, post_id):
335 def ban(request, post_id):
336 """Ban user"""
336 """Ban user"""
337
337
338 user = _get_user(request)
338 user = _get_user(request)
339 post = get_object_or_404(Post, id=post_id)
339 post = get_object_or_404(Post, id=post_id)
340
340
341 if user.is_moderator():
341 if user.is_moderator():
342 # TODO Show confirmation page before ban
342 # TODO Show confirmation page before ban
343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
344 if created:
344 if created:
345 ban.reason = 'Banned for post ' + str(post_id)
345 ban.reason = 'Banned for post ' + str(post_id)
346 ban.save()
346 ban.save()
347
347
348 return _redirect_to_next(request)
348 return _redirect_to_next(request)
349
349
350
350
351 def you_are_banned(request):
351 def you_are_banned(request):
352 """Show the page that notifies that user is banned"""
352 """Show the page that notifies that user is banned"""
353
353
354 context = _init_default_context(request)
354 context = _init_default_context(request)
355
355
356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
357 context['ban_reason'] = ban.reason
357 context['ban_reason'] = ban.reason
358 return render(request, 'boards/staticpages/banned.html', context)
358 return render(request, 'boards/staticpages/banned.html', context)
359
359
360
360
361 def page_404(request):
361 def page_404(request):
362 """Show page 404 (not found error)"""
362 """Show page 404 (not found error)"""
363
363
364 context = _init_default_context(request)
364 context = _init_default_context(request)
365 return render(request, 'boards/404.html', context)
365 return render(request, 'boards/404.html', context)
366
366
367
367
368 @transaction.atomic
368 @transaction.atomic
369 def tag_subscribe(request, tag_name):
369 def tag_subscribe(request, tag_name):
370 """Add tag to favorites"""
370 """Add tag to favorites"""
371
371
372 user = _get_user(request)
372 user = _get_user(request)
373 tag = get_object_or_404(Tag, name=tag_name)
373 tag = get_object_or_404(Tag, name=tag_name)
374
374
375 if not tag in user.fav_tags.all():
375 if not tag in user.fav_tags.all():
376 user.add_tag(tag)
376 user.add_tag(tag)
377
377
378 return _redirect_to_next(request)
378 return _redirect_to_next(request)
379
379
380
380
381 @transaction.atomic
381 @transaction.atomic
382 def tag_unsubscribe(request, tag_name):
382 def tag_unsubscribe(request, tag_name):
383 """Remove tag from favorites"""
383 """Remove tag from favorites"""
384
384
385 user = _get_user(request)
385 user = _get_user(request)
386 tag = get_object_or_404(Tag, name=tag_name)
386 tag = get_object_or_404(Tag, name=tag_name)
387
387
388 if tag in user.fav_tags.all():
388 if tag in user.fav_tags.all():
389 user.remove_tag(tag)
389 user.remove_tag(tag)
390
390
391 return _redirect_to_next(request)
391 return _redirect_to_next(request)
392
392
393
393
394 def static_page(request, name):
394 def static_page(request, name):
395 """Show a static page that needs only tags list and a CSS"""
395 """Show a static page that needs only tags list and a CSS"""
396
396
397 context = _init_default_context(request)
397 context = _init_default_context(request)
398 return render(request, 'boards/staticpages/' + name + '.html', context)
398 return render(request, 'boards/staticpages/' + name + '.html', context)
399
399
400
400
401 def api_get_post(request, post_id):
401 def api_get_post(request, post_id):
402 """
402 """
403 Get the JSON of a post. This can be
403 Get the JSON of a post. This can be
404 used as and API for external clients.
404 used as and API for external clients.
405 """
405 """
406
406
407 post = get_object_or_404(Post, id=post_id)
407 post = get_object_or_404(Post, id=post_id)
408
408
409 json = serializers.serialize("json", [post], fields=(
409 json = serializers.serialize("json", [post], fields=(
410 "pub_time", "_text_rendered", "title", "text", "image",
410 "pub_time", "_text_rendered", "title", "text", "image",
411 "image_width", "image_height", "replies", "tags"
411 "image_width", "image_height", "replies", "tags"
412 ))
412 ))
413
413
414 return HttpResponse(content=json)
414 return HttpResponse(content=json)
415
415
416
416
417 @transaction.atomic
417 @transaction.atomic
418 def api_get_threaddiff(request, thread_id, last_update_time):
418 def api_get_threaddiff(request, thread_id, last_update_time):
419 """Get posts that were changed or added since time"""
419 """Get posts that were changed or added since time"""
420
420
421 thread = get_object_or_404(Post, id=thread_id).thread_new
421 thread = get_object_or_404(Post, id=thread_id).thread_new
422
422
423 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
423 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
424 timezone.get_current_timezone())
424 timezone.get_current_timezone())
425
425
426 json_data = {
426 json_data = {
427 'added': [],
427 'added': [],
428 'updated': [],
428 'updated': [],
429 'last_update': None,
429 'last_update': None,
430 }
430 }
431 added_posts = Post.objects.filter(thread_new=thread,
431 added_posts = Post.objects.filter(thread_new=thread,
432 pub_time__gt=filter_time)\
432 pub_time__gt=filter_time)\
433 .order_by('pub_time')
433 .order_by('pub_time')
434 updated_posts = Post.objects.filter(thread_new=thread,
434 updated_posts = Post.objects.filter(thread_new=thread,
435 pub_time__lte=filter_time,
435 pub_time__lte=filter_time,
436 last_edit_time__gt=filter_time)
436 last_edit_time__gt=filter_time)
437 for post in added_posts:
437 for post in added_posts:
438 json_data['added'].append(get_post(request, post.id).content.strip())
438 json_data['added'].append(get_post(request, post.id).content.strip())
439 for post in updated_posts:
439 for post in updated_posts:
440 json_data['updated'].append(get_post(request, post.id).content.strip())
440 json_data['updated'].append(get_post(request, post.id).content.strip())
441 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
441 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
442
442
443 return HttpResponse(content=json.dumps(json_data))
443 return HttpResponse(content=json.dumps(json_data))
444
444
445
445
446 def get_post(request, post_id):
446 def get_post(request, post_id):
447 """Get the html of a post. Used for popups."""
447 """Get the html of a post. Used for popups."""
448
448
449 post = get_object_or_404(Post, id=post_id)
449 post = get_object_or_404(Post, id=post_id)
450 thread = post.thread_new
450 thread = post.thread_new
451
451
452 context = RequestContext(request)
452 context = RequestContext(request)
453 context["post"] = post
453 context["post"] = post
454 context["can_bump"] = thread.can_bump()
454 context["can_bump"] = thread.can_bump()
455 if "truncated" in request.GET:
455 if "truncated" in request.GET:
456 context["truncated"] = True
456 context["truncated"] = True
457
457
458 return render(request, 'boards/post.html', context)
458 return render(request, 'boards/post.html', context)
459
459
460
460
461 def _get_theme(request, user=None):
461 def _get_theme(request, user=None):
462 """Get user's CSS theme"""
462 """Get user's CSS theme"""
463
463
464 if not user:
464 if not user:
465 user = _get_user(request)
465 user = _get_user(request)
466 theme = user.get_setting('theme')
466 theme = user.get_setting('theme')
467 if not theme:
467 if not theme:
468 theme = neboard.settings.DEFAULT_THEME
468 theme = neboard.settings.DEFAULT_THEME
469
469
470 return theme
470 return theme
471
471
472
472
473 def _init_default_context(request):
473 def _init_default_context(request):
474 """Create context with default values that are used in most views"""
474 """Create context with default values that are used in most views"""
475
475
476 context = RequestContext(request)
476 context = RequestContext(request)
477
477
478 user = _get_user(request)
478 user = _get_user(request)
479 context['user'] = user
479 context['user'] = user
480 context['tags'] = user.get_sorted_fav_tags()
480 context['tags'] = user.get_sorted_fav_tags()
481 context['posts_per_day'] = Post.objects.get_posts_per_day()
481 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
482
482
483 theme = _get_theme(request, user)
483 theme = _get_theme(request, user)
484 context['theme'] = theme
484 context['theme'] = theme
485 context['theme_css'] = 'css/' + theme + '/base_page.css'
485 context['theme_css'] = 'css/' + theme + '/base_page.css'
486
486
487 # This shows the moderator panel
487 # This shows the moderator panel
488 moderate = user.get_setting(SETTING_MODERATE)
488 moderate = user.get_setting(SETTING_MODERATE)
489 if moderate == 'True':
489 if moderate == 'True':
490 context['moderator'] = user.is_moderator()
490 context['moderator'] = user.is_moderator()
491 else:
491 else:
492 context['moderator'] = False
492 context['moderator'] = False
493
493
494 return context
494 return context
495
495
496
496
497 def _get_user(request):
497 def _get_user(request):
498 """
498 """
499 Get current user from the session. If the user does not exist, create
499 Get current user from the session. If the user does not exist, create
500 a new one.
500 a new one.
501 """
501 """
502
502
503 session = request.session
503 session = request.session
504 if not 'user_id' in session:
504 if not 'user_id' in session:
505 request.session.save()
505 request.session.save()
506
506
507 md5 = hashlib.md5()
507 md5 = hashlib.md5()
508 md5.update(session.session_key)
508 md5.update(session.session_key)
509 new_id = md5.hexdigest()
509 new_id = md5.hexdigest()
510
510
511 time_now = timezone.now()
511 time_now = timezone.now()
512 user = User.objects.create(user_id=new_id, rank=RANK_USER,
512 user = User.objects.create(user_id=new_id, rank=RANK_USER,
513 registration_time=time_now)
513 registration_time=time_now)
514
514
515 session['user_id'] = user.id
515 session['user_id'] = user.id
516 else:
516 else:
517 user = User.objects.get(id=session['user_id'])
517 user = User.objects.get(id=session['user_id'])
518
518
519 return user
519 return user
520
520
521
521
522 def _redirect_to_next(request):
522 def _redirect_to_next(request):
523 """
523 """
524 If a 'next' parameter was specified, redirect to the next page. This is
524 If a 'next' parameter was specified, redirect to the next page. This is
525 used when the user is required to return to some page after the current
525 used when the user is required to return to some page after the current
526 view has finished its work.
526 view has finished its work.
527 """
527 """
528
528
529 if 'next' in request.GET:
529 if 'next' in request.GET:
530 next_page = request.GET['next']
530 next_page = request.GET['next']
531 return HttpResponseRedirect(next_page)
531 return HttpResponseRedirect(next_page)
532 else:
532 else:
533 return redirect(index)
533 return redirect(index)
534
534
535
535
536 @transaction.atomic
536 @transaction.atomic
537 def _ban_current_user(request):
537 def _ban_current_user(request):
538 """Add current user to the IP ban list"""
538 """Add current user to the IP ban list"""
539
539
540 ip = utils.get_client_ip(request)
540 ip = utils.get_client_ip(request)
541 ban, created = Ban.objects.get_or_create(ip=ip)
541 ban, created = Ban.objects.get_or_create(ip=ip)
542 if created:
542 if created:
543 ban.can_read = False
543 ban.can_read = False
544 ban.reason = BAN_REASON_SPAM
544 ban.reason = BAN_REASON_SPAM
545 ban.save()
545 ban.save()
546
546
547
547
548 def _remove_invalid_links(text):
548 def _remove_invalid_links(text):
549 """
549 """
550 Replace invalid links in posts so that they won't be parsed.
550 Replace invalid links in posts so that they won't be parsed.
551 Invalid links are links to non-existent posts
551 Invalid links are links to non-existent posts
552 """
552 """
553
553
554 for reply_number in re.finditer(REGEX_REPLY, text):
554 for reply_number in re.finditer(REGEX_REPLY, text):
555 post_id = reply_number.group(1)
555 post_id = reply_number.group(1)
556 post = Post.objects.filter(id=post_id)
556 post = Post.objects.filter(id=post_id)
557 if not post.exists():
557 if not post.exists():
558 text = string.replace(text, '>>' + post_id, post_id)
558 text = string.replace(text, '>>' + post_id, post_id)
559
559
560 return text
560 return text
561
561
562
562
563 def _datetime_to_epoch(datetime):
563 def _datetime_to_epoch(datetime):
564 return int(time.mktime(timezone.localtime(
564 return int(time.mktime(timezone.localtime(
565 datetime,timezone.get_current_timezone()).timetuple())
565 datetime,timezone.get_current_timezone()).timetuple())
566 * 1000000 + datetime.microsecond)
566 * 1000000 + datetime.microsecond)
General Comments 0
You need to be logged in to leave comments. Login now