##// END OF EJS Templates
Added veterans (old users). Veterans currently have lower posting speed limit
neko259 -
r642:67b694de default
parent child Browse files
Show More
@@ -1,339 +1,347 b''
1 1 import re
2 2 import time
3 3 import hashlib
4 4
5 5 from captcha.fields import CaptchaField
6 6 from django import forms
7 7 from django.forms.util import ErrorList
8 8 from django.utils.translation import ugettext_lazy as _
9 9
10 10 from boards.mdx_neboard import formatters
11 11 from boards.models.post import TITLE_MAX_LENGTH
12 12 from boards.models import User, Post
13 13 from neboard import settings
14 14 from boards import utils
15 15 import boards.settings as board_settings
16 16
17 VETERAN_POSTING_DELAY = 5
18
17 19 ATTRIBUTE_PLACEHOLDER = 'placeholder'
18 20
19 21 LAST_POST_TIME = 'last_post_time'
20 22 LAST_LOGIN_TIME = 'last_login_time'
21 23 TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like
22 24 this. 2 new lines are required to start new paragraph.''')
23 25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
24 26
25 27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
26 28
27 29 LABEL_TITLE = _('Title')
28 30 LABEL_TEXT = _('Text')
29 31 LABEL_TAG = _('Tag')
30 32
31 33 TAG_MAX_LENGTH = 20
32 34
33 35 REGEX_TAG = ur'^[\w\d]+$'
34 36
35 37
36 38 class FormatPanel(forms.Textarea):
37 39 def render(self, name, value, attrs=None):
38 40 output = '<div id="mark-panel">'
39 41 for formatter in formatters:
40 42 output += u'<span class="mark_btn"' + \
41 43 u' onClick="addMarkToMsg(\'' + formatter.format_left + \
42 44 '\', \'' + formatter.format_right + '\')">' + \
43 45 formatter.preview_left + formatter.name + \
44 46 formatter.preview_right + u'</span>'
45 47
46 48 output += '</div>'
47 49 output += super(FormatPanel, self).render(name, value, attrs=None)
48 50
49 51 return output
50 52
51 53
52 54 class PlainErrorList(ErrorList):
53 55 def __unicode__(self):
54 56 return self.as_text()
55 57
56 58 def as_text(self):
57 59 return ''.join([u'(!) %s ' % e for e in self])
58 60
59 61
60 62 class NeboardForm(forms.Form):
61 63
62 64 def as_div(self):
63 65 """
64 66 Returns this form rendered as HTML <as_div>s.
65 67 """
66 68
67 69 return self._html_output(
68 70 # TODO Do not show hidden rows in the list here
69 71 normal_row='<div class="form-row">'
70 72 '<div class="form-label">'
71 73 '%(label)s'
72 74 '</div>'
73 75 '<div class="form-input">'
74 76 '%(field)s'
75 77 '</div>'
76 78 '%(help_text)s'
77 79 '</div>',
78 80 error_row='<div class="form-row">'
79 81 '<div class="form-label"></div>'
80 82 '<div class="form-errors">%s</div>'
81 83 '</div>',
82 84 row_ender='</div>',
83 85 help_text_html='%s',
84 86 errors_on_separate_row=True)
85 87
86 88 def as_json_errors(self):
87 89 errors = []
88 90
89 91 for name, field in self.fields.items():
90 92 if self[name].errors:
91 93 errors.append({
92 94 'field': name,
93 95 'errors': self[name].errors.as_text(),
94 96 })
95 97
96 98 return errors
97 99
98 100
99 101 class PostForm(NeboardForm):
100 102
101 103 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
102 104 label=LABEL_TITLE)
103 105 text = forms.CharField(
104 106 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
105 107 required=False, label=LABEL_TEXT)
106 108 image = forms.ImageField(required=False, label=_('Image'))
107 109
108 110 # This field is for spam prevention only
109 111 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
110 112 widget=forms.TextInput(attrs={
111 113 'class': 'form-email'}))
112 114
113 115 session = None
114 116 need_to_ban = False
115 117
116 118 def clean_title(self):
117 119 title = self.cleaned_data['title']
118 120 if title:
119 121 if len(title) > TITLE_MAX_LENGTH:
120 122 raise forms.ValidationError(_('Title must have less than %s '
121 123 'characters') %
122 124 str(TITLE_MAX_LENGTH))
123 125 return title
124 126
125 127 def clean_text(self):
126 128 text = self.cleaned_data['text']
127 129 if text:
128 130 if len(text) > board_settings.MAX_TEXT_LENGTH:
129 131 raise forms.ValidationError(_('Text must have less than %s '
130 132 'characters') %
131 133 str(board_settings
132 134 .MAX_TEXT_LENGTH))
133 135 return text
134 136
135 137 def clean_image(self):
136 138 image = self.cleaned_data['image']
137 139 if image:
138 140 if image._size > board_settings.MAX_IMAGE_SIZE:
139 141 raise forms.ValidationError(
140 142 _('Image must be less than %s bytes')
141 143 % str(board_settings.MAX_IMAGE_SIZE))
142 144
143 145 md5 = hashlib.md5()
144 146 for chunk in image.chunks():
145 147 md5.update(chunk)
146 148 image_hash = md5.hexdigest()
147 149 if Post.objects.filter(image_hash=image_hash).exists():
148 150 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
149 151
150 152 return image
151 153
152 154 def clean(self):
153 155 cleaned_data = super(PostForm, self).clean()
154 156
155 157 if not self.session:
156 158 raise forms.ValidationError('Humans have sessions')
157 159
158 160 if cleaned_data['email']:
159 161 self.need_to_ban = True
160 162 raise forms.ValidationError('A human cannot enter a hidden field')
161 163
162 164 if not self.errors:
163 165 self._clean_text_image()
164 166
165 167 if not self.errors and self.session:
166 168 self._validate_posting_speed()
167 169
168 170 return cleaned_data
169 171
170 172 def _clean_text_image(self):
171 173 text = self.cleaned_data.get('text')
172 174 image = self.cleaned_data.get('image')
173 175
174 176 if (not text) and (not image):
175 177 error_message = _('Either text or image must be entered.')
176 178 self._errors['text'] = self.error_class([error_message])
177 179
178 180 def _validate_posting_speed(self):
179 181 can_post = True
180 182
183 user = User.objects.get(id=self.session['user_id'])
184 if user.is_veteran():
185 posting_delay = VETERAN_POSTING_DELAY
186 else:
187 posting_delay = settings.POSTING_DELAY
188
181 189 if LAST_POST_TIME in self.session:
182 190 now = time.time()
183 191 last_post_time = self.session[LAST_POST_TIME]
184 192
185 193 current_delay = int(now - last_post_time)
186 194
187 if current_delay < settings.POSTING_DELAY:
195 if current_delay < posting_delay:
188 196 error_message = _('Wait %s seconds after last posting') % str(
189 settings.POSTING_DELAY - current_delay)
197 posting_delay - current_delay)
190 198 self._errors['text'] = self.error_class([error_message])
191 199
192 200 can_post = False
193 201
194 202 if can_post:
195 203 self.session[LAST_POST_TIME] = time.time()
196 204
197 205
198 206 class ThreadForm(PostForm):
199 207
200 208 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
201 209
202 210 tags = forms.CharField(
203 211 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
204 212 max_length=100, label=_('Tags'))
205 213
206 214 def clean_tags(self):
207 215 tags = self.cleaned_data['tags']
208 216
209 217 if tags:
210 218 if not self.regex_tags.match(tags):
211 219 raise forms.ValidationError(
212 220 _('Inappropriate characters in tags.'))
213 221
214 222 return tags
215 223
216 224 def clean(self):
217 225 cleaned_data = super(ThreadForm, self).clean()
218 226
219 227 return cleaned_data
220 228
221 229
222 230 class PostCaptchaForm(PostForm):
223 231 captcha = CaptchaField()
224 232
225 233 def __init__(self, *args, **kwargs):
226 234 self.request = kwargs['request']
227 235 del kwargs['request']
228 236
229 237 super(PostCaptchaForm, self).__init__(*args, **kwargs)
230 238
231 239 def clean(self):
232 240 cleaned_data = super(PostCaptchaForm, self).clean()
233 241
234 242 success = self.is_valid()
235 243 utils.update_captcha_access(self.request, success)
236 244
237 245 if success:
238 246 return cleaned_data
239 247 else:
240 248 raise forms.ValidationError(_("Captcha validation failed"))
241 249
242 250
243 251 class ThreadCaptchaForm(ThreadForm):
244 252 captcha = CaptchaField()
245 253
246 254 def __init__(self, *args, **kwargs):
247 255 self.request = kwargs['request']
248 256 del kwargs['request']
249 257
250 258 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
251 259
252 260 def clean(self):
253 261 cleaned_data = super(ThreadCaptchaForm, self).clean()
254 262
255 263 success = self.is_valid()
256 264 utils.update_captcha_access(self.request, success)
257 265
258 266 if success:
259 267 return cleaned_data
260 268 else:
261 269 raise forms.ValidationError(_("Captcha validation failed"))
262 270
263 271
264 272 class SettingsForm(NeboardForm):
265 273
266 274 theme = forms.ChoiceField(choices=settings.THEMES,
267 275 label=_('Theme'))
268 276
269 277
270 278 class ModeratorSettingsForm(SettingsForm):
271 279
272 280 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
273 281 'panel'))
274 282
275 283
276 284 class LoginForm(NeboardForm):
277 285
278 286 user_id = forms.CharField()
279 287
280 288 session = None
281 289
282 290 def clean_user_id(self):
283 291 user_id = self.cleaned_data['user_id']
284 292 if user_id:
285 293 users = User.objects.filter(user_id=user_id)
286 294 if len(users) == 0:
287 295 raise forms.ValidationError(_('No such user found'))
288 296
289 297 return user_id
290 298
291 299 def _validate_login_speed(self):
292 300 can_post = True
293 301
294 302 if LAST_LOGIN_TIME in self.session:
295 303 now = time.time()
296 304 last_login_time = self.session[LAST_LOGIN_TIME]
297 305
298 306 current_delay = int(now - last_login_time)
299 307
300 308 if current_delay < board_settings.LOGIN_TIMEOUT:
301 309 error_message = _('Wait %s minutes after last login') % str(
302 310 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
303 311 self._errors['user_id'] = self.error_class([error_message])
304 312
305 313 can_post = False
306 314
307 315 if can_post:
308 316 self.session[LAST_LOGIN_TIME] = time.time()
309 317
310 318 def clean(self):
311 319 if not self.session:
312 320 raise forms.ValidationError('Humans have sessions')
313 321
314 322 self._validate_login_speed()
315 323
316 324 cleaned_data = super(LoginForm, self).clean()
317 325
318 326 return cleaned_data
319 327
320 328
321 329 class AddTagForm(NeboardForm):
322 330
323 331 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
324 332 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
325 333
326 334 def clean_tag(self):
327 335 tag = self.cleaned_data['tag']
328 336
329 337 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
330 338 if not regex_tag.match(tag):
331 339 raise forms.ValidationError(_('Inappropriate characters in tags.'))
332 340
333 341 return tag
334 342
335 343 def clean(self):
336 344 cleaned_data = super(AddTagForm, self).clean()
337 345
338 346 return cleaned_data
339 347
@@ -1,382 +1,386 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 "POT-Creation-Date: 2014-02-08 20:55+0200\n"
10 "POT-Creation-Date: 2014-03-08 12:24+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:21
38 38 msgid ""
39 39 "Type message here. You can reply to message >>123 like\n"
40 40 " this. 2 new lines are required to start new paragraph."
41 41 msgstr ""
42 42 "Π’Π²Π΅Π΄ΠΈΡ‚Π΅ сообщСниС здСсь. Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π½Π° сообщСниС >>123 Π²ΠΎΡ‚ Ρ‚Π°ΠΊ. 2 "
43 43 "пСрСноса строки ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ для создания Π½ΠΎΠ²ΠΎΠ³ΠΎ Π°Π±Π·Π°Ρ†Π°."
44 44
45 45 #: forms.py:23
46 46 msgid "tag1 several_words_tag"
47 47 msgstr "Ρ‚Π΅Π³1 Ρ‚Π΅Π³_ΠΈΠ·_Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ…_слов"
48 48
49 49 #: forms.py:25
50 50 msgid "Such image was already posted"
51 51 msgstr "Π’Π°ΠΊΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠΆΠ΅ Π±Ρ‹Π»ΠΎ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ"
52 52
53 53 #: forms.py:27
54 54 msgid "Title"
55 55 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
56 56
57 57 #: forms.py:28
58 58 msgid "Text"
59 59 msgstr "ВСкст"
60 60
61 61 #: forms.py:29
62 62 msgid "Tag"
63 63 msgstr "Π’Π΅Π³"
64 64
65 65 #: forms.py:106
66 66 msgid "Image"
67 67 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
68 68
69 69 #: forms.py:109
70 70 msgid "e-mail"
71 71 msgstr ""
72 72
73 73 #: forms.py:120
74 74 #, python-format
75 75 msgid "Title must have less than %s characters"
76 76 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠ΅Ρ‚ΡŒ мСньшС %s символов"
77 77
78 78 #: forms.py:129
79 79 #, python-format
80 80 msgid "Text must have less than %s characters"
81 81 msgstr "ВСкст Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΊΠΎΡ€ΠΎΡ‡Π΅ %s символов"
82 82
83 83 #: forms.py:140
84 84 #, python-format
85 85 msgid "Image must be less than %s bytes"
86 86 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΌΠ΅Π½Π΅Π΅ %s Π±Π°ΠΉΡ‚"
87 87
88 88 #: forms.py:175
89 89 msgid "Either text or image must be entered."
90 90 msgstr "ВСкст ΠΈΠ»ΠΈ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠ° Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Ρ‹."
91 91
92 #: forms.py:188
92 #: forms.py:193
93 93 #, python-format
94 94 msgid "Wait %s seconds after last posting"
95 95 msgstr "ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %s сСкунд послС послСднСго постинга"
96 96
97 #: forms.py:204 templates/boards/tags.html:7 templates/boards/rss/post.html:10
97 #: forms.py:209 templates/boards/tags.html:7 templates/boards/rss/post.html:10
98 98 msgid "Tags"
99 99 msgstr "Π’Π΅Π³ΠΈ"
100 100
101 #: forms.py:212 forms.py:331
101 #: forms.py:217 forms.py:336
102 102 msgid "Inappropriate characters in tags."
103 103 msgstr "НСдопустимыС символы Π² Ρ‚Π΅Π³Π°Ρ…."
104 104
105 #: forms.py:240 forms.py:261
105 #: forms.py:245 forms.py:266
106 106 msgid "Captcha validation failed"
107 107 msgstr "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΊΠ°ΠΏΡ‡ΠΈ ΠΏΡ€ΠΎΠ²Π°Π»Π΅Π½Π°"
108 108
109 #: forms.py:267
109 #: forms.py:272
110 110 msgid "Theme"
111 111 msgstr "Π’Π΅ΠΌΠ°"
112 112
113 #: forms.py:272
113 #: forms.py:277
114 114 msgid "Enable moderation panel"
115 115 msgstr "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ панСль ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ†ΠΈΠΈ"
116 116
117 #: forms.py:287
117 #: forms.py:292
118 118 msgid "No such user found"
119 119 msgstr "Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½"
120 120
121 #: forms.py:301
121 #: forms.py:306
122 122 #, python-format
123 123 msgid "Wait %s minutes after last login"
124 124 msgstr "ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %s ΠΌΠΈΠ½ΡƒΡ‚ послС послСднСго Π²Ρ…ΠΎΠ΄Π°"
125 125
126 126 #: templates/boards/404.html:6
127 127 msgid "Not found"
128 128 msgstr "НС найдСно"
129 129
130 130 #: templates/boards/404.html:12
131 131 msgid "This page does not exist"
132 132 msgstr "Π­Ρ‚ΠΎΠΉ страницы Π½Π΅ сущСствуСт"
133 133
134 134 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
135 135 msgid "Authors"
136 136 msgstr "Авторы"
137 137
138 138 #: templates/boards/authors.html:26
139 139 msgid "Distributed under the"
140 140 msgstr "РаспространяСтся ΠΏΠΎΠ΄"
141 141
142 142 #: templates/boards/authors.html:28
143 143 msgid "license"
144 144 msgstr "Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ"
145 145
146 146 #: templates/boards/authors.html:30
147 147 msgid "Repository"
148 148 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
149 149
150 150 #: templates/boards/base.html:14
151 151 msgid "Feed"
152 152 msgstr "Π›Π΅Π½Ρ‚Π°"
153 153
154 154 #: templates/boards/base.html:31
155 155 msgid "All threads"
156 156 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
157 157
158 158 #: templates/boards/base.html:36
159 159 msgid "Tag management"
160 160 msgstr "Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚Π΅Π³Π°ΠΌΠΈ"
161 161
162 162 #: templates/boards/base.html:38 templates/boards/settings.html:7
163 163 msgid "Settings"
164 164 msgstr "Настройки"
165 165
166 166 #: templates/boards/base.html:50 templates/boards/login.html:6
167 167 #: templates/boards/login.html.py:21
168 168 msgid "Login"
169 169 msgstr "Π’Ρ…ΠΎΠ΄"
170 170
171 171 #: templates/boards/base.html:51
172 172 msgid "Archive"
173 173 msgstr "Архив"
174 174
175 175 #: templates/boards/base.html:53
176 176 #, python-format
177 177 msgid "Speed: %(ppd)s posts per day"
178 178 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
179 179
180 180 #: templates/boards/base.html:55
181 181 msgid "Up"
182 182 msgstr "Π’Π²Π΅Ρ€Ρ…"
183 183
184 184 #: templates/boards/login.html:15
185 185 msgid "User ID"
186 186 msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
187 187
188 188 #: templates/boards/login.html:24
189 189 msgid "Insert your user id above"
190 190 msgstr "Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ свой ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π²Ρ‹ΡˆΠ΅"
191 191
192 192 #: templates/boards/post.html:46
193 193 msgid "Open"
194 194 msgstr "ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ"
195 195
196 196 #: templates/boards/post.html:48
197 197 msgid "Reply"
198 198 msgstr "ΠžΡ‚Π²Π΅Ρ‚"
199 199
200 200 #: templates/boards/post.html:55
201 201 msgid "Edit"
202 202 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ"
203 203
204 204 #: templates/boards/post.html:57
205 205 msgid "Delete"
206 206 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ"
207 207
208 208 #: templates/boards/post.html:60
209 209 msgid "Ban IP"
210 210 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ IP"
211 211
212 212 #: templates/boards/post.html:74
213 213 msgid "Replies"
214 214 msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
215 215
216 216 #: templates/boards/post.html:87 templates/boards/thread.html:82
217 217 #: templates/boards/thread_gallery.html:60
218 218 msgid "images"
219 219 msgstr "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
220 220
221 221 #: templates/boards/post_admin.html:19
222 222 msgid "Tags:"
223 223 msgstr "Π’Π΅Π³ΠΈ:"
224 224
225 225 #: templates/boards/post_admin.html:30
226 226 msgid "Add tag"
227 227 msgstr "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Ρ‚Π΅Π³"
228 228
229 229 #: templates/boards/posting_general.html:56
230 230 msgid "Show tag"
231 231 msgstr "ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ Ρ‚Π΅Π³"
232 232
233 233 #: templates/boards/posting_general.html:60
234 234 msgid "Hide tag"
235 235 msgstr "Π‘ΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ Ρ‚Π΅Π³"
236 236
237 237 #: templates/boards/posting_general.html:79
238 238 msgid "Previous page"
239 239 msgstr "ΠŸΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π°Ρ страница"
240 240
241 241 #: templates/boards/posting_general.html:93
242 242 #, python-format
243 243 msgid "Skipped %(count)s replies. Open thread to see all replies."
244 244 msgstr "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
245 245
246 246 #: templates/boards/posting_general.html:119
247 247 msgid "Next page"
248 248 msgstr "Π‘Π»Π΅Π΄ΡƒΡŽΡ‰Π°Ρ страница"
249 249
250 250 #: templates/boards/posting_general.html:124
251 251 msgid "No threads exist. Create the first one!"
252 252 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
253 253
254 254 #: templates/boards/posting_general.html:130
255 255 msgid "Create new thread"
256 256 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
257 257
258 258 #: templates/boards/posting_general.html:134 templates/boards/thread.html:56
259 259 msgid "Post"
260 260 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
261 261
262 262 #: templates/boards/posting_general.html:138
263 263 msgid "Tags must be delimited by spaces. Text or image is required."
264 264 msgstr ""
265 265 "Π’Π΅Π³ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
266 266
267 267 #: templates/boards/posting_general.html:141 templates/boards/thread.html:60
268 268 msgid "Text syntax"
269 269 msgstr "Бинтаксис тСкста"
270 270
271 271 #: templates/boards/posting_general.html:151
272 272 msgid "Pages:"
273 273 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
274 274
275 275 #: templates/boards/settings.html:14
276 276 msgid "User:"
277 277 msgstr "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ:"
278 278
279 279 #: templates/boards/settings.html:16
280 280 msgid "You are moderator."
281 281 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
282 282
283 283 #: templates/boards/settings.html:19
284 msgid "You are veteran."
285 msgstr "Π’Ρ‹ Π²Π΅Ρ‚Π΅Ρ€Π°Π½."
286
287 #: templates/boards/settings.html:22
284 288 msgid "Posts:"
285 289 msgstr "Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠΉ:"
286 290
287 #: templates/boards/settings.html:20
291 #: templates/boards/settings.html:23
288 292 msgid "First access:"
289 293 msgstr "ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ доступ:"
290 294
291 #: templates/boards/settings.html:22
295 #: templates/boards/settings.html:25
292 296 msgid "Last access:"
293 297 msgstr "ПослСдний доступ: "
294 298
295 #: templates/boards/settings.html:26
299 #: templates/boards/settings.html:29
296 300 msgid "Hidden tags:"
297 301 msgstr "Π‘ΠΊΡ€Ρ‹Ρ‚Ρ‹Π΅ Ρ‚Π΅Π³ΠΈ:"
298 302
299 #: templates/boards/settings.html:41
303 #: templates/boards/settings.html:44
300 304 msgid "Save"
301 305 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
302 306
303 307 #: templates/boards/tags.html:22
304 308 msgid "No tags found."
305 309 msgstr "Π’Π΅Π³ΠΈ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹."
306 310
307 311 #: templates/boards/thread.html:20 templates/boards/thread_gallery.html:21
308 312 msgid "Normal mode"
309 313 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π΅ΠΆΠΈΠΌ"
310 314
311 315 #: templates/boards/thread.html:21 templates/boards/thread_gallery.html:22
312 316 msgid "Gallery mode"
313 317 msgstr "Π Π΅ΠΆΠΈΠΌ Π³Π°Π»Π΅Ρ€Π΅ΠΈ"
314 318
315 319 #: templates/boards/thread.html:29
316 320 msgid "posts to bumplimit"
317 321 msgstr "сообщСний Π΄ΠΎ Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π°"
318 322
319 323 #: templates/boards/thread.html:50
320 324 msgid "Reply to thread"
321 325 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
322 326
323 327 #: templates/boards/thread.html:81 templates/boards/thread_gallery.html:59
324 328 msgid "replies"
325 329 msgstr "ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²"
326 330
327 331 #: templates/boards/thread.html:83 templates/boards/thread_gallery.html:61
328 332 msgid "Last update: "
329 333 msgstr "ПослСднСС обновлСниС: "
330 334
331 335 #: templates/boards/rss/post.html:5
332 336 msgid "Post image"
333 337 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
334 338
335 339 #: templates/boards/staticpages/banned.html:6
336 340 msgid "Banned"
337 341 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½"
338 342
339 343 #: templates/boards/staticpages/banned.html:11
340 344 msgid "Your IP address has been banned. Contact the administrator"
341 345 msgstr "Π’Π°Ρˆ IP адрСс Π±Ρ‹Π» Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½. Π‘Π²ΡΠΆΠΈΡ‚Π΅ΡΡŒ с администратором"
342 346
343 347 #: templates/boards/staticpages/help.html:6
344 348 #: templates/boards/staticpages/help.html:10
345 349 msgid "Syntax"
346 350 msgstr "Бинтаксис"
347 351
348 352 #: templates/boards/staticpages/help.html:11
349 353 msgid "2 line breaks for a new line."
350 354 msgstr "2 ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π° строки ΡΠΎΠ·Π΄Π°ΡŽΡ‚ Π½ΠΎΠ²Ρ‹ΠΉ Π°Π±Π·Π°Ρ†."
351 355
352 356 #: templates/boards/staticpages/help.html:12
353 357 msgid "Italic text"
354 358 msgstr "ΠšΡƒΡ€ΡΠΈΠ²Π½Ρ‹ΠΉ тСкст"
355 359
356 360 #: templates/boards/staticpages/help.html:13
357 361 msgid "Bold text"
358 362 msgstr "ΠŸΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ тСкст"
359 363
360 364 #: templates/boards/staticpages/help.html:14
361 365 msgid "Spoiler"
362 366 msgstr "Π‘ΠΏΠΎΠΉΠ»Π΅Ρ€"
363 367
364 368 #: templates/boards/staticpages/help.html:15
365 369 msgid "Link to a post"
366 370 msgstr "Бсылка Π½Π° сообщСниС"
367 371
368 372 #: templates/boards/staticpages/help.html:16
369 373 msgid "Strikethrough text"
370 374 msgstr "Π—Π°Ρ‡Π΅Ρ€ΠΊΠ½ΡƒΡ‚Ρ‹ΠΉ тСкст"
371 375
372 376 #: templates/boards/staticpages/help.html:17
373 377 msgid "You need to new line before:"
374 378 msgstr "ΠŸΠ΅Ρ€Π΅Π΄ этими Ρ‚Π΅Π³Π°ΠΌΠΈ Π½ΡƒΠΆΠ½Π° новая строка:"
375 379
376 380 #: templates/boards/staticpages/help.html:18
377 381 msgid "Comment"
378 382 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
379 383
380 384 #: templates/boards/staticpages/help.html:19
381 385 msgid "Quote"
382 386 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Π°"
@@ -1,123 +1,132 b''
1 1 from django.db import models
2 2 from django.db.models import Count
3 3 from boards import settings
4 4 from boards.models import Post
5 5 from django.core.cache import cache
6 6
7 7 __author__ = 'neko259'
8 8
9 9 RANK_ADMIN = 0
10 10 RANK_MODERATOR = 10
11 11 RANK_USER = 100
12 12
13 13 BAN_REASON_AUTO = 'Auto'
14 14 BAN_REASON_MAX_LENGTH = 200
15 15
16 VETERAN_POSTS = 1000
17
16 18
17 19 class User(models.Model):
18 20
19 21 class Meta:
20 22 app_label = 'boards'
21 23
22 24 user_id = models.CharField(max_length=50)
23 25 rank = models.IntegerField()
24 26
25 27 registration_time = models.DateTimeField()
26 28
27 29 fav_tags = models.ManyToManyField('Tag', null=True, blank=True)
28 30 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
29 31 blank=True)
30 32
31 33 hidden_tags = models.ManyToManyField('Tag', null=True, blank=True,
32 34 related_name='ht+')
33 35 hidden_threads = models.ManyToManyField('Post', null=True, blank=True,
34 36 related_name='hth+')
35 37
36 38 def save_setting(self, name, value):
37 39 setting, created = Setting.objects.get_or_create(name=name, user=self)
38 40 setting.value = str(value)
39 41 setting.save()
40 42
41 43 return setting
42 44
43 45 def get_setting(self, name):
44 46 if Setting.objects.filter(name=name, user=self).exists():
45 47 setting = Setting.objects.get(name=name, user=self)
46 48 setting_value = setting.value
47 49 else:
48 50 setting_value = None
49 51
50 52 return setting_value
51 53
52 54 def is_moderator(self):
53 55 return RANK_MODERATOR >= self.rank
54 56
55 57 def get_sorted_fav_tags(self):
56 58 cache_key = self._get_tag_cache_key()
57 59 fav_tags = cache.get(cache_key)
58 60 if fav_tags:
59 61 return fav_tags
60 62
61 63 tags = self.fav_tags.annotate(Count('threads')) \
62 64 .filter(threads__count__gt=0).order_by('name')
63 65
64 66 if tags:
65 67 cache.set(cache_key, tags)
66 68
67 69 return tags
68 70
69 71 def get_post_count(self):
70 72 return Post.objects.filter(user=self).count()
71 73
72 74 def __unicode__(self):
73 75 return self.user_id + '(' + str(self.rank) + ')'
74 76
75 77 def get_last_access_time(self):
76 78 """
77 79 Gets user's last post time.
78 80 """
79 81
80 82 posts = Post.objects.filter(user=self)
81 83 if posts.exists() > 0:
82 84 return posts.latest('pub_time').pub_time
83 85
84 86 def add_tag(self, tag):
85 87 self.fav_tags.add(tag)
86 88 cache.delete(self._get_tag_cache_key())
87 89
88 90 def remove_tag(self, tag):
89 91 self.fav_tags.remove(tag)
90 92 cache.delete(self._get_tag_cache_key())
91 93
92 94 def hide_tag(self, tag):
93 95 self.hidden_tags.add(tag)
94 96
95 97 def unhide_tag(self, tag):
96 98 self.hidden_tags.remove(tag)
97 99
100 def is_veteran(self):
101 """
102 Returns if a user is old (veteran).
103 """
104
105 return self.get_post_count() >= VETERAN_POSTS
106
98 107 def _get_tag_cache_key(self):
99 108 return self.user_id + '_tags'
100 109
101 110
102 111 class Setting(models.Model):
103 112
104 113 class Meta:
105 114 app_label = 'boards'
106 115
107 116 name = models.CharField(max_length=50)
108 117 value = models.CharField(max_length=50)
109 118 user = models.ForeignKey(User)
110 119
111 120
112 121 class Ban(models.Model):
113 122
114 123 class Meta:
115 124 app_label = 'boards'
116 125
117 126 ip = models.GenericIPAddressField()
118 127 reason = models.CharField(default=BAN_REASON_AUTO,
119 128 max_length=BAN_REASON_MAX_LENGTH)
120 129 can_read = models.BooleanField(default=True)
121 130
122 131 def __unicode__(self):
123 132 return self.ip
@@ -1,47 +1,50 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load humanize %}
5 5
6 6 {% block head %}
7 7 <title>{% trans 'Settings' %} - {{ site_name }}</title>
8 8 {% endblock %}
9 9
10 10 {% block content %}
11 11
12 12 <div class="post">
13 13 <p>
14 14 {% trans 'User:' %} <b>{{ user.user_id }}</b>.
15 15 {% if user.is_moderator %}
16 16 {% trans 'You are moderator.' %}
17 17 {% endif %}
18 {% if user.is_veteran %}
19 {% trans 'You are veteran.' %}
20 {% endif %}
18 21 </p>
19 22 <p>{% trans 'Posts:' %} {{ user.get_post_count }}</p>
20 23 <p>{% trans 'First access:' %} {{ user.registration_time|naturaltime }}</p>
21 24 {% if user.get_last_access_time %}
22 25 <p>{% trans 'Last access:' %} {{ user.get_last_access_time|naturaltime }}</p>
23 26 {% endif %}
24 27 {% with hidden_tags=user.hidden_tags.all %}
25 28 {% if hidden_tags %}
26 29 <p>{% trans 'Hidden tags:' %}
27 30 {% for tag in hidden_tags %}
28 31 <a class="tag" href="{% url 'tag' tag.name %}">
29 32 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
30 33 {% endfor %}
31 34 </p>
32 35 {% endif %}
33 36 {% endwith %}
34 37 </div>
35 38
36 39 <div class="post-form-w">
37 40 <div class="post-form">
38 41 <form method="post">{% csrf_token %}
39 42 {{ form.as_div }}
40 43 <div class="form-submit">
41 44 <input type="submit" value="{% trans "Save" %}" />
42 45 </div>
43 46 </form>
44 47 </div>
45 48 </div>
46 49
47 50 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now