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