##// END OF EJS Templates
Show posts per
neko259 -
r407:33667f74 default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,371 +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-09 21:41+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:47 templates/boards/posting_general.html:184
37 #: forms.py:48 templates/boards/posting_general.html:206
38 #: templates/boards/thread.html:103
38 #: templates/boards/thread.html:101
39 msgid "Title"
39 msgid "Title"
40 msgstr "Заголовок"
40 msgstr "Заголовок"
41
41
42 #: forms.py:49 templates/boards/posting_general.html:199
42 #: forms.py:50 templates/boards/posting_general.html:221
43 #: templates/boards/thread.html:118
43 #: templates/boards/thread.html:116
44 msgid "Text"
44 msgid "Text"
45 msgstr "Текст"
45 msgstr "Текст"
46
46
47 #: forms.py:50 templates/boards/posting_general.html:204
47 #: forms.py:51 templates/boards/posting_general.html:226
48 #: templates/boards/thread.html:123
48 #: templates/boards/thread.html:121
49 msgid "Image"
49 msgid "Image"
50 msgstr "Изображение"
50 msgstr "Изображение"
51
51
52 #: forms.py:53 templates/boards/posting_general.html:214
52 #: forms.py:54 templates/boards/posting_general.html:236
53 #: templates/boards/thread.html:128
53 #: templates/boards/thread.html:126
54 msgid "e-mail"
54 msgid "e-mail"
55 msgstr ""
55 msgstr ""
56
56
57 #: forms.py:64
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:73
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:84
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:111
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:124
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:138 templates/boards/post.html:39
81 #: forms.py:139 templates/boards/post.html:60
82 #: templates/boards/posting_general.html:209 templates/boards/tags.html:7
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:146
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:174 forms.py:195
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:201
95 #: forms.py:202
96 msgid "Theme"
96 msgid "Theme"
97 msgstr "Тема"
97 msgstr "Тема"
98
98
99 #: forms.py:206
99 #: forms.py:207
100 msgid "Enable moderation panel"
100 msgid "Enable moderation panel"
101 msgstr "Включить панель модерации"
101 msgstr "Включить панель модерации"
102
102
103 #: forms.py:221
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:235
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:34
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:39
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:41
148 #: templates/boards/base.html:42
149 msgid "Settings"
149 msgid "Settings"
150 msgstr "Настройки"
150 msgstr "Настройки"
151
151
152 #: templates/boards/base.html:48 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:49
157 #: templates/boards/base.html:50
158 #, python-format
159 msgid "Speed: %(posts_per_day)s posts per day"
160 msgstr "Скорость: %(posts_per_day)s сообщений в день"
161
162 #: templates/boards/base.html:51
158 msgid "Up"
163 msgid "Up"
159 msgstr "Вверх"
164 msgstr "Вверх"
160
165
161 #: templates/boards/login.html:15
166 #: templates/boards/login.html:15
162 msgid "User ID"
167 msgid "User ID"
163 msgstr "ID пользователя"
168 msgstr "ID пользователя"
164
169
165 #: templates/boards/login.html:24
170 #: templates/boards/login.html:24
166 msgid "Insert your user id above"
171 msgid "Insert your user id above"
167 msgstr "Вставьте свой ID пользователя выше"
172 msgstr "Вставьте свой ID пользователя выше"
168
173
169 #: templates/boards/post.html:10 templates/boards/rss/post.html:5
174 #: templates/boards/post.html:34 templates/boards/posting_general.html:100
170 msgid "Post image"
175 #: templates/boards/thread.html:59
171 msgstr "Изображение сообщения"
172
173 #: templates/boards/post.html:26 templates/boards/posting_general.html:79
174 #: templates/boards/thread.html:60
175 msgid "Delete"
176 msgid "Delete"
176 msgstr "Удалить"
177 msgstr "Удалить"
177
178
178 #: templates/boards/post.html:29 templates/boards/posting_general.html:82
179 #: templates/boards/post.html:37 templates/boards/posting_general.html:104
179 #: templates/boards/thread.html:63
180 #: templates/boards/thread.html:62
180 msgid "Ban IP"
181 msgid "Ban IP"
181 msgstr "Заблокировать IP"
182 msgstr "Заблокировать IP"
182
183
183 #: templates/boards/posting_general.html:44
184 #: templates/boards/post.html:50 templates/boards/posting_general.html:113
185 #: templates/boards/posting_general.html:169 templates/boards/thread.html:71
186 msgid "Replies"
187 msgstr "Ответы"
188
189 #: templates/boards/posting_general.html:63
184 msgid "Previous page"
190 msgid "Previous page"
185 msgstr "Предыдущая страница"
191 msgstr "Предыдущая страница"
186
192
187 #: templates/boards/posting_general.html:74
193 #: templates/boards/posting_general.html:94
188 msgid "Reply"
194 msgid "Reply"
189 msgstr "Ответ"
195 msgstr "Ответ"
190
196
191 #: templates/boards/posting_general.html:91
197 #: templates/boards/posting_general.html:122 templates/boards/thread.html:154
192 #: templates/boards/posting_general.html:147 templates/boards/thread.html:72
193 msgid "Replies"
194 msgstr "Ответы"
195
196 #: templates/boards/posting_general.html:100 templates/boards/thread.html:156
197 msgid "replies"
198 msgid "replies"
198 msgstr "ответов"
199 msgstr "ответов"
199
200
200 #: templates/boards/posting_general.html:101 templates/boards/thread.html:157
201 #: templates/boards/posting_general.html:123 templates/boards/thread.html:155
201 msgid "images"
202 msgid "images"
202 msgstr "изображений"
203 msgstr "изображений"
203
204
204 #: templates/boards/posting_general.html:170
205 #: templates/boards/posting_general.html:192
205 msgid "Next page"
206 msgid "Next page"
206 msgstr "Следующая страница"
207 msgstr "Следующая страница"
207
208
208 #: templates/boards/posting_general.html:175
209 #: templates/boards/posting_general.html:197
209 msgid "No threads exist. Create the first one!"
210 msgid "No threads exist. Create the first one!"
210 msgstr "Нет тем. Создайте первую!"
211 msgstr "Нет тем. Создайте первую!"
211
212
212 #: templates/boards/posting_general.html:181
213 #: templates/boards/posting_general.html:203
213 msgid "Create new thread"
214 msgid "Create new thread"
214 msgstr "Создать новую тему"
215 msgstr "Создать новую тему"
215
216
216 #: templates/boards/posting_general.html:189 templates/boards/thread.html:108
217 #: templates/boards/posting_general.html:211 templates/boards/thread.html:106
217 msgid "Formatting"
218 msgid "Formatting"
218 msgstr "Форматирование"
219 msgstr "Форматирование"
219
220
220 #: templates/boards/posting_general.html:191 templates/boards/thread.html:110
221 #: templates/boards/posting_general.html:213 templates/boards/thread.html:108
221 msgid "quote"
222 msgid "quote"
222 msgstr "цитата"
223 msgstr "цитата"
223
224
224 #: templates/boards/posting_general.html:192 templates/boards/thread.html:111
225 #: templates/boards/posting_general.html:214 templates/boards/thread.html:109
225 msgid "italic"
226 msgid "italic"
226 msgstr "курсив"
227 msgstr "курсив"
227
228
228 #: templates/boards/posting_general.html:193 templates/boards/thread.html:112
229 #: templates/boards/posting_general.html:215 templates/boards/thread.html:110
229 msgid "bold"
230 msgid "bold"
230 msgstr "полужирный"
231 msgstr "полужирный"
231
232
232 #: templates/boards/posting_general.html:194 templates/boards/thread.html:113
233 #: templates/boards/posting_general.html:216 templates/boards/thread.html:111
233 msgid "spoiler"
234 msgid "spoiler"
234 msgstr "спойлер"
235 msgstr "спойлер"
235
236
236 #: templates/boards/posting_general.html:195 templates/boards/thread.html:114
237 #: templates/boards/posting_general.html:217 templates/boards/thread.html:112
237 msgid "comment"
238 msgid "comment"
238 msgstr "комментарий"
239 msgstr "комментарий"
239
240
240 #: templates/boards/posting_general.html:227 templates/boards/thread.html:142
241 #: templates/boards/posting_general.html:249 templates/boards/thread.html:140
241 msgid "Post"
242 msgid "Post"
242 msgstr "Отправить"
243 msgstr "Отправить"
243
244
244 #: templates/boards/posting_general.html:229
245 #: templates/boards/posting_general.html:251
245 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."
246 msgstr ""
247 msgstr ""
247 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
248 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
248
249
249 #: templates/boards/posting_general.html:232 templates/boards/thread.html:144
250 #: templates/boards/posting_general.html:254 templates/boards/thread.html:142
250 msgid "Text syntax"
251 msgid "Text syntax"
251 msgstr "Синтаксис текста"
252 msgstr "Синтаксис текста"
252
253
253 #: templates/boards/posting_general.html:242
254 #: templates/boards/posting_general.html:264
254 msgid "Pages:"
255 msgid "Pages:"
255 msgstr "Страницы: "
256 msgstr "Страницы: "
256
257
257 #: templates/boards/settings.html:14
258 #: templates/boards/settings.html:14
258 msgid "User:"
259 msgid "User:"
259 msgstr "Пользователь:"
260 msgstr "Пользователь:"
260
261
261 #: templates/boards/settings.html:16
262 #: templates/boards/settings.html:16
262 msgid "You are moderator."
263 msgid "You are moderator."
263 msgstr "Вы модератор."
264 msgstr "Вы модератор."
264
265
265 #: templates/boards/settings.html:19
266 #: templates/boards/settings.html:19
266 msgid "Posts:"
267 msgid "Posts:"
267 msgstr "Сообщений:"
268 msgstr "Сообщений:"
268
269
269 #: templates/boards/settings.html:20
270 #: templates/boards/settings.html:20
270 msgid "First access:"
271 msgid "First access:"
271 msgstr "Первый доступ:"
272 msgstr "Первый доступ:"
272
273
273 #: templates/boards/settings.html:22
274 #: templates/boards/settings.html:22
274 msgid "Last access:"
275 msgid "Last access:"
275 msgstr "Последний доступ: "
276 msgstr "Последний доступ: "
276
277
277 #: templates/boards/settings.html:31
278 #: templates/boards/settings.html:31
278 msgid "Save"
279 msgid "Save"
279 msgstr "Сохранить"
280 msgstr "Сохранить"
280
281
281 #: templates/boards/tags.html:24
282 #: templates/boards/tags.html:24
282 msgid "threads"
283 msgid "threads"
283 msgstr "тем"
284 msgstr "тем"
284
285
285 #: templates/boards/tags.html:37
286 #: templates/boards/tags.html:37
286 msgid "No tags found."
287 msgid "No tags found."
287 msgstr "Теги не найдены."
288 msgstr "Теги не найдены."
288
289
289 #: templates/boards/thread.html:25
290 #: templates/boards/thread.html:24
290 msgid "posts to bumplimit"
291 msgid "posts to bumplimit"
291 msgstr "сообщений до бамплимита"
292 msgstr "сообщений до бамплимита"
292
293
293 #: templates/boards/thread.html:100
294 #: templates/boards/thread.html:98
294 msgid "Reply to thread"
295 msgid "Reply to thread"
295 msgstr "Ответить в тему"
296 msgstr "Ответить в тему"
296
297
297 #: templates/boards/thread.html:158
298 #: templates/boards/thread.html:156
298 msgid "Last update: "
299 msgid "Last update: "
299 msgstr "Последнее обновление: "
300 msgstr "Последнее обновление: "
300
301
302 #: templates/boards/rss/post.html:5
303 msgid "Post image"
304 msgstr "Изображение сообщения"
305
301 #: templates/boards/staticpages/banned.html:6
306 #: templates/boards/staticpages/banned.html:6
302 msgid "Banned"
307 msgid "Banned"
303 msgstr "Заблокирован"
308 msgstr "Заблокирован"
304
309
305 #: templates/boards/staticpages/banned.html:11
310 #: templates/boards/staticpages/banned.html:11
306 msgid "Your IP address has been banned. Contact the administrator"
311 msgid "Your IP address has been banned. Contact the administrator"
307 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
312 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
308
313
309 #: templates/boards/staticpages/help.html:6
314 #: templates/boards/staticpages/help.html:6
310 #: templates/boards/staticpages/help.html:10
315 #: templates/boards/staticpages/help.html:10
311 msgid "Syntax"
316 msgid "Syntax"
312 msgstr "Синтаксис"
317 msgstr "Синтаксис"
313
318
314 #: templates/boards/staticpages/help.html:11
319 #: templates/boards/staticpages/help.html:11
315 msgid "2 line breaks for a new line."
320 msgid "2 line breaks for a new line."
316 msgstr "2 перевода строки создают новый абзац."
321 msgstr "2 перевода строки создают новый абзац."
317
322
318 #: templates/boards/staticpages/help.html:12
323 #: templates/boards/staticpages/help.html:12
319 msgid "Italic text"
324 msgid "Italic text"
320 msgstr "Курсивный текст"
325 msgstr "Курсивный текст"
321
326
322 #: templates/boards/staticpages/help.html:13
327 #: templates/boards/staticpages/help.html:13
323 msgid "Bold text"
328 msgid "Bold text"
324 msgstr "Полужирный текст"
329 msgstr "Полужирный текст"
325
330
326 #: templates/boards/staticpages/help.html:14
331 #: templates/boards/staticpages/help.html:14
327 msgid "Spoiler"
332 msgid "Spoiler"
328 msgstr "Спойлер"
333 msgstr "Спойлер"
329
334
330 #: templates/boards/staticpages/help.html:15
335 #: templates/boards/staticpages/help.html:15
331 msgid "Comment"
336 msgid "Comment"
332 msgstr "Комментарий"
337 msgstr "Комментарий"
333
338
334 #: templates/boards/staticpages/help.html:16
339 #: templates/boards/staticpages/help.html:16
335 msgid "Quote"
340 msgid "Quote"
336 msgstr "Цитата"
341 msgstr "Цитата"
337
342
338 #: templates/boards/staticpages/help.html:17
343 #: templates/boards/staticpages/help.html:17
339 msgid "Link to a post"
344 msgid "Link to a post"
340 msgstr "Ссылка на сообщение"
345 msgstr "Ссылка на сообщение"
341
346
342 #: templates/boards/staticpages/help.html:18
347 #: templates/boards/staticpages/help.html:18
343 msgid "Strikethrough text"
348 msgid "Strikethrough text"
344 msgstr "Зачеркнутый текст"
349 msgstr "Зачеркнутый текст"
345
350
346 #~ msgid "Tag: "
351 #~ msgid "Tag: "
347 #~ msgstr "Тег: "
352 #~ msgstr "Тег: "
348
353
349 #~ msgid "Remove"
354 #~ msgid "Remove"
350 #~ msgstr "Удалить"
355 #~ msgstr "Удалить"
351
356
352 #~ msgid "Add"
357 #~ msgid "Add"
353 #~ msgstr "Добавить"
358 #~ msgstr "Добавить"
354
359
355 #~ msgid "Basic markdown syntax."
360 #~ msgid "Basic markdown syntax."
356 #~ msgstr "Базовый синтаксис markdown."
361 #~ msgstr "Базовый синтаксис markdown."
357
362
358 #~ msgid "Example: "
363 #~ msgid "Example: "
359 #~ msgstr "Пример: "
364 #~ msgstr "Пример: "
360
365
361 #~ msgid "tags"
366 #~ msgid "tags"
362 #~ msgstr "тегов"
367 #~ msgstr "тегов"
363
368
364 #~ msgid "Get!"
369 #~ msgid "Get!"
365 #~ msgstr "Гет!"
370 #~ msgstr "Гет!"
366
371
367 #~ msgid "View"
372 #~ msgid "View"
368 #~ msgstr "Просмотр"
373 #~ msgstr "Просмотр"
369
374
370 #~ msgid "gets"
375 #~ msgid "gets"
371 #~ msgstr "гетов"
376 #~ msgstr "гетов"
@@ -1,295 +1,308 b''
1 from datetime import datetime, timedelta
2 from datetime import time as dtime
1 import os
3 import os
2 from random import random
4 from random import random
3 import time
5 import time
4 import math
6 import math
5 import re
7 import re
6
8
7 from django.db import models
9 from django.db import models
8 from django.http import Http404
10 from django.http import Http404
9 from django.utils import timezone
11 from django.utils import timezone
10 from markupfield.fields import MarkupField
12 from markupfield.fields import MarkupField
11
13
12 from neboard import settings
14 from neboard import settings
13 from boards import settings as boards_settings
15 from boards import settings as boards_settings
14 from boards import thumbs
16 from boards import thumbs
15
17
16 BAN_REASON_AUTO = 'Auto'
18 BAN_REASON_AUTO = 'Auto'
17
19
18 IMAGE_THUMB_SIZE = (200, 150)
20 IMAGE_THUMB_SIZE = (200, 150)
19
21
20 TITLE_MAX_LENGTH = 50
22 TITLE_MAX_LENGTH = 50
21
23
22 DEFAULT_MARKUP_TYPE = 'markdown'
24 DEFAULT_MARKUP_TYPE = 'markdown'
23
25
24 NO_PARENT = -1
26 NO_PARENT = -1
25 NO_IP = '0.0.0.0'
27 NO_IP = '0.0.0.0'
26 UNKNOWN_UA = ''
28 UNKNOWN_UA = ''
27 ALL_PAGES = -1
29 ALL_PAGES = -1
28 IMAGES_DIRECTORY = 'images/'
30 IMAGES_DIRECTORY = 'images/'
29 FILE_EXTENSION_DELIMITER = '.'
31 FILE_EXTENSION_DELIMITER = '.'
30
32
31 SETTING_MODERATE = "moderate"
33 SETTING_MODERATE = "moderate"
32
34
33 REGEX_REPLY = re.compile('>>(\d+)')
35 REGEX_REPLY = re.compile('>>(\d+)')
34
36
35
37
36 class PostManager(models.Manager):
38 class PostManager(models.Manager):
37
39
38 def create_post(self, title, text, image=None, thread=None,
40 def create_post(self, title, text, image=None, thread=None,
39 ip=NO_IP, tags=None, user=None):
41 ip=NO_IP, tags=None, user=None):
40 posting_time = timezone.now()
42 posting_time = timezone.now()
41 if not thread:
43 if not thread:
42 thread = Thread.objects.create(bump_time=posting_time,
44 thread = Thread.objects.create(bump_time=posting_time,
43 last_edit_time=posting_time)
45 last_edit_time=posting_time)
44 else:
46 else:
45 thread.bump()
47 thread.bump()
46 thread.last_edit_time = posting_time
48 thread.last_edit_time = posting_time
47 thread.save()
49 thread.save()
48
50
49 post = self.create(title=title,
51 post = self.create(title=title,
50 text=text,
52 text=text,
51 pub_time=posting_time,
53 pub_time=posting_time,
52 thread_new=thread,
54 thread_new=thread,
53 image=image,
55 image=image,
54 poster_ip=ip,
56 poster_ip=ip,
55 poster_user_agent=UNKNOWN_UA,
57 poster_user_agent=UNKNOWN_UA,
56 last_edit_time=posting_time,
58 last_edit_time=posting_time,
57 user=user)
59 user=user)
58
60
59 thread.replies.add(post)
61 thread.replies.add(post)
60 if tags:
62 if tags:
61 linked_tags = []
63 linked_tags = []
62 for tag in tags:
64 for tag in tags:
63 tag_linked_tags = tag.get_linked_tags()
65 tag_linked_tags = tag.get_linked_tags()
64 if len(tag_linked_tags) > 0:
66 if len(tag_linked_tags) > 0:
65 linked_tags.extend(tag_linked_tags)
67 linked_tags.extend(tag_linked_tags)
66
68
67 tags.extend(linked_tags)
69 tags.extend(linked_tags)
68 map(thread.add_tag, tags)
70 map(thread.add_tag, tags)
69
71
70 self._delete_old_threads()
72 self._delete_old_threads()
71 self.connect_replies(post)
73 self.connect_replies(post)
72
74
73 return post
75 return post
74
76
75 def delete_post(self, post):
77 def delete_post(self, post):
76 thread = post.thread_new
78 thread = post.thread_new
77 thread.last_edit_time = timezone.now()
79 thread.last_edit_time = timezone.now()
78 thread.save()
80 thread.save()
79
81
80 post.delete()
82 post.delete()
81
83
82 def delete_posts_by_ip(self, ip):
84 def delete_posts_by_ip(self, ip):
83 posts = self.filter(poster_ip=ip)
85 posts = self.filter(poster_ip=ip)
84 map(self.delete_post, posts)
86 map(self.delete_post, posts)
85
87
86 # TODO Move this method to thread manager
88 # TODO Move this method to thread manager
87 def get_threads(self, tag=None, page=ALL_PAGES,
89 def get_threads(self, tag=None, page=ALL_PAGES,
88 order_by='-bump_time'):
90 order_by='-bump_time'):
89 if tag:
91 if tag:
90 threads = tag.threads
92 threads = tag.threads
91
93
92 if not threads.exists():
94 if not threads.exists():
93 raise Http404
95 raise Http404
94 else:
96 else:
95 threads = Thread.objects.all()
97 threads = Thread.objects.all()
96
98
97 threads = threads.order_by(order_by)
99 threads = threads.order_by(order_by)
98
100
99 if page != ALL_PAGES:
101 if page != ALL_PAGES:
100 thread_count = threads.count()
102 thread_count = threads.count()
101
103
102 if page < self._get_page_count(thread_count):
104 if page < self._get_page_count(thread_count):
103 start_thread = page * settings.THREADS_PER_PAGE
105 start_thread = page * settings.THREADS_PER_PAGE
104 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
106 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
105 thread_count)
107 thread_count)
106 threads = threads[start_thread:end_thread]
108 threads = threads[start_thread:end_thread]
107
109
108 return threads
110 return threads
109
111
110 # TODO Move this method to thread manager
112 # TODO Move this method to thread manager
111 def get_thread_page_count(self, tag=None):
113 def get_thread_page_count(self, tag=None):
112 if tag:
114 if tag:
113 threads = Thread.objects.filter(tags=tag)
115 threads = Thread.objects.filter(tags=tag)
114 else:
116 else:
115 threads = Thread.objects.all()
117 threads = Thread.objects.all()
116
118
117 return self._get_page_count(threads.count())
119 return self._get_page_count(threads.count())
118
120
119 # TODO Move this method to thread manager
121 # TODO Move this method to thread manager
120 def _delete_old_threads(self):
122 def _delete_old_threads(self):
121 """
123 """
122 Preserves maximum thread count. If there are too many threads,
124 Preserves maximum thread count. If there are too many threads,
123 delete the old ones.
125 delete the old ones.
124 """
126 """
125
127
126 # TODO Move old threads to the archive instead of deleting them.
128 # TODO Move old threads to the archive instead of deleting them.
127 # Maybe make some 'old' field in the model to indicate the thread
129 # Maybe make some 'old' field in the model to indicate the thread
128 # must not be shown and be able for replying.
130 # must not be shown and be able for replying.
129
131
130 threads = Thread.objects.all()
132 threads = Thread.objects.all()
131 thread_count = threads.count()
133 thread_count = threads.count()
132
134
133 if thread_count > settings.MAX_THREAD_COUNT:
135 if thread_count > settings.MAX_THREAD_COUNT:
134 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
136 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
135 old_threads = threads[thread_count - num_threads_to_delete:]
137 old_threads = threads[thread_count - num_threads_to_delete:]
136
138
137 map(Thread.delete_with_posts, old_threads)
139 map(Thread.delete_with_posts, old_threads)
138
140
139 def connect_replies(self, post):
141 def connect_replies(self, post):
140 """Connect replies to a post to show them as a refmap"""
142 """Connect replies to a post to show them as a refmap"""
141
143
142 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
144 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
143 post_id = reply_number.group(1)
145 post_id = reply_number.group(1)
144 ref_post = self.filter(id=post_id)
146 ref_post = self.filter(id=post_id)
145 if ref_post.count() > 0:
147 if ref_post.count() > 0:
146 referenced_post = ref_post[0]
148 referenced_post = ref_post[0]
147 referenced_post.referenced_posts.add(post)
149 referenced_post.referenced_posts.add(post)
148 referenced_post.last_edit_time = post.pub_time
150 referenced_post.last_edit_time = post.pub_time
149 referenced_post.save()
151 referenced_post.save()
150
152
151 def _get_page_count(self, thread_count):
153 def _get_page_count(self, thread_count):
152 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
154 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
153
155
156 def get_posts_per_day(self):
157 """Get count of posts for the current day"""
158
159 today = datetime.now().date()
160 tomorrow = today + timedelta(1)
161 today_start = datetime.combine(today, dtime())
162 today_end = datetime.combine(tomorrow, dtime())
163
164 return self.filter(pub_time__lte=today_end, pub_time__gte=today_start)\
165 .count()
166
154
167
155 class Post(models.Model):
168 class Post(models.Model):
156 """A post is a message."""
169 """A post is a message."""
157
170
158 objects = PostManager()
171 objects = PostManager()
159
172
160 class Meta:
173 class Meta:
161 app_label = 'boards'
174 app_label = 'boards'
162
175
163 def _update_image_filename(self, filename):
176 def _update_image_filename(self, filename):
164 """Get unique image filename"""
177 """Get unique image filename"""
165
178
166 path = IMAGES_DIRECTORY
179 path = IMAGES_DIRECTORY
167 new_name = str(int(time.mktime(time.gmtime())))
180 new_name = str(int(time.mktime(time.gmtime())))
168 new_name += str(int(random() * 1000))
181 new_name += str(int(random() * 1000))
169 new_name += FILE_EXTENSION_DELIMITER
182 new_name += FILE_EXTENSION_DELIMITER
170 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
183 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
171
184
172 return os.path.join(path, new_name)
185 return os.path.join(path, new_name)
173
186
174 title = models.CharField(max_length=TITLE_MAX_LENGTH)
187 title = models.CharField(max_length=TITLE_MAX_LENGTH)
175 pub_time = models.DateTimeField()
188 pub_time = models.DateTimeField()
176 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
189 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
177 escape_html=False)
190 escape_html=False)
178
191
179 image_width = models.IntegerField(default=0)
192 image_width = models.IntegerField(default=0)
180 image_height = models.IntegerField(default=0)
193 image_height = models.IntegerField(default=0)
181
194
182 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
195 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
183 blank=True, sizes=(IMAGE_THUMB_SIZE,),
196 blank=True, sizes=(IMAGE_THUMB_SIZE,),
184 width_field='image_width',
197 width_field='image_width',
185 height_field='image_height')
198 height_field='image_height')
186
199
187 poster_ip = models.GenericIPAddressField()
200 poster_ip = models.GenericIPAddressField()
188 poster_user_agent = models.TextField()
201 poster_user_agent = models.TextField()
189
202
190 thread = models.ForeignKey('Post', null=True, default=None)
203 thread = models.ForeignKey('Post', null=True, default=None)
191 thread_new = models.ForeignKey('Thread', null=True, default=None)
204 thread_new = models.ForeignKey('Thread', null=True, default=None)
192 last_edit_time = models.DateTimeField()
205 last_edit_time = models.DateTimeField()
193 user = models.ForeignKey('User', null=True, default=None)
206 user = models.ForeignKey('User', null=True, default=None)
194
207
195 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
208 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
196 null=True,
209 null=True,
197 blank=True, related_name='rfp+')
210 blank=True, related_name='rfp+')
198
211
199 def __unicode__(self):
212 def __unicode__(self):
200 return '#' + str(self.id) + ' ' + self.title + ' (' + \
213 return '#' + str(self.id) + ' ' + self.title + ' (' + \
201 self.text.raw[:50] + ')'
214 self.text.raw[:50] + ')'
202
215
203 def get_title(self):
216 def get_title(self):
204 title = self.title
217 title = self.title
205 if len(title) == 0:
218 if len(title) == 0:
206 title = self.text.raw[:20]
219 title = self.text.raw[:20]
207
220
208 return title
221 return title
209
222
210 def get_sorted_referenced_posts(self):
223 def get_sorted_referenced_posts(self):
211 return self.referenced_posts.order_by('id')
224 return self.referenced_posts.order_by('id')
212
225
213 def is_referenced(self):
226 def is_referenced(self):
214 return self.referenced_posts.all().exists()
227 return self.referenced_posts.all().exists()
215
228
216 def is_opening(self):
229 def is_opening(self):
217 return self.thread_new.get_replies()[0] == self
230 return self.thread_new.get_replies()[0] == self
218
231
219
232
220 class Thread(models.Model):
233 class Thread(models.Model):
221
234
222 class Meta:
235 class Meta:
223 app_label = 'boards'
236 app_label = 'boards'
224
237
225 tags = models.ManyToManyField('Tag')
238 tags = models.ManyToManyField('Tag')
226 bump_time = models.DateTimeField()
239 bump_time = models.DateTimeField()
227 last_edit_time = models.DateTimeField()
240 last_edit_time = models.DateTimeField()
228 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
241 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
229 blank=True, related_name='tre+')
242 blank=True, related_name='tre+')
230
243
231 def get_tags(self):
244 def get_tags(self):
232 """Get a sorted tag list"""
245 """Get a sorted tag list"""
233
246
234 return self.tags.order_by('name')
247 return self.tags.order_by('name')
235
248
236 def bump(self):
249 def bump(self):
237 """Bump (move to up) thread"""
250 """Bump (move to up) thread"""
238
251
239 if self.can_bump():
252 if self.can_bump():
240 self.bump_time = timezone.now()
253 self.bump_time = timezone.now()
241
254
242 def get_reply_count(self):
255 def get_reply_count(self):
243 return self.replies.count()
256 return self.replies.count()
244
257
245 def get_images_count(self):
258 def get_images_count(self):
246 return self.replies.filter(image_width__gt=0).count()
259 return self.replies.filter(image_width__gt=0).count()
247
260
248 def can_bump(self):
261 def can_bump(self):
249 """Check if the thread can be bumped by replying"""
262 """Check if the thread can be bumped by replying"""
250
263
251 post_count = self.get_reply_count()
264 post_count = self.get_reply_count()
252
265
253 return post_count <= settings.MAX_POSTS_PER_THREAD
266 return post_count <= settings.MAX_POSTS_PER_THREAD
254
267
255 def delete_with_posts(self):
268 def delete_with_posts(self):
256 """Completely delete thread"""
269 """Completely delete thread"""
257
270
258 if self.replies.count() > 0:
271 if self.replies.count() > 0:
259 map(Post.objects.delete_post, self.replies.all())
272 map(Post.objects.delete_post, self.replies.all())
260
273
261 self.delete()
274 self.delete()
262
275
263 def get_last_replies(self):
276 def get_last_replies(self):
264 """Get last replies, not including opening post"""
277 """Get last replies, not including opening post"""
265
278
266 if settings.LAST_REPLIES_COUNT > 0:
279 if settings.LAST_REPLIES_COUNT > 0:
267 reply_count = self.get_reply_count()
280 reply_count = self.get_reply_count()
268
281
269 if reply_count > 0:
282 if reply_count > 0:
270 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
283 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
271 reply_count - 1)
284 reply_count - 1)
272 last_replies = self.replies.all().order_by('pub_time')[
285 last_replies = self.replies.all().order_by('pub_time')[
273 reply_count - reply_count_to_show:]
286 reply_count - reply_count_to_show:]
274
287
275 return last_replies
288 return last_replies
276
289
277 def get_replies(self):
290 def get_replies(self):
278 """Get sorted thread posts"""
291 """Get sorted thread posts"""
279
292
280 return self.replies.all().order_by('pub_time')
293 return self.replies.all().order_by('pub_time')
281
294
282 def add_tag(self, tag):
295 def add_tag(self, tag):
283 """Connect thread to a tag and tag to a thread"""
296 """Connect thread to a tag and tag to a thread"""
284
297
285 self.tags.add(tag)
298 self.tags.add(tag)
286 tag.threads.add(self)
299 tag.threads.add(self)
287
300
288 def get_opening_post(self):
301 def get_opening_post(self):
289 return self.get_replies()[0]
302 return self.get_replies()[0]
290
303
291 def __unicode__(self):
304 def __unicode__(self):
292 return str(self.get_replies()[0].id)
305 return str(self.get_replies()[0].id)
293
306
294 def get_pub_time(self):
307 def get_pub_time(self):
295 return self.get_opening_post().pub_time No newline at end of file
308 return self.get_opening_post().pub_time
@@ -1,58 +1,59 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3 {% load static from staticfiles %}
3 {% load static from staticfiles %}
4
4
5 <!DOCTYPE html>
5 <!DOCTYPE html>
6 <html>
6 <html>
7 <head>
7 <head>
8 <link rel="stylesheet" type="text/css"
8 <link rel="stylesheet" type="text/css"
9 href="{% static 'css/base.css' %}" media="all"/>
9 href="{% static 'css/base.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css"
10 <link rel="stylesheet" type="text/css"
11 href="{% static theme_css %}" media="all"/>
11 href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title=
12 <link rel="alternate" type="application/rss+xml" href="rss/" title=
13 "{% trans 'Feed' %}"/>
13 "{% trans 'Feed' %}"/>
14
14
15 <link rel="icon" type="image/png"
15 <link rel="icon" type="image/png"
16 href="{% static 'favicon.png' %}">
16 href="{% static 'favicon.png' %}">
17
17
18 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta name="viewport" content="width=device-width, initial-scale=1"/>
19 <meta charset="utf-8"/>
19 <meta charset="utf-8"/>
20
20
21 {% block head %}{% endblock %}
21 {% block head %}{% endblock %}
22 </head>
22 </head>
23 <body>
23 <body>
24 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
24 <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>
25 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
26 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
26 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
27 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
27 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
28 <script src="{% static 'js/panel.js' %}"></script>
28 <script src="{% static 'js/panel.js' %}"></script>
29 <script src="{% static 'js/popup.js' %}"></script>
29 <script src="{% static 'js/popup.js' %}"></script>
30 <script src="{% static 'js/image.js' %}"></script>
30 <script src="{% static 'js/image.js' %}"></script>
31 <script src="{% static 'js/refpopup.js' %}"></script>
31 <script src="{% static 'js/refpopup.js' %}"></script>
32 <script src="{% static 'js/main.js' %}"></script>
32 <script src="{% static 'js/main.js' %}"></script>
33
33
34 <div class="navigation_panel">
34 <div class="navigation_panel">
35 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
35 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
36 {% for tag in tags %}
36 {% for tag in tags %}
37 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
37 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
38 >#{{ tag.name }}</a>,
38 >#{{ tag.name }}</a>,
39 {% endfor %}
39 {% endfor %}
40 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
40 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
41 >[...]</a>
41 >[...]</a>
42 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
42 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
43 </div>
43 </div>
44
44
45 {% block content %}{% endblock %}
45 {% block content %}{% endblock %}
46
46
47 <div class="navigation_panel">
47 <div class="navigation_panel">
48 {% block metapanel %}{% endblock %}
48 {% block metapanel %}{% endblock %}
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 {% blocktrans %}Speed: {{ posts_per_day }} posts per day{% endblocktrans %}
50 <a class="link" href="#top">{% trans 'Up' %}</a>
51 <a class="link" href="#top">{% trans 'Up' %}</a>
51 </div>
52 </div>
52
53
53 <div class="footer">
54 <div class="footer">
54 <!-- Put your banners here -->
55 <!-- Put your banners here -->
55 </div>
56 </div>
56
57
57 </body>
58 </body>
58 </html>
59 </html>
@@ -1,565 +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
482
482 theme = _get_theme(request, user)
483 theme = _get_theme(request, user)
483 context['theme'] = theme
484 context['theme'] = theme
484 context['theme_css'] = 'css/' + theme + '/base_page.css'
485 context['theme_css'] = 'css/' + theme + '/base_page.css'
485
486
486 # This shows the moderator panel
487 # This shows the moderator panel
487 moderate = user.get_setting(SETTING_MODERATE)
488 moderate = user.get_setting(SETTING_MODERATE)
488 if moderate == 'True':
489 if moderate == 'True':
489 context['moderator'] = user.is_moderator()
490 context['moderator'] = user.is_moderator()
490 else:
491 else:
491 context['moderator'] = False
492 context['moderator'] = False
492
493
493 return context
494 return context
494
495
495
496
496 def _get_user(request):
497 def _get_user(request):
497 """
498 """
498 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
499 a new one.
500 a new one.
500 """
501 """
501
502
502 session = request.session
503 session = request.session
503 if not 'user_id' in session:
504 if not 'user_id' in session:
504 request.session.save()
505 request.session.save()
505
506
506 md5 = hashlib.md5()
507 md5 = hashlib.md5()
507 md5.update(session.session_key)
508 md5.update(session.session_key)
508 new_id = md5.hexdigest()
509 new_id = md5.hexdigest()
509
510
510 time_now = timezone.now()
511 time_now = timezone.now()
511 user = User.objects.create(user_id=new_id, rank=RANK_USER,
512 user = User.objects.create(user_id=new_id, rank=RANK_USER,
512 registration_time=time_now)
513 registration_time=time_now)
513
514
514 session['user_id'] = user.id
515 session['user_id'] = user.id
515 else:
516 else:
516 user = User.objects.get(id=session['user_id'])
517 user = User.objects.get(id=session['user_id'])
517
518
518 return user
519 return user
519
520
520
521
521 def _redirect_to_next(request):
522 def _redirect_to_next(request):
522 """
523 """
523 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
524 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
525 view has finished its work.
526 view has finished its work.
526 """
527 """
527
528
528 if 'next' in request.GET:
529 if 'next' in request.GET:
529 next_page = request.GET['next']
530 next_page = request.GET['next']
530 return HttpResponseRedirect(next_page)
531 return HttpResponseRedirect(next_page)
531 else:
532 else:
532 return redirect(index)
533 return redirect(index)
533
534
534
535
535 @transaction.atomic
536 @transaction.atomic
536 def _ban_current_user(request):
537 def _ban_current_user(request):
537 """Add current user to the IP ban list"""
538 """Add current user to the IP ban list"""
538
539
539 ip = utils.get_client_ip(request)
540 ip = utils.get_client_ip(request)
540 ban, created = Ban.objects.get_or_create(ip=ip)
541 ban, created = Ban.objects.get_or_create(ip=ip)
541 if created:
542 if created:
542 ban.can_read = False
543 ban.can_read = False
543 ban.reason = BAN_REASON_SPAM
544 ban.reason = BAN_REASON_SPAM
544 ban.save()
545 ban.save()
545
546
546
547
547 def _remove_invalid_links(text):
548 def _remove_invalid_links(text):
548 """
549 """
549 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.
550 Invalid links are links to non-existent posts
551 Invalid links are links to non-existent posts
551 """
552 """
552
553
553 for reply_number in re.finditer(REGEX_REPLY, text):
554 for reply_number in re.finditer(REGEX_REPLY, text):
554 post_id = reply_number.group(1)
555 post_id = reply_number.group(1)
555 post = Post.objects.filter(id=post_id)
556 post = Post.objects.filter(id=post_id)
556 if not post.exists():
557 if not post.exists():
557 text = string.replace(text, '>>' + post_id, post_id)
558 text = string.replace(text, '>>' + post_id, post_id)
558
559
559 return text
560 return text
560
561
561
562
562 def _datetime_to_epoch(datetime):
563 def _datetime_to_epoch(datetime):
563 return int(time.mktime(timezone.localtime(
564 return int(time.mktime(timezone.localtime(
564 datetime,timezone.get_current_timezone()).timetuple())
565 datetime,timezone.get_current_timezone()).timetuple())
565 * 1000000 + datetime.microsecond)
566 * 1000000 + datetime.microsecond)
@@ -1,48 +1,48 b''
1 = Features =
1 = Features =
2 [DONE] Connecting tags to each other
2 [DONE] Connecting tags to each other
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
4 [DONE] Better django admin pages to simplify admin operations
4 [DONE] Better django admin pages to simplify admin operations
5 [DONE] Regen script to update all posts
5 [DONE] Regen script to update all posts
6 [DONE] Remove jump links from refmaps
6 [DONE] Remove jump links from refmaps
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
8 denied". Use second only for autoban for spam
8 denied". Use second only for autoban for spam
9 [DONE] Clean up tests and make them run ALWAYS
9 [DONE] Clean up tests and make them run ALWAYS
10 [DONE] Use transactions in tests
10 [DONE] Use transactions in tests
11 [DONE] Thread autoupdate (JS + API)
11 [DONE] Thread autoupdate (JS + API)
12 [IN PROGRESS] Split up post model into post and thread,
12 [DONE] Split up post model into post and thread,
13 and move everything that is used only in 1st post to thread model.
13 and move everything that is used only in 1st post to thread model.
14 [DONE] Show board speed in the lower panel (posts per day)
14
15
15 [NOT STARTED] Tree view (JS)
16 [NOT STARTED] Tree view (JS)
16 [NOT STARTED] Adding tags to images filename
17 [NOT STARTED] Adding tags to images filename
17 [NOT STARTED] Federative network for s2s communication
18 [NOT STARTED] Federative network for s2s communication
18 [NOT STARTED] XMPP gate
19 [NOT STARTED] XMPP gate
19 [NOT STARTED] Bitmessage gate
20 [NOT STARTED] Bitmessage gate
20 [NOT STARTED] Notification engine
21 [NOT STARTED] Notification engine
21 [NOT STARTED] Javascript disabling engine
22 [NOT STARTED] Javascript disabling engine
22 [NOT STARTED] Group tags by first letter in all tags list
23 [NOT STARTED] Group tags by first letter in all tags list
23 [NOT STARTED] Show board speed in the lower panel (posts per day)
24 [NOT STARTED] Character counter in the post field
24 [NOT STARTED] Character counter in the post field
25 [NOT STARTED] Save image thumbnails size to the separate field
25 [NOT STARTED] Save image thumbnails size to the separate field
26 [NOT STARTED] Whitelist functionality. Permin autoban of an address
26 [NOT STARTED] Whitelist functionality. Permin autoban of an address
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 performance), posts per day/week/month, users (or IPs)
28 performance), posts per day/week/month, users (or IPs)
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
30 post or its part (delimited by N characters) into quote of the new post.
30 post or its part (delimited by N characters) into quote of the new post.
31 [NOT STARTED] Ban confirmation page with reason
31 [NOT STARTED] Ban confirmation page with reason
32 [NOT STARTED] Post deletion confirmation page
32 [NOT STARTED] Post deletion confirmation page
33 [NOT STARTED] Moderating page. Tags editing and adding
33 [NOT STARTED] Moderating page. Tags editing and adding
34 [NOT STARTED] Get thread graph image using pygraphviz
34 [NOT STARTED] Get thread graph image using pygraphviz
35 [NOT STARTED] Creating post via AJAX without reloading page
35 [NOT STARTED] Creating post via AJAX without reloading page
36 [NOT STARTED] Subscribing to tag via AJAX
36 [NOT STARTED] Subscribing to tag via AJAX
37
37
38 = Bugs =
38 = Bugs =
39 [DONE] Fix bug with creating threads from tag view
39 [DONE] Fix bug with creating threads from tag view
40 [DONE] Quote characters within quote causes quote parsing to fail
40 [DONE] Quote characters within quote causes quote parsing to fail
41
41
42 [IN PROGRESS] Replies, images, last update time in bottom panel doesn't change when
42 [IN PROGRESS] Replies, images, last update time in bottom panel doesn't change when
43 thread updates
43 thread updates
44
44
45 = Testing =
45 = Testing =
46 [NOT STARTED] Make tests for every view
46 [NOT STARTED] Make tests for every view
47 [NOT STARTED] Make tests for every model
47 [NOT STARTED] Make tests for every model
48 [NOT STARTED] Make tests for every form
48 [NOT STARTED] Make tests for every form
General Comments 0
You need to be logged in to leave comments. Login now