##// END OF EJS Templates
Strip text and tags before saving
neko259 -
r678:fba82bb1 default
parent child Browse files
Show More
@@ -1,352 +1,352 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 text = self.cleaned_data['text']
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 217 max_length=100, label=_('Tags'))
218 218
219 219 def clean_tags(self):
220 tags = self.cleaned_data['tags']
220 tags = self.cleaned_data['tags'].strip()
221 221
222 222 if tags:
223 223 if not self.regex_tags.match(tags):
224 224 raise forms.ValidationError(
225 225 _('Inappropriate characters in tags.'))
226 226
227 227 return tags
228 228
229 229 def clean(self):
230 230 cleaned_data = super(ThreadForm, self).clean()
231 231
232 232 return cleaned_data
233 233
234 234
235 235 class PostCaptchaForm(PostForm):
236 236 captcha = CaptchaField()
237 237
238 238 def __init__(self, *args, **kwargs):
239 239 self.request = kwargs['request']
240 240 del kwargs['request']
241 241
242 242 super(PostCaptchaForm, self).__init__(*args, **kwargs)
243 243
244 244 def clean(self):
245 245 cleaned_data = super(PostCaptchaForm, self).clean()
246 246
247 247 success = self.is_valid()
248 248 utils.update_captcha_access(self.request, success)
249 249
250 250 if success:
251 251 return cleaned_data
252 252 else:
253 253 raise forms.ValidationError(_("Captcha validation failed"))
254 254
255 255
256 256 class ThreadCaptchaForm(ThreadForm):
257 257 captcha = CaptchaField()
258 258
259 259 def __init__(self, *args, **kwargs):
260 260 self.request = kwargs['request']
261 261 del kwargs['request']
262 262
263 263 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
264 264
265 265 def clean(self):
266 266 cleaned_data = super(ThreadCaptchaForm, self).clean()
267 267
268 268 success = self.is_valid()
269 269 utils.update_captcha_access(self.request, success)
270 270
271 271 if success:
272 272 return cleaned_data
273 273 else:
274 274 raise forms.ValidationError(_("Captcha validation failed"))
275 275
276 276
277 277 class SettingsForm(NeboardForm):
278 278
279 279 theme = forms.ChoiceField(choices=settings.THEMES,
280 280 label=_('Theme'))
281 281
282 282
283 283 class ModeratorSettingsForm(SettingsForm):
284 284
285 285 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
286 286 'panel'))
287 287
288 288
289 289 class LoginForm(NeboardForm):
290 290
291 291 user_id = forms.CharField()
292 292
293 293 session = None
294 294
295 295 def clean_user_id(self):
296 296 user_id = self.cleaned_data['user_id']
297 297 if user_id:
298 298 users = User.objects.filter(user_id=user_id)
299 299 if len(users) == 0:
300 300 raise forms.ValidationError(_('No such user found'))
301 301
302 302 return user_id
303 303
304 304 def _validate_login_speed(self):
305 305 can_post = True
306 306
307 307 if LAST_LOGIN_TIME in self.session:
308 308 now = time.time()
309 309 last_login_time = self.session[LAST_LOGIN_TIME]
310 310
311 311 current_delay = int(now - last_login_time)
312 312
313 313 if current_delay < board_settings.LOGIN_TIMEOUT:
314 314 error_message = _('Wait %s minutes after last login') % str(
315 315 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
316 316 self._errors['user_id'] = self.error_class([error_message])
317 317
318 318 can_post = False
319 319
320 320 if can_post:
321 321 self.session[LAST_LOGIN_TIME] = time.time()
322 322
323 323 def clean(self):
324 324 if not self.session:
325 325 raise forms.ValidationError('Humans have sessions')
326 326
327 327 self._validate_login_speed()
328 328
329 329 cleaned_data = super(LoginForm, self).clean()
330 330
331 331 return cleaned_data
332 332
333 333
334 334 class AddTagForm(NeboardForm):
335 335
336 336 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
337 337 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
338 338
339 339 def clean_tag(self):
340 340 tag = self.cleaned_data['tag']
341 341
342 342 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
343 343 if not regex_tag.match(tag):
344 344 raise forms.ValidationError(_('Inappropriate characters in tags.'))
345 345
346 346 return tag
347 347
348 348 def clean(self):
349 349 cleaned_data = super(AddTagForm, self).clean()
350 350
351 351 return cleaned_data
352 352
General Comments 0
You need to be logged in to leave comments. Login now