forms.py
339 lines
| 10.0 KiB
| text/x-python
|
PythonLexer
/ boards / forms.py
neko259
|
r69 | import re | ||
neko259
|
r527 | import time | ||
import hashlib | ||||
Ilyas
|
r78 | from captcha.fields import CaptchaField | ||
Ilyas
|
r14 | from django import forms | ||
neko259
|
r76 | from django.forms.util import ErrorList | ||
neko259
|
r205 | from django.utils.translation import ugettext_lazy as _ | ||
neko259
|
r527 | |||
neko259
|
r438 | from boards.mdx_neboard import formatters | ||
neko259
|
r386 | from boards.models.post import TITLE_MAX_LENGTH | ||
neko259
|
r527 | from boards.models import User, Post | ||
neko259
|
r35 | from neboard import settings | ||
wnc_21
|
r95 | from boards import utils | ||
neko259
|
r333 | import boards.settings as board_settings | ||
neko259
|
r76 | |||
neko259
|
r517 | ATTRIBUTE_PLACEHOLDER = 'placeholder' | ||
LAST_POST_TIME = 'last_post_time' | ||||
LAST_LOGIN_TIME = 'last_login_time' | ||||
TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like | ||||
this. 2 new lines are required to start new paragraph.''') | ||||
neko259
|
r521 | TAGS_PLACEHOLDER = _('tag1 several_words_tag') | ||
neko259
|
r211 | |||
neko259
|
r527 | ERROR_IMAGE_DUPLICATE = _('Such image was already posted') | ||
LABEL_TITLE = _('Title') | ||||
LABEL_TEXT = _('Text') | ||||
neko259
|
r566 | LABEL_TAG = _('Tag') | ||
TAG_MAX_LENGTH = 20 | ||||
REGEX_TAG = ur'^[\w\d]+$' | ||||
neko259
|
r527 | |||
neko259
|
r153 | |||
neko259
|
r438 | class FormatPanel(forms.Textarea): | ||
def render(self, name, value, attrs=None): | ||||
output = '<div id="mark-panel">' | ||||
for formatter in formatters: | ||||
output += u'<span class="mark_btn"' + \ | ||||
u' onClick="addMarkToMsg(\'' + formatter.format_left + \ | ||||
'\', \'' + formatter.format_right + '\')">' + \ | ||||
formatter.preview_left + formatter.name + \ | ||||
formatter.preview_right + u'</span>' | ||||
output += '</div>' | ||||
output += super(FormatPanel, self).render(name, value, attrs=None) | ||||
return output | ||||
neko259
|
r76 | class PlainErrorList(ErrorList): | ||
def __unicode__(self): | ||||
return self.as_text() | ||||
def as_text(self): | ||||
return ''.join([u'(!) %s ' % e for e in self]) | ||||
neko259
|
r16 | |||
neko259
|
r205 | class NeboardForm(forms.Form): | ||
neko259
|
r426 | def as_div(self): | ||
neko259
|
r438 | """ | ||
Returns this form rendered as HTML <as_div>s. | ||||
""" | ||||
neko259
|
r205 | return self._html_output( | ||
neko259
|
r425 | # TODO Do not show hidden rows in the list here | ||
neko259
|
r205 | normal_row='<div class="form-row">' | ||
'<div class="form-label">' | ||||
'%(label)s' | ||||
'</div>' | ||||
'<div class="form-input">' | ||||
'%(field)s' | ||||
'</div>' | ||||
'%(help_text)s' | ||||
'</div>', | ||||
neko259
|
r426 | error_row='<div class="form-row">' | ||
'<div class="form-label"></div>' | ||||
'<div class="form-errors">%s</div>' | ||||
'</div>', | ||||
row_ender='</div>', | ||||
help_text_html='%s', | ||||
neko259
|
r205 | errors_on_separate_row=True) | ||
neko259
|
r533 | def as_json_errors(self): | ||
errors = [] | ||||
for name, field in self.fields.items(): | ||||
if self[name].errors: | ||||
errors.append({ | ||||
'field': name, | ||||
'errors': self[name].errors.as_text(), | ||||
}) | ||||
return errors | ||||
neko259
|
r438 | |||
neko259
|
r205 | class PostForm(NeboardForm): | ||
neko259
|
r76 | |||
neko259
|
r232 | title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False, | ||
neko259
|
r527 | label=LABEL_TITLE) | ||
neko259
|
r517 | text = forms.CharField( | ||
widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}), | ||||
neko259
|
r527 | required=False, label=LABEL_TEXT) | ||
neko259
|
r232 | image = forms.ImageField(required=False, label=_('Image')) | ||
neko259
|
r29 | |||
neko259
|
r207 | # This field is for spam prevention only | ||
neko259
|
r232 | email = forms.CharField(max_length=100, required=False, label=_('e-mail'), | ||
widget=forms.TextInput(attrs={ | ||||
'class': 'form-email'})) | ||||
neko259
|
r207 | |||
neko259
|
r153 | session = None | ||
neko259
|
r271 | need_to_ban = False | ||
neko259
|
r153 | |||
neko259
|
r76 | def clean_title(self): | ||
title = self.cleaned_data['title'] | ||||
if title: | ||||
if len(title) > TITLE_MAX_LENGTH: | ||||
neko259
|
r211 | raise forms.ValidationError(_('Title must have less than %s ' | ||
'characters') % | ||||
str(TITLE_MAX_LENGTH)) | ||||
neko259
|
r76 | return title | ||
neko259
|
r29 | def clean_text(self): | ||
text = self.cleaned_data['text'] | ||||
if text: | ||||
neko259
|
r333 | if len(text) > board_settings.MAX_TEXT_LENGTH: | ||
neko259
|
r211 | raise forms.ValidationError(_('Text must have less than %s ' | ||
'characters') % | ||||
neko259
|
r333 | str(board_settings | ||
.MAX_TEXT_LENGTH)) | ||||
neko259
|
r29 | return text | ||
def clean_image(self): | ||||
image = self.cleaned_data['image'] | ||||
if image: | ||||
neko259
|
r333 | if image._size > board_settings.MAX_IMAGE_SIZE: | ||
raise forms.ValidationError( | ||||
_('Image must be less than %s bytes') | ||||
% str(board_settings.MAX_IMAGE_SIZE)) | ||||
neko259
|
r527 | |||
md5 = hashlib.md5() | ||||
for chunk in image.chunks(): | ||||
md5.update(chunk) | ||||
image_hash = md5.hexdigest() | ||||
if Post.objects.filter(image_hash=image_hash).exists(): | ||||
raise forms.ValidationError(ERROR_IMAGE_DUPLICATE) | ||||
neko259
|
r29 | return image | ||
def clean(self): | ||||
cleaned_data = super(PostForm, self).clean() | ||||
neko259
|
r211 | if not self.session: | ||
raise forms.ValidationError('Humans have sessions') | ||||
if cleaned_data['email']: | ||||
neko259
|
r271 | self.need_to_ban = True | ||
neko259
|
r211 | raise forms.ValidationError('A human cannot enter a hidden field') | ||
neko259
|
r207 | |||
neko259
|
r151 | if not self.errors: | ||
self._clean_text_image() | ||||
neko259
|
r77 | |||
neko259
|
r153 | if not self.errors and self.session: | ||
self._validate_posting_speed() | ||||
neko259
|
r76 | return cleaned_data | ||
def _clean_text_image(self): | ||||
text = self.cleaned_data.get('text') | ||||
image = self.cleaned_data.get('image') | ||||
neko259
|
r29 | |||
if (not text) and (not image): | ||||
neko259
|
r205 | error_message = _('Either text or image must be entered.') | ||
neko259
|
r77 | self._errors['text'] = self.error_class([error_message]) | ||
neko259
|
r153 | |||
def _validate_posting_speed(self): | ||||
can_post = True | ||||
if LAST_POST_TIME in self.session: | ||||
now = time.time() | ||||
last_post_time = self.session[LAST_POST_TIME] | ||||
current_delay = int(now - last_post_time) | ||||
if current_delay < settings.POSTING_DELAY: | ||||
neko259
|
r211 | error_message = _('Wait %s seconds after last posting') % str( | ||
settings.POSTING_DELAY - current_delay) | ||||
neko259
|
r153 | self._errors['text'] = self.error_class([error_message]) | ||
can_post = False | ||||
if can_post: | ||||
self.session[LAST_POST_TIME] = time.time() | ||||
neko259
|
r29 | |||
class ThreadForm(PostForm): | ||||
neko259
|
r232 | |||
neko259
|
r71 | regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE) | ||
neko259
|
r232 | |||
neko259
|
r517 | tags = forms.CharField( | ||
widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}), | ||||
max_length=100, label=_('Tags')) | ||||
neko259
|
r31 | |||
def clean_tags(self): | ||||
tags = self.cleaned_data['tags'] | ||||
neko259
|
r69 | |||
neko259
|
r31 | if tags: | ||
neko259
|
r69 | if not self.regex_tags.match(tags): | ||
raise forms.ValidationError( | ||||
neko259
|
r205 | _('Inappropriate characters in tags.')) | ||
neko259
|
r31 | |||
return tags | ||||
def clean(self): | ||||
cleaned_data = super(ThreadForm, self).clean() | ||||
neko259
|
r35 | return cleaned_data | ||
wnc_21
|
r95 | class PostCaptchaForm(PostForm): | ||
captcha = CaptchaField() | ||||
def __init__(self, *args, **kwargs): | ||||
self.request = kwargs['request'] | ||||
del kwargs['request'] | ||||
super(PostCaptchaForm, self).__init__(*args, **kwargs) | ||||
def clean(self): | ||||
cleaned_data = super(PostCaptchaForm, self).clean() | ||||
success = self.is_valid() | ||||
utils.update_captcha_access(self.request, success) | ||||
if success: | ||||
return cleaned_data | ||||
else: | ||||
neko259
|
r205 | raise forms.ValidationError(_("Captcha validation failed")) | ||
wnc_21
|
r95 | |||
Ilyas
|
r78 | class ThreadCaptchaForm(ThreadForm): | ||
captcha = CaptchaField() | ||||
wnc_21
|
r95 | def __init__(self, *args, **kwargs): | ||
self.request = kwargs['request'] | ||||
del kwargs['request'] | ||||
super(ThreadCaptchaForm, self).__init__(*args, **kwargs) | ||||
def clean(self): | ||||
cleaned_data = super(ThreadCaptchaForm, self).clean() | ||||
success = self.is_valid() | ||||
utils.update_captcha_access(self.request, success) | ||||
if success: | ||||
return cleaned_data | ||||
else: | ||||
neko259
|
r205 | raise forms.ValidationError(_("Captcha validation failed")) | ||
wnc_21
|
r95 | |||
Ilyas
|
r78 | |||
neko259
|
r205 | class SettingsForm(NeboardForm): | ||
theme = forms.ChoiceField(choices=settings.THEMES, | ||||
label=_('Theme')) | ||||
neko259
|
r144 | |||
neko259
|
r205 | class ModeratorSettingsForm(SettingsForm): | ||
moderate = forms.BooleanField(required=False, label=_('Enable moderation ' | ||||
'panel')) | ||||
class LoginForm(NeboardForm): | ||||
neko259
|
r211 | |||
neko259
|
r144 | user_id = forms.CharField() | ||
neko259
|
r211 | session = None | ||
neko259
|
r144 | def clean_user_id(self): | ||
user_id = self.cleaned_data['user_id'] | ||||
if user_id: | ||||
users = User.objects.filter(user_id=user_id) | ||||
if len(users) == 0: | ||||
neko259
|
r205 | raise forms.ValidationError(_('No such user found')) | ||
neko259
|
r144 | |||
return user_id | ||||
neko259
|
r211 | def _validate_login_speed(self): | ||
can_post = True | ||||
if LAST_LOGIN_TIME in self.session: | ||||
now = time.time() | ||||
last_login_time = self.session[LAST_LOGIN_TIME] | ||||
current_delay = int(now - last_login_time) | ||||
neko259
|
r333 | if current_delay < board_settings.LOGIN_TIMEOUT: | ||
neko259
|
r211 | error_message = _('Wait %s minutes after last login') % str( | ||
neko259
|
r333 | (board_settings.LOGIN_TIMEOUT - current_delay) / 60) | ||
neko259
|
r211 | self._errors['user_id'] = self.error_class([error_message]) | ||
can_post = False | ||||
if can_post: | ||||
self.session[LAST_LOGIN_TIME] = time.time() | ||||
neko259
|
r144 | def clean(self): | ||
neko259
|
r211 | if not self.session: | ||
raise forms.ValidationError('Humans have sessions') | ||||
self._validate_login_speed() | ||||
neko259
|
r144 | cleaned_data = super(LoginForm, self).clean() | ||
neko259
|
r271 | return cleaned_data | ||
neko259
|
r566 | |||
class AddTagForm(NeboardForm): | ||||
tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG) | ||||
method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag') | ||||
def clean_tag(self): | ||||
tag = self.cleaned_data['tag'] | ||||
regex_tag = re.compile(REGEX_TAG, re.UNICODE) | ||||
if not regex_tag.match(tag): | ||||
raise forms.ValidationError(_('Inappropriate characters in tags.')) | ||||
return tag | ||||
def clean(self): | ||||
cleaned_data = super(AddTagForm, self).clean() | ||||
return cleaned_data | ||||