##// END OF EJS Templates
Now image form field only accepts image files
neko259 -
r675:cec27780 default
parent child Browse files
Show More
@@ -1,351 +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
110
110 # This field is for spam prevention only
111 # This field is for spam prevention only
111 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
112 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
112 widget=forms.TextInput(attrs={
113 widget=forms.TextInput(attrs={
113 'class': 'form-email'}))
114 'class': 'form-email'}))
114
115
115 session = None
116 session = None
116 need_to_ban = False
117 need_to_ban = False
117
118
118 def clean_title(self):
119 def clean_title(self):
119 title = self.cleaned_data['title']
120 title = self.cleaned_data['title']
120 if title:
121 if title:
121 if len(title) > TITLE_MAX_LENGTH:
122 if len(title) > TITLE_MAX_LENGTH:
122 raise forms.ValidationError(_('Title must have less than %s '
123 raise forms.ValidationError(_('Title must have less than %s '
123 'characters') %
124 'characters') %
124 str(TITLE_MAX_LENGTH))
125 str(TITLE_MAX_LENGTH))
125 return title
126 return title
126
127
127 def clean_text(self):
128 def clean_text(self):
128 text = self.cleaned_data['text']
129 text = self.cleaned_data['text']
129 if text:
130 if text:
130 if len(text) > board_settings.MAX_TEXT_LENGTH:
131 if len(text) > board_settings.MAX_TEXT_LENGTH:
131 raise forms.ValidationError(_('Text must have less than %s '
132 raise forms.ValidationError(_('Text must have less than %s '
132 'characters') %
133 'characters') %
133 str(board_settings
134 str(board_settings
134 .MAX_TEXT_LENGTH))
135 .MAX_TEXT_LENGTH))
135 return text
136 return text
136
137
137 def clean_image(self):
138 def clean_image(self):
138 image = self.cleaned_data['image']
139 image = self.cleaned_data['image']
139 if image:
140 if image:
140 if image._size > board_settings.MAX_IMAGE_SIZE:
141 if image._size > board_settings.MAX_IMAGE_SIZE:
141 raise forms.ValidationError(
142 raise forms.ValidationError(
142 _('Image must be less than %s bytes')
143 _('Image must be less than %s bytes')
143 % str(board_settings.MAX_IMAGE_SIZE))
144 % str(board_settings.MAX_IMAGE_SIZE))
144
145
145 md5 = hashlib.md5()
146 md5 = hashlib.md5()
146 for chunk in image.chunks():
147 for chunk in image.chunks():
147 md5.update(chunk)
148 md5.update(chunk)
148 image_hash = md5.hexdigest()
149 image_hash = md5.hexdigest()
149 if Post.objects.filter(image_hash=image_hash).exists():
150 if Post.objects.filter(image_hash=image_hash).exists():
150 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
151 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
151
152
152 return image
153 return image
153
154
154 def clean(self):
155 def clean(self):
155 cleaned_data = super(PostForm, self).clean()
156 cleaned_data = super(PostForm, self).clean()
156
157
157 if not self.session:
158 if not self.session:
158 raise forms.ValidationError('Humans have sessions')
159 raise forms.ValidationError('Humans have sessions')
159
160
160 if cleaned_data['email']:
161 if cleaned_data['email']:
161 self.need_to_ban = True
162 self.need_to_ban = True
162 raise forms.ValidationError('A human cannot enter a hidden field')
163 raise forms.ValidationError('A human cannot enter a hidden field')
163
164
164 if not self.errors:
165 if not self.errors:
165 self._clean_text_image()
166 self._clean_text_image()
166
167
167 if not self.errors and self.session:
168 if not self.errors and self.session:
168 self._validate_posting_speed()
169 self._validate_posting_speed()
169
170
170 return cleaned_data
171 return cleaned_data
171
172
172 def _clean_text_image(self):
173 def _clean_text_image(self):
173 text = self.cleaned_data.get('text')
174 text = self.cleaned_data.get('text')
174 image = self.cleaned_data.get('image')
175 image = self.cleaned_data.get('image')
175
176
176 if (not text) and (not image):
177 if (not text) and (not image):
177 error_message = _('Either text or image must be entered.')
178 error_message = _('Either text or image must be entered.')
178 self._errors['text'] = self.error_class([error_message])
179 self._errors['text'] = self.error_class([error_message])
179
180
180 def _validate_posting_speed(self):
181 def _validate_posting_speed(self):
181 can_post = True
182 can_post = True
182
183
183 # TODO Remove this, it's only for test
184 # TODO Remove this, it's only for test
184 if not 'user_id' in self.session:
185 if not 'user_id' in self.session:
185 return
186 return
186
187
187 user = User.objects.get(id=self.session['user_id'])
188 user = User.objects.get(id=self.session['user_id'])
188 if user.is_veteran():
189 if user.is_veteran():
189 posting_delay = VETERAN_POSTING_DELAY
190 posting_delay = VETERAN_POSTING_DELAY
190 else:
191 else:
191 posting_delay = settings.POSTING_DELAY
192 posting_delay = settings.POSTING_DELAY
192
193
193 if LAST_POST_TIME in self.session:
194 if LAST_POST_TIME in self.session:
194 now = time.time()
195 now = time.time()
195 last_post_time = self.session[LAST_POST_TIME]
196 last_post_time = self.session[LAST_POST_TIME]
196
197
197 current_delay = int(now - last_post_time)
198 current_delay = int(now - last_post_time)
198
199
199 if current_delay < posting_delay:
200 if current_delay < posting_delay:
200 error_message = _('Wait %s seconds after last posting') % str(
201 error_message = _('Wait %s seconds after last posting') % str(
201 posting_delay - current_delay)
202 posting_delay - current_delay)
202 self._errors['text'] = self.error_class([error_message])
203 self._errors['text'] = self.error_class([error_message])
203
204
204 can_post = False
205 can_post = False
205
206
206 if can_post:
207 if can_post:
207 self.session[LAST_POST_TIME] = time.time()
208 self.session[LAST_POST_TIME] = time.time()
208
209
209
210
210 class ThreadForm(PostForm):
211 class ThreadForm(PostForm):
211
212
212 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
213 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
213
214
214 tags = forms.CharField(
215 tags = forms.CharField(
215 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
216 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
216 max_length=100, label=_('Tags'))
217 max_length=100, label=_('Tags'))
217
218
218 def clean_tags(self):
219 def clean_tags(self):
219 tags = self.cleaned_data['tags']
220 tags = self.cleaned_data['tags']
220
221
221 if tags:
222 if tags:
222 if not self.regex_tags.match(tags):
223 if not self.regex_tags.match(tags):
223 raise forms.ValidationError(
224 raise forms.ValidationError(
224 _('Inappropriate characters in tags.'))
225 _('Inappropriate characters in tags.'))
225
226
226 return tags
227 return tags
227
228
228 def clean(self):
229 def clean(self):
229 cleaned_data = super(ThreadForm, self).clean()
230 cleaned_data = super(ThreadForm, self).clean()
230
231
231 return cleaned_data
232 return cleaned_data
232
233
233
234
234 class PostCaptchaForm(PostForm):
235 class PostCaptchaForm(PostForm):
235 captcha = CaptchaField()
236 captcha = CaptchaField()
236
237
237 def __init__(self, *args, **kwargs):
238 def __init__(self, *args, **kwargs):
238 self.request = kwargs['request']
239 self.request = kwargs['request']
239 del kwargs['request']
240 del kwargs['request']
240
241
241 super(PostCaptchaForm, self).__init__(*args, **kwargs)
242 super(PostCaptchaForm, self).__init__(*args, **kwargs)
242
243
243 def clean(self):
244 def clean(self):
244 cleaned_data = super(PostCaptchaForm, self).clean()
245 cleaned_data = super(PostCaptchaForm, self).clean()
245
246
246 success = self.is_valid()
247 success = self.is_valid()
247 utils.update_captcha_access(self.request, success)
248 utils.update_captcha_access(self.request, success)
248
249
249 if success:
250 if success:
250 return cleaned_data
251 return cleaned_data
251 else:
252 else:
252 raise forms.ValidationError(_("Captcha validation failed"))
253 raise forms.ValidationError(_("Captcha validation failed"))
253
254
254
255
255 class ThreadCaptchaForm(ThreadForm):
256 class ThreadCaptchaForm(ThreadForm):
256 captcha = CaptchaField()
257 captcha = CaptchaField()
257
258
258 def __init__(self, *args, **kwargs):
259 def __init__(self, *args, **kwargs):
259 self.request = kwargs['request']
260 self.request = kwargs['request']
260 del kwargs['request']
261 del kwargs['request']
261
262
262 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
263 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
263
264
264 def clean(self):
265 def clean(self):
265 cleaned_data = super(ThreadCaptchaForm, self).clean()
266 cleaned_data = super(ThreadCaptchaForm, self).clean()
266
267
267 success = self.is_valid()
268 success = self.is_valid()
268 utils.update_captcha_access(self.request, success)
269 utils.update_captcha_access(self.request, success)
269
270
270 if success:
271 if success:
271 return cleaned_data
272 return cleaned_data
272 else:
273 else:
273 raise forms.ValidationError(_("Captcha validation failed"))
274 raise forms.ValidationError(_("Captcha validation failed"))
274
275
275
276
276 class SettingsForm(NeboardForm):
277 class SettingsForm(NeboardForm):
277
278
278 theme = forms.ChoiceField(choices=settings.THEMES,
279 theme = forms.ChoiceField(choices=settings.THEMES,
279 label=_('Theme'))
280 label=_('Theme'))
280
281
281
282
282 class ModeratorSettingsForm(SettingsForm):
283 class ModeratorSettingsForm(SettingsForm):
283
284
284 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
285 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
285 'panel'))
286 'panel'))
286
287
287
288
288 class LoginForm(NeboardForm):
289 class LoginForm(NeboardForm):
289
290
290 user_id = forms.CharField()
291 user_id = forms.CharField()
291
292
292 session = None
293 session = None
293
294
294 def clean_user_id(self):
295 def clean_user_id(self):
295 user_id = self.cleaned_data['user_id']
296 user_id = self.cleaned_data['user_id']
296 if user_id:
297 if user_id:
297 users = User.objects.filter(user_id=user_id)
298 users = User.objects.filter(user_id=user_id)
298 if len(users) == 0:
299 if len(users) == 0:
299 raise forms.ValidationError(_('No such user found'))
300 raise forms.ValidationError(_('No such user found'))
300
301
301 return user_id
302 return user_id
302
303
303 def _validate_login_speed(self):
304 def _validate_login_speed(self):
304 can_post = True
305 can_post = True
305
306
306 if LAST_LOGIN_TIME in self.session:
307 if LAST_LOGIN_TIME in self.session:
307 now = time.time()
308 now = time.time()
308 last_login_time = self.session[LAST_LOGIN_TIME]
309 last_login_time = self.session[LAST_LOGIN_TIME]
309
310
310 current_delay = int(now - last_login_time)
311 current_delay = int(now - last_login_time)
311
312
312 if current_delay < board_settings.LOGIN_TIMEOUT:
313 if current_delay < board_settings.LOGIN_TIMEOUT:
313 error_message = _('Wait %s minutes after last login') % str(
314 error_message = _('Wait %s minutes after last login') % str(
314 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
315 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
315 self._errors['user_id'] = self.error_class([error_message])
316 self._errors['user_id'] = self.error_class([error_message])
316
317
317 can_post = False
318 can_post = False
318
319
319 if can_post:
320 if can_post:
320 self.session[LAST_LOGIN_TIME] = time.time()
321 self.session[LAST_LOGIN_TIME] = time.time()
321
322
322 def clean(self):
323 def clean(self):
323 if not self.session:
324 if not self.session:
324 raise forms.ValidationError('Humans have sessions')
325 raise forms.ValidationError('Humans have sessions')
325
326
326 self._validate_login_speed()
327 self._validate_login_speed()
327
328
328 cleaned_data = super(LoginForm, self).clean()
329 cleaned_data = super(LoginForm, self).clean()
329
330
330 return cleaned_data
331 return cleaned_data
331
332
332
333
333 class AddTagForm(NeboardForm):
334 class AddTagForm(NeboardForm):
334
335
335 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
336 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
336 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
337 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
337
338
338 def clean_tag(self):
339 def clean_tag(self):
339 tag = self.cleaned_data['tag']
340 tag = self.cleaned_data['tag']
340
341
341 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
342 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
342 if not regex_tag.match(tag):
343 if not regex_tag.match(tag):
343 raise forms.ValidationError(_('Inappropriate characters in tags.'))
344 raise forms.ValidationError(_('Inappropriate characters in tags.'))
344
345
345 return tag
346 return tag
346
347
347 def clean(self):
348 def clean(self):
348 cleaned_data = super(AddTagForm, self).clean()
349 cleaned_data = super(AddTagForm, self).clean()
349
350
350 return cleaned_data
351 return cleaned_data
351
352
General Comments 0
You need to be logged in to leave comments. Login now