##// END OF EJS Templates
Disallow threads with space-only tags
neko259 -
r679:d10ed955 default
parent child Browse files
Show More
@@ -1,352 +1,351 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 17 VETERAN_POSTING_DELAY = 5
18 18
19 19 ATTRIBUTE_PLACEHOLDER = 'placeholder'
20 20
21 21 LAST_POST_TIME = 'last_post_time'
22 22 LAST_LOGIN_TIME = 'last_login_time'
23 23 TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like
24 24 this. 2 new lines are required to start new paragraph.''')
25 25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
26 26
27 27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
28 28
29 29 LABEL_TITLE = _('Title')
30 30 LABEL_TEXT = _('Text')
31 31 LABEL_TAG = _('Tag')
32 32
33 33 TAG_MAX_LENGTH = 20
34 34
35 35 REGEX_TAG = ur'^[\w\d]+$'
36 36
37 37
38 38 class FormatPanel(forms.Textarea):
39 39 def render(self, name, value, attrs=None):
40 40 output = '<div id="mark-panel">'
41 41 for formatter in formatters:
42 42 output += u'<span class="mark_btn"' + \
43 43 u' onClick="addMarkToMsg(\'' + formatter.format_left + \
44 44 '\', \'' + formatter.format_right + '\')">' + \
45 45 formatter.preview_left + formatter.name + \
46 46 formatter.preview_right + u'</span>'
47 47
48 48 output += '</div>'
49 49 output += super(FormatPanel, self).render(name, value, attrs=None)
50 50
51 51 return output
52 52
53 53
54 54 class PlainErrorList(ErrorList):
55 55 def __unicode__(self):
56 56 return self.as_text()
57 57
58 58 def as_text(self):
59 59 return ''.join([u'(!) %s ' % e for e in self])
60 60
61 61
62 62 class NeboardForm(forms.Form):
63 63
64 64 def as_div(self):
65 65 """
66 66 Returns this form rendered as HTML <as_div>s.
67 67 """
68 68
69 69 return self._html_output(
70 70 # TODO Do not show hidden rows in the list here
71 71 normal_row='<div class="form-row">'
72 72 '<div class="form-label">'
73 73 '%(label)s'
74 74 '</div>'
75 75 '<div class="form-input">'
76 76 '%(field)s'
77 77 '</div>'
78 78 '%(help_text)s'
79 79 '</div>',
80 80 error_row='<div class="form-row">'
81 81 '<div class="form-label"></div>'
82 82 '<div class="form-errors">%s</div>'
83 83 '</div>',
84 84 row_ender='</div>',
85 85 help_text_html='%s',
86 86 errors_on_separate_row=True)
87 87
88 88 def as_json_errors(self):
89 89 errors = []
90 90
91 91 for name, field in self.fields.items():
92 92 if self[name].errors:
93 93 errors.append({
94 94 'field': name,
95 95 'errors': self[name].errors.as_text(),
96 96 })
97 97
98 98 return errors
99 99
100 100
101 101 class PostForm(NeboardForm):
102 102
103 103 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
104 104 label=LABEL_TITLE)
105 105 text = forms.CharField(
106 106 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
107 107 required=False, label=LABEL_TEXT)
108 108 image = forms.ImageField(required=False, label=_('Image'),
109 109 widget=forms.ClearableFileInput(attrs={'accept': 'image/*'}))
110 110
111 111 # This field is for spam prevention only
112 112 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
113 113 widget=forms.TextInput(attrs={
114 114 'class': 'form-email'}))
115 115
116 116 session = None
117 117 need_to_ban = False
118 118
119 119 def clean_title(self):
120 120 title = self.cleaned_data['title']
121 121 if title:
122 122 if len(title) > TITLE_MAX_LENGTH:
123 123 raise forms.ValidationError(_('Title must have less than %s '
124 124 'characters') %
125 125 str(TITLE_MAX_LENGTH))
126 126 return title
127 127
128 128 def clean_text(self):
129 129 text = self.cleaned_data['text'].strip()
130 130 if text:
131 131 if len(text) > board_settings.MAX_TEXT_LENGTH:
132 132 raise forms.ValidationError(_('Text must have less than %s '
133 133 'characters') %
134 134 str(board_settings
135 135 .MAX_TEXT_LENGTH))
136 136 return text
137 137
138 138 def clean_image(self):
139 139 image = self.cleaned_data['image']
140 140 if image:
141 141 if image._size > board_settings.MAX_IMAGE_SIZE:
142 142 raise forms.ValidationError(
143 143 _('Image must be less than %s bytes')
144 144 % str(board_settings.MAX_IMAGE_SIZE))
145 145
146 146 md5 = hashlib.md5()
147 147 for chunk in image.chunks():
148 148 md5.update(chunk)
149 149 image_hash = md5.hexdigest()
150 150 if Post.objects.filter(image_hash=image_hash).exists():
151 151 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
152 152
153 153 return image
154 154
155 155 def clean(self):
156 156 cleaned_data = super(PostForm, self).clean()
157 157
158 158 if not self.session:
159 159 raise forms.ValidationError('Humans have sessions')
160 160
161 161 if cleaned_data['email']:
162 162 self.need_to_ban = True
163 163 raise forms.ValidationError('A human cannot enter a hidden field')
164 164
165 165 if not self.errors:
166 166 self._clean_text_image()
167 167
168 168 if not self.errors and self.session:
169 169 self._validate_posting_speed()
170 170
171 171 return cleaned_data
172 172
173 173 def _clean_text_image(self):
174 174 text = self.cleaned_data.get('text')
175 175 image = self.cleaned_data.get('image')
176 176
177 177 if (not text) and (not image):
178 178 error_message = _('Either text or image must be entered.')
179 179 self._errors['text'] = self.error_class([error_message])
180 180
181 181 def _validate_posting_speed(self):
182 182 can_post = True
183 183
184 184 # TODO Remove this, it's only for test
185 185 if not 'user_id' in self.session:
186 186 return
187 187
188 188 user = User.objects.get(id=self.session['user_id'])
189 189 if user.is_veteran():
190 190 posting_delay = VETERAN_POSTING_DELAY
191 191 else:
192 192 posting_delay = settings.POSTING_DELAY
193 193
194 194 if LAST_POST_TIME in self.session:
195 195 now = time.time()
196 196 last_post_time = self.session[LAST_POST_TIME]
197 197
198 198 current_delay = int(now - last_post_time)
199 199
200 200 if current_delay < posting_delay:
201 201 error_message = _('Wait %s seconds after last posting') % str(
202 202 posting_delay - current_delay)
203 203 self._errors['text'] = self.error_class([error_message])
204 204
205 205 can_post = False
206 206
207 207 if can_post:
208 208 self.session[LAST_POST_TIME] = time.time()
209 209
210 210
211 211 class ThreadForm(PostForm):
212 212
213 213 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
214 214
215 215 tags = forms.CharField(
216 216 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
217 max_length=100, label=_('Tags'))
217 max_length=100, label=_('Tags'), required=True)
218 218
219 219 def clean_tags(self):
220 220 tags = self.cleaned_data['tags'].strip()
221 221
222 if tags:
223 if not self.regex_tags.match(tags):
224 raise forms.ValidationError(
225 _('Inappropriate characters in tags.'))
222 if not tags or not self.regex_tags.match(tags):
223 raise forms.ValidationError(
224 _('Inappropriate characters in tags.'))
226 225
227 226 return tags
228 227
229 228 def clean(self):
230 229 cleaned_data = super(ThreadForm, self).clean()
231 230
232 231 return cleaned_data
233 232
234 233
235 234 class PostCaptchaForm(PostForm):
236 235 captcha = CaptchaField()
237 236
238 237 def __init__(self, *args, **kwargs):
239 238 self.request = kwargs['request']
240 239 del kwargs['request']
241 240
242 241 super(PostCaptchaForm, self).__init__(*args, **kwargs)
243 242
244 243 def clean(self):
245 244 cleaned_data = super(PostCaptchaForm, self).clean()
246 245
247 246 success = self.is_valid()
248 247 utils.update_captcha_access(self.request, success)
249 248
250 249 if success:
251 250 return cleaned_data
252 251 else:
253 252 raise forms.ValidationError(_("Captcha validation failed"))
254 253
255 254
256 255 class ThreadCaptchaForm(ThreadForm):
257 256 captcha = CaptchaField()
258 257
259 258 def __init__(self, *args, **kwargs):
260 259 self.request = kwargs['request']
261 260 del kwargs['request']
262 261
263 262 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
264 263
265 264 def clean(self):
266 265 cleaned_data = super(ThreadCaptchaForm, self).clean()
267 266
268 267 success = self.is_valid()
269 268 utils.update_captcha_access(self.request, success)
270 269
271 270 if success:
272 271 return cleaned_data
273 272 else:
274 273 raise forms.ValidationError(_("Captcha validation failed"))
275 274
276 275
277 276 class SettingsForm(NeboardForm):
278 277
279 278 theme = forms.ChoiceField(choices=settings.THEMES,
280 279 label=_('Theme'))
281 280
282 281
283 282 class ModeratorSettingsForm(SettingsForm):
284 283
285 284 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
286 285 'panel'))
287 286
288 287
289 288 class LoginForm(NeboardForm):
290 289
291 290 user_id = forms.CharField()
292 291
293 292 session = None
294 293
295 294 def clean_user_id(self):
296 295 user_id = self.cleaned_data['user_id']
297 296 if user_id:
298 297 users = User.objects.filter(user_id=user_id)
299 298 if len(users) == 0:
300 299 raise forms.ValidationError(_('No such user found'))
301 300
302 301 return user_id
303 302
304 303 def _validate_login_speed(self):
305 304 can_post = True
306 305
307 306 if LAST_LOGIN_TIME in self.session:
308 307 now = time.time()
309 308 last_login_time = self.session[LAST_LOGIN_TIME]
310 309
311 310 current_delay = int(now - last_login_time)
312 311
313 312 if current_delay < board_settings.LOGIN_TIMEOUT:
314 313 error_message = _('Wait %s minutes after last login') % str(
315 314 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
316 315 self._errors['user_id'] = self.error_class([error_message])
317 316
318 317 can_post = False
319 318
320 319 if can_post:
321 320 self.session[LAST_LOGIN_TIME] = time.time()
322 321
323 322 def clean(self):
324 323 if not self.session:
325 324 raise forms.ValidationError('Humans have sessions')
326 325
327 326 self._validate_login_speed()
328 327
329 328 cleaned_data = super(LoginForm, self).clean()
330 329
331 330 return cleaned_data
332 331
333 332
334 333 class AddTagForm(NeboardForm):
335 334
336 335 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
337 336 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
338 337
339 338 def clean_tag(self):
340 339 tag = self.cleaned_data['tag']
341 340
342 341 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
343 342 if not regex_tag.match(tag):
344 343 raise forms.ValidationError(_('Inappropriate characters in tags.'))
345 344
346 345 return tag
347 346
348 347 def clean(self):
349 348 cleaned_data = super(AddTagForm, self).clean()
350 349
351 350 return cleaned_data
352 351
General Comments 0
You need to be logged in to leave comments. Login now