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