##// END OF EJS Templates
Pass textarea attributes to fix w3c validation of label
neko259 -
r1408:9d9f4258 default
parent child Browse files
Show More
@@ -1,406 +1,406 b''
1 import hashlib
1 import hashlib
2 import re
2 import re
3 import time
3 import time
4 import logging
4 import logging
5 import pytz
5 import pytz
6
6
7 from django import forms
7 from django import forms
8 from django.core.files.uploadedfile import SimpleUploadedFile
8 from django.core.files.uploadedfile import SimpleUploadedFile
9 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.forms.util import ErrorList
10 from django.forms.util import ErrorList
11 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
11 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
12
12
13 from boards.mdx_neboard import formatters
13 from boards.mdx_neboard import formatters
14 from boards.models.attachment.downloaders import Downloader
14 from boards.models.attachment.downloaders import Downloader
15 from boards.models.post import TITLE_MAX_LENGTH
15 from boards.models.post import TITLE_MAX_LENGTH
16 from boards.models import Tag, Post
16 from boards.models import Tag, Post
17 from boards.utils import validate_file_size, get_file_mimetype, \
17 from boards.utils import validate_file_size, get_file_mimetype, \
18 FILE_EXTENSION_DELIMITER
18 FILE_EXTENSION_DELIMITER
19 from neboard import settings
19 from neboard import settings
20 import boards.settings as board_settings
20 import boards.settings as board_settings
21 import neboard
21 import neboard
22
22
23 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
23 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
24
24
25 VETERAN_POSTING_DELAY = 5
25 VETERAN_POSTING_DELAY = 5
26
26
27 ATTRIBUTE_PLACEHOLDER = 'placeholder'
27 ATTRIBUTE_PLACEHOLDER = 'placeholder'
28 ATTRIBUTE_ROWS = 'rows'
28 ATTRIBUTE_ROWS = 'rows'
29
29
30 LAST_POST_TIME = 'last_post_time'
30 LAST_POST_TIME = 'last_post_time'
31 LAST_LOGIN_TIME = 'last_login_time'
31 LAST_LOGIN_TIME = 'last_login_time'
32 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
32 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
33 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
33 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
34
34
35 LABEL_TITLE = _('Title')
35 LABEL_TITLE = _('Title')
36 LABEL_TEXT = _('Text')
36 LABEL_TEXT = _('Text')
37 LABEL_TAG = _('Tag')
37 LABEL_TAG = _('Tag')
38 LABEL_SEARCH = _('Search')
38 LABEL_SEARCH = _('Search')
39
39
40 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
40 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
41 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
41 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
42
42
43 TAG_MAX_LENGTH = 20
43 TAG_MAX_LENGTH = 20
44
44
45 TEXTAREA_ROWS = 4
45 TEXTAREA_ROWS = 4
46
46
47 TRIPCODE_DELIM = '#'
47 TRIPCODE_DELIM = '#'
48
48
49 # TODO Maybe this may be converted into the database table?
49 # TODO Maybe this may be converted into the database table?
50 MIMETYPE_EXTENSIONS = {
50 MIMETYPE_EXTENSIONS = {
51 'image/jpeg': 'jpeg',
51 'image/jpeg': 'jpeg',
52 'image/png': 'png',
52 'image/png': 'png',
53 'image/gif': 'gif',
53 'image/gif': 'gif',
54 'video/webm': 'webm',
54 'video/webm': 'webm',
55 'application/pdf': 'pdf',
55 'application/pdf': 'pdf',
56 'x-diff': 'diff',
56 'x-diff': 'diff',
57 'image/svg+xml': 'svg',
57 'image/svg+xml': 'svg',
58 'application/x-shockwave-flash': 'swf',
58 'application/x-shockwave-flash': 'swf',
59 }
59 }
60
60
61
61
62 def get_timezones():
62 def get_timezones():
63 timezones = []
63 timezones = []
64 for tz in pytz.common_timezones:
64 for tz in pytz.common_timezones:
65 timezones.append((tz, tz),)
65 timezones.append((tz, tz),)
66 return timezones
66 return timezones
67
67
68
68
69 class FormatPanel(forms.Textarea):
69 class FormatPanel(forms.Textarea):
70 """
70 """
71 Panel for text formatting. Consists of buttons to add different tags to the
71 Panel for text formatting. Consists of buttons to add different tags to the
72 form text area.
72 form text area.
73 """
73 """
74
74
75 def render(self, name, value, attrs=None):
75 def render(self, name, value, attrs=None):
76 output = '<div id="mark-panel">'
76 output = '<div id="mark-panel">'
77 for formatter in formatters:
77 for formatter in formatters:
78 output += '<span class="mark_btn"' + \
78 output += '<span class="mark_btn"' + \
79 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
79 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
80 '\', \'' + formatter.format_right + '\')">' + \
80 '\', \'' + formatter.format_right + '\')">' + \
81 formatter.preview_left + formatter.name + \
81 formatter.preview_left + formatter.name + \
82 formatter.preview_right + '</span>'
82 formatter.preview_right + '</span>'
83
83
84 output += '</div>'
84 output += '</div>'
85 output += super(FormatPanel, self).render(name, value, attrs=None)
85 output += super(FormatPanel, self).render(name, value, attrs=attrs)
86
86
87 return output
87 return output
88
88
89
89
90 class PlainErrorList(ErrorList):
90 class PlainErrorList(ErrorList):
91 def __unicode__(self):
91 def __unicode__(self):
92 return self.as_text()
92 return self.as_text()
93
93
94 def as_text(self):
94 def as_text(self):
95 return ''.join(['(!) %s ' % e for e in self])
95 return ''.join(['(!) %s ' % e for e in self])
96
96
97
97
98 class NeboardForm(forms.Form):
98 class NeboardForm(forms.Form):
99 """
99 """
100 Form with neboard-specific formatting.
100 Form with neboard-specific formatting.
101 """
101 """
102
102
103 def as_div(self):
103 def as_div(self):
104 """
104 """
105 Returns this form rendered as HTML <as_div>s.
105 Returns this form rendered as HTML <as_div>s.
106 """
106 """
107
107
108 return self._html_output(
108 return self._html_output(
109 # TODO Do not show hidden rows in the list here
109 # TODO Do not show hidden rows in the list here
110 normal_row='<div class="form-row">'
110 normal_row='<div class="form-row">'
111 '<div class="form-label">'
111 '<div class="form-label">'
112 '%(label)s'
112 '%(label)s'
113 '</div>'
113 '</div>'
114 '<div class="form-input">'
114 '<div class="form-input">'
115 '%(field)s'
115 '%(field)s'
116 '</div>'
116 '</div>'
117 '</div>'
117 '</div>'
118 '<div class="form-row">'
118 '<div class="form-row">'
119 '%(help_text)s'
119 '%(help_text)s'
120 '</div>',
120 '</div>',
121 error_row='<div class="form-row">'
121 error_row='<div class="form-row">'
122 '<div class="form-label"></div>'
122 '<div class="form-label"></div>'
123 '<div class="form-errors">%s</div>'
123 '<div class="form-errors">%s</div>'
124 '</div>',
124 '</div>',
125 row_ender='</div>',
125 row_ender='</div>',
126 help_text_html='%s',
126 help_text_html='%s',
127 errors_on_separate_row=True)
127 errors_on_separate_row=True)
128
128
129 def as_json_errors(self):
129 def as_json_errors(self):
130 errors = []
130 errors = []
131
131
132 for name, field in list(self.fields.items()):
132 for name, field in list(self.fields.items()):
133 if self[name].errors:
133 if self[name].errors:
134 errors.append({
134 errors.append({
135 'field': name,
135 'field': name,
136 'errors': self[name].errors.as_text(),
136 'errors': self[name].errors.as_text(),
137 })
137 })
138
138
139 return errors
139 return errors
140
140
141
141
142 class PostForm(NeboardForm):
142 class PostForm(NeboardForm):
143
143
144 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
144 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
145 label=LABEL_TITLE,
145 label=LABEL_TITLE,
146 widget=forms.TextInput(
146 widget=forms.TextInput(
147 attrs={ATTRIBUTE_PLACEHOLDER:
147 attrs={ATTRIBUTE_PLACEHOLDER:
148 'test#tripcode'}))
148 'test#tripcode'}))
149 text = forms.CharField(
149 text = forms.CharField(
150 widget=FormatPanel(attrs={
150 widget=FormatPanel(attrs={
151 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
151 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
152 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
152 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
153 }),
153 }),
154 required=False, label=LABEL_TEXT)
154 required=False, label=LABEL_TEXT)
155 file = forms.FileField(required=False, label=_('File'),
155 file = forms.FileField(required=False, label=_('File'),
156 widget=forms.ClearableFileInput(
156 widget=forms.ClearableFileInput(
157 attrs={'accept': 'file/*'}))
157 attrs={'accept': 'file/*'}))
158 file_url = forms.CharField(required=False, label=_('File URL'),
158 file_url = forms.CharField(required=False, label=_('File URL'),
159 widget=forms.TextInput(
159 widget=forms.TextInput(
160 attrs={ATTRIBUTE_PLACEHOLDER:
160 attrs={ATTRIBUTE_PLACEHOLDER:
161 'http://example.com/image.png'}))
161 'http://example.com/image.png'}))
162
162
163 # This field is for spam prevention only
163 # This field is for spam prevention only
164 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
164 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
165 widget=forms.TextInput(attrs={
165 widget=forms.TextInput(attrs={
166 'class': 'form-email'}))
166 'class': 'form-email'}))
167 threads = forms.CharField(required=False, label=_('Additional threads'),
167 threads = forms.CharField(required=False, label=_('Additional threads'),
168 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
168 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
169 '123 456 789'}))
169 '123 456 789'}))
170
170
171 session = None
171 session = None
172 need_to_ban = False
172 need_to_ban = False
173
173
174 def _update_file_extension(self, file):
174 def _update_file_extension(self, file):
175 if file:
175 if file:
176 mimetype = get_file_mimetype(file)
176 mimetype = get_file_mimetype(file)
177 extension = MIMETYPE_EXTENSIONS.get(mimetype)
177 extension = MIMETYPE_EXTENSIONS.get(mimetype)
178 if extension:
178 if extension:
179 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
179 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
180 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
180 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
181
181
182 file.name = new_filename
182 file.name = new_filename
183 else:
183 else:
184 logger = logging.getLogger('boards.forms.extension')
184 logger = logging.getLogger('boards.forms.extension')
185
185
186 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
186 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
187
187
188 def clean_title(self):
188 def clean_title(self):
189 title = self.cleaned_data['title']
189 title = self.cleaned_data['title']
190 if title:
190 if title:
191 if len(title) > TITLE_MAX_LENGTH:
191 if len(title) > TITLE_MAX_LENGTH:
192 raise forms.ValidationError(_('Title must have less than %s '
192 raise forms.ValidationError(_('Title must have less than %s '
193 'characters') %
193 'characters') %
194 str(TITLE_MAX_LENGTH))
194 str(TITLE_MAX_LENGTH))
195 return title
195 return title
196
196
197 def clean_text(self):
197 def clean_text(self):
198 text = self.cleaned_data['text'].strip()
198 text = self.cleaned_data['text'].strip()
199 if text:
199 if text:
200 max_length = board_settings.get_int('Forms', 'MaxTextLength')
200 max_length = board_settings.get_int('Forms', 'MaxTextLength')
201 if len(text) > max_length:
201 if len(text) > max_length:
202 raise forms.ValidationError(_('Text must have less than %s '
202 raise forms.ValidationError(_('Text must have less than %s '
203 'characters') % str(max_length))
203 'characters') % str(max_length))
204 return text
204 return text
205
205
206 def clean_file(self):
206 def clean_file(self):
207 file = self.cleaned_data['file']
207 file = self.cleaned_data['file']
208
208
209 if file:
209 if file:
210 validate_file_size(file.size)
210 validate_file_size(file.size)
211 self._update_file_extension(file)
211 self._update_file_extension(file)
212
212
213 return file
213 return file
214
214
215 def clean_file_url(self):
215 def clean_file_url(self):
216 url = self.cleaned_data['file_url']
216 url = self.cleaned_data['file_url']
217
217
218 file = None
218 file = None
219 if url:
219 if url:
220 file = self._get_file_from_url(url)
220 file = self._get_file_from_url(url)
221
221
222 if not file:
222 if not file:
223 raise forms.ValidationError(_('Invalid URL'))
223 raise forms.ValidationError(_('Invalid URL'))
224 else:
224 else:
225 validate_file_size(file.size)
225 validate_file_size(file.size)
226 self._update_file_extension(file)
226 self._update_file_extension(file)
227
227
228 return file
228 return file
229
229
230 def clean_threads(self):
230 def clean_threads(self):
231 threads_str = self.cleaned_data['threads']
231 threads_str = self.cleaned_data['threads']
232
232
233 if len(threads_str) > 0:
233 if len(threads_str) > 0:
234 threads_id_list = threads_str.split(' ')
234 threads_id_list = threads_str.split(' ')
235
235
236 threads = list()
236 threads = list()
237
237
238 for thread_id in threads_id_list:
238 for thread_id in threads_id_list:
239 try:
239 try:
240 thread = Post.objects.get(id=int(thread_id))
240 thread = Post.objects.get(id=int(thread_id))
241 if not thread.is_opening() or thread.get_thread().archived:
241 if not thread.is_opening() or thread.get_thread().archived:
242 raise ObjectDoesNotExist()
242 raise ObjectDoesNotExist()
243 threads.append(thread)
243 threads.append(thread)
244 except (ObjectDoesNotExist, ValueError):
244 except (ObjectDoesNotExist, ValueError):
245 raise forms.ValidationError(_('Invalid additional thread list'))
245 raise forms.ValidationError(_('Invalid additional thread list'))
246
246
247 return threads
247 return threads
248
248
249 def clean(self):
249 def clean(self):
250 cleaned_data = super(PostForm, self).clean()
250 cleaned_data = super(PostForm, self).clean()
251
251
252 if cleaned_data['email']:
252 if cleaned_data['email']:
253 self.need_to_ban = True
253 self.need_to_ban = True
254 raise forms.ValidationError('A human cannot enter a hidden field')
254 raise forms.ValidationError('A human cannot enter a hidden field')
255
255
256 if not self.errors:
256 if not self.errors:
257 self._clean_text_file()
257 self._clean_text_file()
258
258
259 if not self.errors and self.session:
259 if not self.errors and self.session:
260 self._validate_posting_speed()
260 self._validate_posting_speed()
261
261
262 return cleaned_data
262 return cleaned_data
263
263
264 def get_file(self):
264 def get_file(self):
265 """
265 """
266 Gets file from form or URL.
266 Gets file from form or URL.
267 """
267 """
268
268
269 file = self.cleaned_data['file']
269 file = self.cleaned_data['file']
270 return file or self.cleaned_data['file_url']
270 return file or self.cleaned_data['file_url']
271
271
272 def get_tripcode(self):
272 def get_tripcode(self):
273 title = self.cleaned_data['title']
273 title = self.cleaned_data['title']
274 if title is not None and TRIPCODE_DELIM in title:
274 if title is not None and TRIPCODE_DELIM in title:
275 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
275 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
276 tripcode = hashlib.md5(code.encode()).hexdigest()
276 tripcode = hashlib.md5(code.encode()).hexdigest()
277 else:
277 else:
278 tripcode = ''
278 tripcode = ''
279 return tripcode
279 return tripcode
280
280
281 def get_title(self):
281 def get_title(self):
282 title = self.cleaned_data['title']
282 title = self.cleaned_data['title']
283 if title is not None and TRIPCODE_DELIM in title:
283 if title is not None and TRIPCODE_DELIM in title:
284 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
284 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
285 else:
285 else:
286 return title
286 return title
287
287
288 def _clean_text_file(self):
288 def _clean_text_file(self):
289 text = self.cleaned_data.get('text')
289 text = self.cleaned_data.get('text')
290 file = self.get_file()
290 file = self.get_file()
291
291
292 if (not text) and (not file):
292 if (not text) and (not file):
293 error_message = _('Either text or file must be entered.')
293 error_message = _('Either text or file must be entered.')
294 self._errors['text'] = self.error_class([error_message])
294 self._errors['text'] = self.error_class([error_message])
295
295
296 def _validate_posting_speed(self):
296 def _validate_posting_speed(self):
297 can_post = True
297 can_post = True
298
298
299 posting_delay = settings.POSTING_DELAY
299 posting_delay = settings.POSTING_DELAY
300
300
301 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
301 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
302 now = time.time()
302 now = time.time()
303
303
304 current_delay = 0
304 current_delay = 0
305
305
306 if LAST_POST_TIME not in self.session:
306 if LAST_POST_TIME not in self.session:
307 self.session[LAST_POST_TIME] = now
307 self.session[LAST_POST_TIME] = now
308
308
309 need_delay = True
309 need_delay = True
310 else:
310 else:
311 last_post_time = self.session.get(LAST_POST_TIME)
311 last_post_time = self.session.get(LAST_POST_TIME)
312 current_delay = int(now - last_post_time)
312 current_delay = int(now - last_post_time)
313
313
314 need_delay = current_delay < posting_delay
314 need_delay = current_delay < posting_delay
315
315
316 if need_delay:
316 if need_delay:
317 delay = posting_delay - current_delay
317 delay = posting_delay - current_delay
318 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
318 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
319 delay) % {'delay': delay}
319 delay) % {'delay': delay}
320 self._errors['text'] = self.error_class([error_message])
320 self._errors['text'] = self.error_class([error_message])
321
321
322 can_post = False
322 can_post = False
323
323
324 if can_post:
324 if can_post:
325 self.session[LAST_POST_TIME] = now
325 self.session[LAST_POST_TIME] = now
326
326
327 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
327 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
328 """
328 """
329 Gets an file file from URL.
329 Gets an file file from URL.
330 """
330 """
331
331
332 img_temp = None
332 img_temp = None
333
333
334 try:
334 try:
335 for downloader in Downloader.__subclasses__():
335 for downloader in Downloader.__subclasses__():
336 if downloader.handles(url):
336 if downloader.handles(url):
337 return downloader.download(url)
337 return downloader.download(url)
338 # If nobody of the specific downloaders handles this, use generic
338 # If nobody of the specific downloaders handles this, use generic
339 # one
339 # one
340 return Downloader.download(url)
340 return Downloader.download(url)
341 except forms.ValidationError as e:
341 except forms.ValidationError as e:
342 raise e
342 raise e
343 except Exception as e:
343 except Exception as e:
344 # Just return no file
344 # Just return no file
345 pass
345 pass
346
346
347
347
348 class ThreadForm(PostForm):
348 class ThreadForm(PostForm):
349
349
350 tags = forms.CharField(
350 tags = forms.CharField(
351 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
351 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
352 max_length=100, label=_('Tags'), required=True)
352 max_length=100, label=_('Tags'), required=True)
353
353
354 def clean_tags(self):
354 def clean_tags(self):
355 tags = self.cleaned_data['tags'].strip()
355 tags = self.cleaned_data['tags'].strip()
356
356
357 if not tags or not REGEX_TAGS.match(tags):
357 if not tags or not REGEX_TAGS.match(tags):
358 raise forms.ValidationError(
358 raise forms.ValidationError(
359 _('Inappropriate characters in tags.'))
359 _('Inappropriate characters in tags.'))
360
360
361 required_tag_exists = False
361 required_tag_exists = False
362 tag_set = set()
362 tag_set = set()
363 for tag_string in tags.split():
363 for tag_string in tags.split():
364 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
364 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
365 tag_set.add(tag)
365 tag_set.add(tag)
366
366
367 # If this is a new tag, don't check for its parents because nobody
367 # If this is a new tag, don't check for its parents because nobody
368 # added them yet
368 # added them yet
369 if not created:
369 if not created:
370 tag_set |= set(tag.get_all_parents())
370 tag_set |= set(tag.get_all_parents())
371
371
372 for tag in tag_set:
372 for tag in tag_set:
373 if tag.required:
373 if tag.required:
374 required_tag_exists = True
374 required_tag_exists = True
375 break
375 break
376
376
377 if not required_tag_exists:
377 if not required_tag_exists:
378 raise forms.ValidationError(
378 raise forms.ValidationError(
379 _('Need at least one section.'))
379 _('Need at least one section.'))
380
380
381 return tag_set
381 return tag_set
382
382
383 def clean(self):
383 def clean(self):
384 cleaned_data = super(ThreadForm, self).clean()
384 cleaned_data = super(ThreadForm, self).clean()
385
385
386 return cleaned_data
386 return cleaned_data
387
387
388
388
389 class SettingsForm(NeboardForm):
389 class SettingsForm(NeboardForm):
390
390
391 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
391 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
392 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
392 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
393 username = forms.CharField(label=_('User name'), required=False)
393 username = forms.CharField(label=_('User name'), required=False)
394 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
394 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
395
395
396 def clean_username(self):
396 def clean_username(self):
397 username = self.cleaned_data['username']
397 username = self.cleaned_data['username']
398
398
399 if username and not REGEX_TAGS.match(username):
399 if username and not REGEX_TAGS.match(username):
400 raise forms.ValidationError(_('Inappropriate characters.'))
400 raise forms.ValidationError(_('Inappropriate characters.'))
401
401
402 return username
402 return username
403
403
404
404
405 class SearchForm(NeboardForm):
405 class SearchForm(NeboardForm):
406 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
406 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
General Comments 0
You need to be logged in to leave comments. Login now