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