##// END OF EJS Templates
Compatibility with django 2.1
neko259 -
r2128:90fde64a default
parent child Browse files
Show More
@@ -1,541 +1,541 b''
1 import logging
1 import logging
2
2
3 import pytz
3 import pytz
4 import re
4 import re
5 from PIL import Image
5 from PIL import Image
6 from django import forms
6 from django import forms
7 from django.core.files.images import get_image_dimensions
7 from django.core.files.images import get_image_dimensions
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
9 from django.forms.utils import ErrorList
9 from django.forms.utils import ErrorList
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
11
11
12 import boards.settings as board_settings
12 import boards.settings as board_settings
13 from boards import utils
13 from boards import utils
14 from boards.abstracts.constants import REGEX_TAGS
14 from boards.abstracts.constants import REGEX_TAGS
15 from boards.abstracts.settingsmanager import get_settings_manager, \
15 from boards.abstracts.settingsmanager import get_settings_manager, \
16 SETTING_CONFIRMED_USER
16 SETTING_CONFIRMED_USER
17 from boards.abstracts.sticker_factory import get_attachment_by_alias
17 from boards.abstracts.sticker_factory import get_attachment_by_alias
18 from boards.forms.fields import UrlFileField
18 from boards.forms.fields import UrlFileField
19 from boards.forms.validators import TimeValidator, PowValidator
19 from boards.forms.validators import TimeValidator, PowValidator
20 from boards.mdx_neboard import formatters
20 from boards.mdx_neboard import formatters
21 from boards.models import Attachment
21 from boards.models import Attachment
22 from boards.models import Tag
22 from boards.models import Tag
23 from boards.models.attachment import StickerPack
23 from boards.models.attachment import StickerPack
24 from boards.models.attachment.downloaders import download, REGEX_MAGNET
24 from boards.models.attachment.downloaders import download, REGEX_MAGNET
25 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
25 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
26 from boards.models.post import TITLE_MAX_LENGTH
26 from boards.models.post import TITLE_MAX_LENGTH
27 from boards.settings import SECTION_FORMS
27 from boards.settings import SECTION_FORMS
28 from boards.utils import validate_file_size, get_file_mimetype, \
28 from boards.utils import validate_file_size, get_file_mimetype, \
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
30
30
31 FORMAT_PANEL_BUTTON = '<span class="mark_btn" ' \
31 FORMAT_PANEL_BUTTON = '<span class="mark_btn" ' \
32 'onClick="addMarkToMsg(\'{}\', {}, \'{}\')">{}{}{}</span>'
32 'onClick="addMarkToMsg(\'{}\', {}, \'{}\')">{}{}{}</span>'
33
33
34
34
35 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
35 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
36 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
36 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
37
37
38 VETERAN_POSTING_DELAY = 5
38 VETERAN_POSTING_DELAY = 5
39
39
40 ATTRIBUTE_PLACEHOLDER = 'placeholder'
40 ATTRIBUTE_PLACEHOLDER = 'placeholder'
41 ATTRIBUTE_ROWS = 'rows'
41 ATTRIBUTE_ROWS = 'rows'
42
42
43 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
43 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
44 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
44 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
45
45
46 LABEL_TITLE = _('Title')
46 LABEL_TITLE = _('Title')
47 LABEL_TEXT = _('Text')
47 LABEL_TEXT = _('Text')
48 LABEL_TAG = _('Tag')
48 LABEL_TAG = _('Tag')
49 LABEL_SEARCH = _('Search')
49 LABEL_SEARCH = _('Search')
50 LABEL_FILE = _('File')
50 LABEL_FILE = _('File')
51 LABEL_DUPLICATES = _('Check for duplicates')
51 LABEL_DUPLICATES = _('Check for duplicates')
52 LABEL_URL = _('Do not download URLs')
52 LABEL_URL = _('Do not download URLs')
53
53
54 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
54 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
55 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
55 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
56 ERROR_DUPLICATES = 'Some files are already present on the board.'
56 ERROR_DUPLICATES = 'Some files are already present on the board.'
57
57
58 TAG_MAX_LENGTH = 20
58 TAG_MAX_LENGTH = 20
59
59
60 TEXTAREA_ROWS = 4
60 TEXTAREA_ROWS = 4
61
61
62 TRIPCODE_DELIM = '##'
62 TRIPCODE_DELIM = '##'
63
63
64 # TODO Maybe this may be converted into the database table?
64 # TODO Maybe this may be converted into the database table?
65 MIMETYPE_EXTENSIONS = {
65 MIMETYPE_EXTENSIONS = {
66 'image/jpeg': 'jpeg',
66 'image/jpeg': 'jpeg',
67 'image/png': 'png',
67 'image/png': 'png',
68 'image/gif': 'gif',
68 'image/gif': 'gif',
69 'video/webm': 'webm',
69 'video/webm': 'webm',
70 'application/pdf': 'pdf',
70 'application/pdf': 'pdf',
71 'x-diff': 'diff',
71 'x-diff': 'diff',
72 'image/svg+xml': 'svg',
72 'image/svg+xml': 'svg',
73 'application/x-shockwave-flash': 'swf',
73 'application/x-shockwave-flash': 'swf',
74 'image/x-ms-bmp': 'bmp',
74 'image/x-ms-bmp': 'bmp',
75 'image/bmp': 'bmp',
75 'image/bmp': 'bmp',
76 }
76 }
77
77
78 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
78 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
79 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
79 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
80 DOWN_MODE_URL = 'URL'
80 DOWN_MODE_URL = 'URL'
81 DOWN_MODE_TRY = 'TRY'
81 DOWN_MODE_TRY = 'TRY'
82
82
83
83
84 logger = logging.getLogger('boards.forms')
84 logger = logging.getLogger('boards.forms')
85
85
86
86
87 def get_timezones():
87 def get_timezones():
88 timezones = []
88 timezones = []
89 for tz in pytz.common_timezones:
89 for tz in pytz.common_timezones:
90 timezones.append((tz, tz),)
90 timezones.append((tz, tz),)
91 return timezones
91 return timezones
92
92
93
93
94 class FormatPanel(forms.Textarea):
94 class FormatPanel(forms.Textarea):
95 """
95 """
96 Panel for text formatting. Consists of buttons to add different tags to the
96 Panel for text formatting. Consists of buttons to add different tags to the
97 form text area.
97 form text area.
98 """
98 """
99
99
100 def render(self, name, value, attrs=None):
100 def render(self, name, value, attrs=None, renderer=None):
101 output_template = '<div id="mark-panel">{}</div>'
101 output_template = '<div id="mark-panel">{}</div>'
102
102
103 buttons = [self._get_button(formatter) for formatter in formatters]
103 buttons = [self._get_button(formatter) for formatter in formatters]
104
104
105 output = output_template.format(''.join(buttons))
105 output = output_template.format(''.join(buttons))
106
106
107 output += super(FormatPanel, self).render(name, value, attrs=attrs)
107 output += super(FormatPanel, self).render(name, value, attrs=attrs)
108
108
109 return output
109 return output
110
110
111 def _get_button(self, formatter):
111 def _get_button(self, formatter):
112 return FORMAT_PANEL_BUTTON.format(
112 return FORMAT_PANEL_BUTTON.format(
113 formatter.tag_name,
113 formatter.tag_name,
114 formatter.has_input,
114 formatter.has_input,
115 formatter.input_prompt,
115 formatter.input_prompt,
116 formatter.preview_left,
116 formatter.preview_left,
117 formatter.name,
117 formatter.name,
118 formatter.preview_right)
118 formatter.preview_right)
119
119
120
120
121 class PlainErrorList(ErrorList):
121 class PlainErrorList(ErrorList):
122 def __unicode__(self):
122 def __unicode__(self):
123 return self.as_text()
123 return self.as_text()
124
124
125 def as_text(self):
125 def as_text(self):
126 return ''.join(['(!) %s ' % e for e in self])
126 return ''.join(['(!) %s ' % e for e in self])
127
127
128
128
129 class NeboardForm(forms.Form):
129 class NeboardForm(forms.Form):
130 """
130 """
131 Form with neboard-specific formatting.
131 Form with neboard-specific formatting.
132 """
132 """
133 required_css_class = 'required-field'
133 required_css_class = 'required-field'
134
134
135 def as_div(self):
135 def as_div(self):
136 """
136 """
137 Returns this form rendered as HTML <as_div>s.
137 Returns this form rendered as HTML <as_div>s.
138 """
138 """
139
139
140 return self._html_output(
140 return self._html_output(
141 # TODO Do not show hidden rows in the list here
141 # TODO Do not show hidden rows in the list here
142 normal_row='<div class="form-row">'
142 normal_row='<div class="form-row">'
143 '<div class="form-label">'
143 '<div class="form-label">'
144 '%(label)s'
144 '%(label)s'
145 '</div>'
145 '</div>'
146 '<div class="form-input">'
146 '<div class="form-input">'
147 '%(field)s'
147 '%(field)s'
148 '</div>'
148 '</div>'
149 '</div>'
149 '</div>'
150 '<div class="form-row">'
150 '<div class="form-row">'
151 '%(help_text)s'
151 '%(help_text)s'
152 '</div>',
152 '</div>',
153 error_row='<div class="form-row">'
153 error_row='<div class="form-row">'
154 '<div class="form-label"></div>'
154 '<div class="form-label"></div>'
155 '<div class="form-errors">%s</div>'
155 '<div class="form-errors">%s</div>'
156 '</div>',
156 '</div>',
157 row_ender='</div>',
157 row_ender='</div>',
158 help_text_html='%s',
158 help_text_html='%s',
159 errors_on_separate_row=True)
159 errors_on_separate_row=True)
160
160
161 def as_json_errors(self):
161 def as_json_errors(self):
162 errors = []
162 errors = []
163
163
164 for name, field in list(self.fields.items()):
164 for name, field in list(self.fields.items()):
165 if self[name].errors:
165 if self[name].errors:
166 errors.append({
166 errors.append({
167 'field': name,
167 'field': name,
168 'errors': self[name].errors.as_text(),
168 'errors': self[name].errors.as_text(),
169 })
169 })
170
170
171 return errors
171 return errors
172
172
173
173
174 class PostForm(NeboardForm):
174 class PostForm(NeboardForm):
175
175
176 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
176 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
177 label=LABEL_TITLE,
177 label=LABEL_TITLE,
178 widget=forms.TextInput(
178 widget=forms.TextInput(
179 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
179 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
180 text = forms.CharField(
180 text = forms.CharField(
181 widget=FormatPanel(attrs={
181 widget=FormatPanel(attrs={
182 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
182 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
183 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
183 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
184 }),
184 }),
185 required=False, label=LABEL_TEXT)
185 required=False, label=LABEL_TEXT)
186 download_mode = forms.ChoiceField(
186 download_mode = forms.ChoiceField(
187 choices=(
187 choices=(
188 (DOWN_MODE_TRY, _('Download or insert as URLs')),
188 (DOWN_MODE_TRY, _('Download or insert as URLs')),
189 (DOWN_MODE_DOWNLOAD, _('Download')),
189 (DOWN_MODE_DOWNLOAD, _('Download')),
190 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
190 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
191 (DOWN_MODE_URL, _('Insert as URLs')),
191 (DOWN_MODE_URL, _('Insert as URLs')),
192 ),
192 ),
193 initial=DOWN_MODE_TRY,
193 initial=DOWN_MODE_TRY,
194 label=_('File process mode'))
194 label=_('File process mode'))
195 file = UrlFileField(required=False, label=LABEL_FILE)
195 file = UrlFileField(required=False, label=LABEL_FILE)
196
196
197 # This field is for spam prevention only
197 # This field is for spam prevention only
198 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
198 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
199 widget=forms.TextInput(attrs={
199 widget=forms.TextInput(attrs={
200 'class': 'form-email'}))
200 'class': 'form-email'}))
201 subscribe = forms.BooleanField(required=False,
201 subscribe = forms.BooleanField(required=False,
202 label=_('Subscribe to thread'))
202 label=_('Subscribe to thread'))
203
203
204 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
204 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
205 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
205 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
206 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
206 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
207
207
208 need_to_ban = False
208 need_to_ban = False
209
209
210 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
210 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
211 initial=None, error_class=ErrorList, label_suffix=None,
211 initial=None, error_class=ErrorList, label_suffix=None,
212 empty_permitted=False, field_order=None,
212 empty_permitted=False, field_order=None,
213 use_required_attribute=None, renderer=None, session=None):
213 use_required_attribute=None, renderer=None, session=None):
214 super().__init__(data, files, auto_id, prefix, initial, error_class,
214 super().__init__(data, files, auto_id, prefix, initial, error_class,
215 label_suffix, empty_permitted, field_order,
215 label_suffix, empty_permitted, field_order,
216 use_required_attribute, renderer)
216 use_required_attribute, renderer)
217
217
218 self.session = session
218 self.session = session
219
219
220 def clean_title(self):
220 def clean_title(self):
221 title = self.cleaned_data['title']
221 title = self.cleaned_data['title']
222 if title:
222 if title:
223 if len(title) > TITLE_MAX_LENGTH:
223 if len(title) > TITLE_MAX_LENGTH:
224 raise forms.ValidationError(_('Title must have less than %s '
224 raise forms.ValidationError(_('Title must have less than %s '
225 'characters') %
225 'characters') %
226 str(TITLE_MAX_LENGTH))
226 str(TITLE_MAX_LENGTH))
227 return title
227 return title
228
228
229 def clean_text(self):
229 def clean_text(self):
230 text = self.cleaned_data['text'].strip()
230 text = self.cleaned_data['text'].strip()
231 if text:
231 if text:
232 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
232 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
233 if len(text) > max_length:
233 if len(text) > max_length:
234 raise forms.ValidationError(_('Text must have less than %s '
234 raise forms.ValidationError(_('Text must have less than %s '
235 'characters') % str(max_length))
235 'characters') % str(max_length))
236 return text
236 return text
237
237
238 def clean_file(self):
238 def clean_file(self):
239 return self._clean_files(self.cleaned_data['file'])
239 return self._clean_files(self.cleaned_data['file'])
240
240
241 def clean(self):
241 def clean(self):
242 cleaned_data = super(PostForm, self).clean()
242 cleaned_data = super(PostForm, self).clean()
243
243
244 if cleaned_data['email']:
244 if cleaned_data['email']:
245 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
245 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
246 self.need_to_ban = True
246 self.need_to_ban = True
247 raise forms.ValidationError('A human cannot enter a hidden field')
247 raise forms.ValidationError('A human cannot enter a hidden field')
248
248
249 if not self.errors:
249 if not self.errors:
250 self._clean_text_file()
250 self._clean_text_file()
251
251
252 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
252 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
253 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
253 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
254
254
255 settings_manager = get_settings_manager(self)
255 settings_manager = get_settings_manager(self)
256 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting(SETTING_CONFIRMED_USER)):
256 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting(SETTING_CONFIRMED_USER)):
257 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
257 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
258 if pow_difficulty > 0:
258 if pow_difficulty > 0:
259 validator = PowValidator(
259 validator = PowValidator(
260 self.session, cleaned_data['timestamp'],
260 self.session, cleaned_data['timestamp'],
261 cleaned_data['iteration'], cleaned_data['guess'],
261 cleaned_data['iteration'], cleaned_data['guess'],
262 cleaned_data['text'])
262 cleaned_data['text'])
263 else:
263 else:
264 validator = TimeValidator(self.session)
264 validator = TimeValidator(self.session)
265
265
266 validator.validate()
266 validator.validate()
267 for error in validator.get_errors():
267 for error in validator.get_errors():
268 self._add_general_error(error)
268 self._add_general_error(error)
269
269
270 settings_manager.set_setting(SETTING_CONFIRMED_USER, True)
270 settings_manager.set_setting(SETTING_CONFIRMED_USER, True)
271 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
271 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
272 self._check_file_duplicates(self.get_files())
272 self._check_file_duplicates(self.get_files())
273
273
274 return cleaned_data
274 return cleaned_data
275
275
276 def get_files(self):
276 def get_files(self):
277 """
277 """
278 Gets file from form or URL.
278 Gets file from form or URL.
279 """
279 """
280
280
281 files = []
281 files = []
282 for file in self.cleaned_data['file']:
282 for file in self.cleaned_data['file']:
283 if isinstance(file, UploadedFile):
283 if isinstance(file, UploadedFile):
284 files.append(file)
284 files.append(file)
285
285
286 return files
286 return files
287
287
288 def get_file_urls(self):
288 def get_file_urls(self):
289 files = []
289 files = []
290 for file in self.cleaned_data['file']:
290 for file in self.cleaned_data['file']:
291 if type(file) == str:
291 if type(file) == str:
292 files.append(file)
292 files.append(file)
293
293
294 return files
294 return files
295
295
296 def get_tripcode(self):
296 def get_tripcode(self):
297 title = self.cleaned_data['title']
297 title = self.cleaned_data['title']
298 if title is not None and TRIPCODE_DELIM in title:
298 if title is not None and TRIPCODE_DELIM in title:
299 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
299 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
300 else:
300 else:
301 tripcode = ''
301 tripcode = ''
302 return tripcode
302 return tripcode
303
303
304 def get_title(self):
304 def get_title(self):
305 title = self.cleaned_data['title']
305 title = self.cleaned_data['title']
306 if title is not None and TRIPCODE_DELIM in title:
306 if title is not None and TRIPCODE_DELIM in title:
307 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
307 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
308 else:
308 else:
309 return title
309 return title
310
310
311 def get_images(self):
311 def get_images(self):
312 return self.cleaned_data.get('stickers', [])
312 return self.cleaned_data.get('stickers', [])
313
313
314 def is_subscribe(self):
314 def is_subscribe(self):
315 return self.cleaned_data['subscribe']
315 return self.cleaned_data['subscribe']
316
316
317 def _update_file_extension(self, file):
317 def _update_file_extension(self, file):
318 if file:
318 if file:
319 mimetype = get_file_mimetype(file)
319 mimetype = get_file_mimetype(file)
320 extension = MIMETYPE_EXTENSIONS.get(mimetype)
320 extension = MIMETYPE_EXTENSIONS.get(mimetype)
321 if extension:
321 if extension:
322 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
322 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
323 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
323 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
324
324
325 file.name = new_filename
325 file.name = new_filename
326 else:
326 else:
327 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
327 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
328
328
329 def _clean_files(self, inputs):
329 def _clean_files(self, inputs):
330 files = []
330 files = []
331
331
332 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
332 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
333 if len(inputs) > max_file_count:
333 if len(inputs) > max_file_count:
334 raise forms.ValidationError(
334 raise forms.ValidationError(
335 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
335 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
336 max_file_count) % {'files': max_file_count})
336 max_file_count) % {'files': max_file_count})
337
337
338 size = 0
338 size = 0
339 for file_input in inputs:
339 for file_input in inputs:
340 if isinstance(file_input, UploadedFile):
340 if isinstance(file_input, UploadedFile):
341 file = self._clean_file_file(file_input)
341 file = self._clean_file_file(file_input)
342 size += file.size
342 size += file.size
343 files.append(file)
343 files.append(file)
344 else:
344 else:
345 files.append(self._clean_file_url(file_input))
345 files.append(self._clean_file_url(file_input))
346
346
347 for file in files:
347 for file in files:
348 self._validate_image_dimensions(file)
348 self._validate_image_dimensions(file)
349 validate_file_size(size)
349 validate_file_size(size)
350
350
351 return files
351 return files
352
352
353 def _validate_image_dimensions(self, file):
353 def _validate_image_dimensions(self, file):
354 if isinstance(file, UploadedFile):
354 if isinstance(file, UploadedFile):
355 mimetype = get_file_mimetype(file)
355 mimetype = get_file_mimetype(file)
356 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
356 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
357 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
357 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
358 try:
358 try:
359 print(get_image_dimensions(file))
359 print(get_image_dimensions(file))
360 except Exception:
360 except Exception:
361 raise forms.ValidationError('Possible decompression bomb or large image.')
361 raise forms.ValidationError('Possible decompression bomb or large image.')
362
362
363 def _clean_file_file(self, file):
363 def _clean_file_file(self, file):
364 self._update_file_extension(file)
364 self._update_file_extension(file)
365
365
366 return file
366 return file
367
367
368 def _clean_file_url(self, url):
368 def _clean_file_url(self, url):
369 file = None
369 file = None
370
370
371 if url:
371 if url:
372 mode = self.cleaned_data['download_mode']
372 mode = self.cleaned_data['download_mode']
373 if mode == DOWN_MODE_URL:
373 if mode == DOWN_MODE_URL:
374 return url
374 return url
375
375
376 try:
376 try:
377 image = get_attachment_by_alias(url, self.session)
377 image = get_attachment_by_alias(url, self.session)
378 if image is not None:
378 if image is not None:
379 if 'stickers' not in self.cleaned_data:
379 if 'stickers' not in self.cleaned_data:
380 self.cleaned_data['stickers'] = []
380 self.cleaned_data['stickers'] = []
381 self.cleaned_data['stickers'].append(image)
381 self.cleaned_data['stickers'].append(image)
382 return
382 return
383
383
384 if file is None:
384 if file is None:
385 file = self._get_file_from_url(url)
385 file = self._get_file_from_url(url)
386 if not file:
386 if not file:
387 raise forms.ValidationError(_('Invalid URL'))
387 raise forms.ValidationError(_('Invalid URL'))
388 self._update_file_extension(file)
388 self._update_file_extension(file)
389 except forms.ValidationError as e:
389 except forms.ValidationError as e:
390 # Assume we will get the plain URL instead of a file and save it
390 # Assume we will get the plain URL instead of a file and save it
391 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
391 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
392 logger.info('Error in forms: {}'.format(e))
392 logger.info('Error in forms: {}'.format(e))
393 return url
393 return url
394 else:
394 else:
395 raise e
395 raise e
396
396
397 return file
397 return file
398
398
399 def _clean_text_file(self):
399 def _clean_text_file(self):
400 text = self.cleaned_data.get('text')
400 text = self.cleaned_data.get('text')
401 file = self.get_files()
401 file = self.get_files()
402 file_url = self.get_file_urls()
402 file_url = self.get_file_urls()
403 images = self.get_images()
403 images = self.get_images()
404
404
405 if (not text) and (not file) and (not file_url) and len(images) == 0:
405 if (not text) and (not file) and (not file_url) and len(images) == 0:
406 error_message = _('Either text or file must be entered.')
406 error_message = _('Either text or file must be entered.')
407 self._add_general_error(error_message)
407 self._add_general_error(error_message)
408
408
409 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
409 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
410 """
410 """
411 Gets an file file from URL.
411 Gets an file file from URL.
412 """
412 """
413
413
414 try:
414 try:
415 return download(url)
415 return download(url)
416 except forms.ValidationError as e:
416 except forms.ValidationError as e:
417 raise e
417 raise e
418 except Exception as e:
418 except Exception as e:
419 raise forms.ValidationError(e)
419 raise forms.ValidationError(e)
420
420
421 def _check_file_duplicates(self, files):
421 def _check_file_duplicates(self, files):
422 for file in files:
422 for file in files:
423 file_hash = utils.get_file_hash(file)
423 file_hash = utils.get_file_hash(file)
424 if Attachment.objects.get_existing_duplicate(file_hash, file):
424 if Attachment.objects.get_existing_duplicate(file_hash, file):
425 self._add_general_error(_(ERROR_DUPLICATES))
425 self._add_general_error(_(ERROR_DUPLICATES))
426
426
427 def _add_general_error(self, message):
427 def _add_general_error(self, message):
428 self.add_error('text', forms.ValidationError(message))
428 self.add_error('text', forms.ValidationError(message))
429
429
430
430
431 class ThreadForm(PostForm):
431 class ThreadForm(PostForm):
432
432
433 tags = forms.CharField(
433 tags = forms.CharField(
434 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
434 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
435 max_length=100, label=_('Tags'), required=True)
435 max_length=100, label=_('Tags'), required=True)
436 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
436 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
437 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
437 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
438
438
439 def clean_tags(self):
439 def clean_tags(self):
440 tags = self.cleaned_data['tags'].strip()
440 tags = self.cleaned_data['tags'].strip()
441
441
442 if not tags or not REGEX_TAGS.match(tags):
442 if not tags or not REGEX_TAGS.match(tags):
443 raise forms.ValidationError(
443 raise forms.ValidationError(
444 _('Inappropriate characters in tags.'))
444 _('Inappropriate characters in tags.'))
445
445
446 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
446 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
447 .strip().lower()
447 .strip().lower()
448
448
449 required_tag_exists = False
449 required_tag_exists = False
450 tag_set = set()
450 tag_set = set()
451 for tag_string in tags.split():
451 for tag_string in tags.split():
452 tag_name = tag_string.strip().lower()
452 tag_name = tag_string.strip().lower()
453 if tag_name == default_tag_name:
453 if tag_name == default_tag_name:
454 required_tag_exists = True
454 required_tag_exists = True
455 tag, created = Tag.objects.get_or_create_with_alias(
455 tag, created = Tag.objects.get_or_create_with_alias(
456 name=tag_name, required=True)
456 name=tag_name, required=True)
457 else:
457 else:
458 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
458 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
459 tag_set.add(tag)
459 tag_set.add(tag)
460
460
461 # If this is a new tag, don't check for its parents because nobody
461 # If this is a new tag, don't check for its parents because nobody
462 # added them yet
462 # added them yet
463 if not created:
463 if not created:
464 tag_set |= set(tag.get_all_parents())
464 tag_set |= set(tag.get_all_parents())
465
465
466 for tag in tag_set:
466 for tag in tag_set:
467 if tag.required:
467 if tag.required:
468 required_tag_exists = True
468 required_tag_exists = True
469 break
469 break
470
470
471 # Use default tag if no section exists
471 # Use default tag if no section exists
472 if not required_tag_exists:
472 if not required_tag_exists:
473 default_tag, created = Tag.objects.get_or_create_with_alias(
473 default_tag, created = Tag.objects.get_or_create_with_alias(
474 name=default_tag_name, required=True)
474 name=default_tag_name, required=True)
475 tag_set.add(default_tag)
475 tag_set.add(default_tag)
476
476
477 return tag_set
477 return tag_set
478
478
479 def clean(self):
479 def clean(self):
480 cleaned_data = super(ThreadForm, self).clean()
480 cleaned_data = super(ThreadForm, self).clean()
481
481
482 return cleaned_data
482 return cleaned_data
483
483
484 def is_monochrome(self):
484 def is_monochrome(self):
485 return self.cleaned_data['monochrome']
485 return self.cleaned_data['monochrome']
486
486
487 def clean_stickerpack(self):
487 def clean_stickerpack(self):
488 stickerpack = self.cleaned_data['stickerpack']
488 stickerpack = self.cleaned_data['stickerpack']
489 if stickerpack:
489 if stickerpack:
490 tripcode = self.get_tripcode()
490 tripcode = self.get_tripcode()
491 if not tripcode:
491 if not tripcode:
492 raise forms.ValidationError(_(
492 raise forms.ValidationError(_(
493 'Tripcode should be specified to own a stickerpack.'))
493 'Tripcode should be specified to own a stickerpack.'))
494 title = self.get_title()
494 title = self.get_title()
495 if not title:
495 if not title:
496 raise forms.ValidationError(_(
496 raise forms.ValidationError(_(
497 'Title should be specified as a stickerpack name.'))
497 'Title should be specified as a stickerpack name.'))
498 if not REGEX_TAGS.match(title):
498 if not REGEX_TAGS.match(title):
499 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
499 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
500
500
501 existing_pack = StickerPack.objects.filter(name=title).first()
501 existing_pack = StickerPack.objects.filter(name=title).first()
502 if existing_pack:
502 if existing_pack:
503 if existing_pack.tripcode != tripcode:
503 if existing_pack.tripcode != tripcode:
504 raise forms.ValidationError(_(
504 raise forms.ValidationError(_(
505 'A sticker pack with this name already exists and is'
505 'A sticker pack with this name already exists and is'
506 ' owned by another tripcode.'))
506 ' owned by another tripcode.'))
507 if not existing_pack.tripcode:
507 if not existing_pack.tripcode:
508 raise forms.ValidationError(_(
508 raise forms.ValidationError(_(
509 'This sticker pack can only be updated by an '
509 'This sticker pack can only be updated by an '
510 'administrator.'))
510 'administrator.'))
511
511
512 return stickerpack
512 return stickerpack
513
513
514 def is_stickerpack(self):
514 def is_stickerpack(self):
515 return self.cleaned_data['stickerpack']
515 return self.cleaned_data['stickerpack']
516
516
517
517
518 class SettingsForm(NeboardForm):
518 class SettingsForm(NeboardForm):
519
519
520 theme = forms.ChoiceField(
520 theme = forms.ChoiceField(
521 choices=board_settings.get_list_dict('View', 'Themes'),
521 choices=board_settings.get_list_dict('View', 'Themes'),
522 label=_('Theme'))
522 label=_('Theme'))
523 image_viewer = forms.ChoiceField(
523 image_viewer = forms.ChoiceField(
524 choices=board_settings.get_list_dict('View', 'ImageViewers'),
524 choices=board_settings.get_list_dict('View', 'ImageViewers'),
525 label=_('Image view mode'))
525 label=_('Image view mode'))
526 username = forms.CharField(label=_('User name'), required=False)
526 username = forms.CharField(label=_('User name'), required=False)
527 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
527 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
528 subscribe_by_default = forms.BooleanField(
528 subscribe_by_default = forms.BooleanField(
529 required=False, label=_('Subscribe to threads by default'))
529 required=False, label=_('Subscribe to threads by default'))
530
530
531 def clean_username(self):
531 def clean_username(self):
532 username = self.cleaned_data['username']
532 username = self.cleaned_data['username']
533
533
534 if username and not REGEX_USERNAMES.match(username):
534 if username and not REGEX_USERNAMES.match(username):
535 raise forms.ValidationError(_('Inappropriate characters.'))
535 raise forms.ValidationError(_('Inappropriate characters.'))
536
536
537 return username
537 return username
538
538
539
539
540 class SearchForm(NeboardForm):
540 class SearchForm(NeboardForm):
541 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
541 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,12 +1,12 b''
1 python-magic
1 python-magic
2 httplib2
2 httplib2
3 simplejson
3 simplejson
4 pytube
4 pytube
5 requests
5 requests
6 pillow
6 pillow
7 django>=1.8,<2.1
7 django>=1.8
8 bbcode
8 bbcode
9 django-debug-toolbar
9 django-debug-toolbar
10 pytz
10 pytz
11 ecdsa
11 ecdsa
12 feedparser
12 feedparser
General Comments 0
You need to be logged in to leave comments. Login now