##// END OF EJS Templates
Add default tag instead of raising an error when no section is specified
neko259 -
r1655:3877b64d default
parent child Browse files
Show More
@@ -1,43 +1,44 b''
1 [Version]
1 [Version]
2 Version = 3.2.2 Sunako
2 Version = 3.2.2 Sunako
3 SiteName = Neboard DEV
3 SiteName = Neboard DEV
4
4
5 [Cache]
5 [Cache]
6 # Timeout for caching, if cache is used
6 # Timeout for caching, if cache is used
7 CacheTimeout = 600
7 CacheTimeout = 600
8
8
9 [Forms]
9 [Forms]
10 # Max post length in characters
10 # Max post length in characters
11 MaxTextLength = 30000
11 MaxTextLength = 30000
12 MaxFileSize = 8000000
12 MaxFileSize = 8000000
13 LimitFirstPosting = true
13 LimitFirstPosting = true
14 LimitPostingSpeed = false
14 LimitPostingSpeed = false
15 PowDifficulty = 0
15 PowDifficulty = 0
16 # Delay in seconds
16 # Delay in seconds
17 PostingDelay = 30
17 PostingDelay = 30
18 Autoban = false
18 Autoban = false
19 DefaultTag = test
19
20
20 [Messages]
21 [Messages]
21 # Thread bumplimit
22 # Thread bumplimit
22 MaxPostsPerThread = 10
23 MaxPostsPerThread = 10
23 ThreadArchiveDays = 300
24 ThreadArchiveDays = 300
24 AnonymousMode = false
25 AnonymousMode = false
25
26
26 [View]
27 [View]
27 DefaultTheme = md
28 DefaultTheme = md
28 DefaultImageViewer = simple
29 DefaultImageViewer = simple
29 LastRepliesCount = 3
30 LastRepliesCount = 3
30 ThreadsPerPage = 3
31 ThreadsPerPage = 3
31 ImagesPerPageGallery = 20
32 ImagesPerPageGallery = 20
32 MaxFavoriteThreads = 20
33 MaxFavoriteThreads = 20
33
34
34 [Storage]
35 [Storage]
35 # Enable archiving threads instead of deletion when the thread limit is reached
36 # Enable archiving threads instead of deletion when the thread limit is reached
36 ArchiveThreads = true
37 ArchiveThreads = true
37
38
38 [External]
39 [External]
39 # Thread update
40 # Thread update
40 WebsocketsEnabled = false
41 WebsocketsEnabled = false
41
42
42 [RSS]
43 [RSS]
43 MaxItems = 20
44 MaxItems = 20
@@ -1,469 +1,472 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 required_css_class = 'required-field'
112 required_css_class = 'required-field'
113
113
114 def as_div(self):
114 def as_div(self):
115 """
115 """
116 Returns this form rendered as HTML <as_div>s.
116 Returns this form rendered as HTML <as_div>s.
117 """
117 """
118
118
119 return self._html_output(
119 return self._html_output(
120 # TODO Do not show hidden rows in the list here
120 # TODO Do not show hidden rows in the list here
121 normal_row='<div class="form-row">'
121 normal_row='<div class="form-row">'
122 '<div class="form-label">'
122 '<div class="form-label">'
123 '%(label)s'
123 '%(label)s'
124 '</div>'
124 '</div>'
125 '<div class="form-input">'
125 '<div class="form-input">'
126 '%(field)s'
126 '%(field)s'
127 '</div>'
127 '</div>'
128 '</div>'
128 '</div>'
129 '<div class="form-row">'
129 '<div class="form-row">'
130 '%(help_text)s'
130 '%(help_text)s'
131 '</div>',
131 '</div>',
132 error_row='<div class="form-row">'
132 error_row='<div class="form-row">'
133 '<div class="form-label"></div>'
133 '<div class="form-label"></div>'
134 '<div class="form-errors">%s</div>'
134 '<div class="form-errors">%s</div>'
135 '</div>',
135 '</div>',
136 row_ender='</div>',
136 row_ender='</div>',
137 help_text_html='%s',
137 help_text_html='%s',
138 errors_on_separate_row=True)
138 errors_on_separate_row=True)
139
139
140 def as_json_errors(self):
140 def as_json_errors(self):
141 errors = []
141 errors = []
142
142
143 for name, field in list(self.fields.items()):
143 for name, field in list(self.fields.items()):
144 if self[name].errors:
144 if self[name].errors:
145 errors.append({
145 errors.append({
146 'field': name,
146 'field': name,
147 'errors': self[name].errors.as_text(),
147 'errors': self[name].errors.as_text(),
148 })
148 })
149
149
150 return errors
150 return errors
151
151
152
152
153 class PostForm(NeboardForm):
153 class PostForm(NeboardForm):
154
154
155 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
155 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
156 label=LABEL_TITLE,
156 label=LABEL_TITLE,
157 widget=forms.TextInput(
157 widget=forms.TextInput(
158 attrs={ATTRIBUTE_PLACEHOLDER:
158 attrs={ATTRIBUTE_PLACEHOLDER:
159 'test#tripcode'}))
159 'test#tripcode'}))
160 text = forms.CharField(
160 text = forms.CharField(
161 widget=FormatPanel(attrs={
161 widget=FormatPanel(attrs={
162 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
162 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
163 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
163 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
164 }),
164 }),
165 required=False, label=LABEL_TEXT)
165 required=False, label=LABEL_TEXT)
166 file = forms.FileField(required=False, label=_('File'),
166 file = forms.FileField(required=False, label=_('File'),
167 widget=forms.ClearableFileInput(
167 widget=forms.ClearableFileInput(
168 attrs={'accept': 'file/*'}))
168 attrs={'accept': 'file/*'}))
169 file_url = forms.CharField(required=False, label=_('File URL'),
169 file_url = forms.CharField(required=False, label=_('File URL'),
170 widget=forms.TextInput(
170 widget=forms.TextInput(
171 attrs={ATTRIBUTE_PLACEHOLDER:
171 attrs={ATTRIBUTE_PLACEHOLDER:
172 'http://example.com/image.png'}))
172 'http://example.com/image.png'}))
173
173
174 # This field is for spam prevention only
174 # This field is for spam prevention only
175 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
175 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
176 widget=forms.TextInput(attrs={
176 widget=forms.TextInput(attrs={
177 'class': 'form-email'}))
177 'class': 'form-email'}))
178 threads = forms.CharField(required=False, label=_('Additional threads'),
178 threads = forms.CharField(required=False, label=_('Additional threads'),
179 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
179 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
180 '123 456 789'}))
180 '123 456 789'}))
181 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
181 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
182
182
183 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
183 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
184 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
184 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
185 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
185 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
186
186
187 session = None
187 session = None
188 need_to_ban = False
188 need_to_ban = False
189 image = None
189 image = None
190
190
191 def _update_file_extension(self, file):
191 def _update_file_extension(self, file):
192 if file:
192 if file:
193 mimetype = get_file_mimetype(file)
193 mimetype = get_file_mimetype(file)
194 extension = MIMETYPE_EXTENSIONS.get(mimetype)
194 extension = MIMETYPE_EXTENSIONS.get(mimetype)
195 if extension:
195 if extension:
196 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
196 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
197 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
197 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
198
198
199 file.name = new_filename
199 file.name = new_filename
200 else:
200 else:
201 logger = logging.getLogger('boards.forms.extension')
201 logger = logging.getLogger('boards.forms.extension')
202
202
203 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
203 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
204
204
205 def clean_title(self):
205 def clean_title(self):
206 title = self.cleaned_data['title']
206 title = self.cleaned_data['title']
207 if title:
207 if title:
208 if len(title) > TITLE_MAX_LENGTH:
208 if len(title) > TITLE_MAX_LENGTH:
209 raise forms.ValidationError(_('Title must have less than %s '
209 raise forms.ValidationError(_('Title must have less than %s '
210 'characters') %
210 'characters') %
211 str(TITLE_MAX_LENGTH))
211 str(TITLE_MAX_LENGTH))
212 return title
212 return title
213
213
214 def clean_text(self):
214 def clean_text(self):
215 text = self.cleaned_data['text'].strip()
215 text = self.cleaned_data['text'].strip()
216 if text:
216 if text:
217 max_length = board_settings.get_int('Forms', 'MaxTextLength')
217 max_length = board_settings.get_int('Forms', 'MaxTextLength')
218 if len(text) > max_length:
218 if len(text) > max_length:
219 raise forms.ValidationError(_('Text must have less than %s '
219 raise forms.ValidationError(_('Text must have less than %s '
220 'characters') % str(max_length))
220 'characters') % str(max_length))
221 return text
221 return text
222
222
223 def clean_file(self):
223 def clean_file(self):
224 file = self.cleaned_data['file']
224 file = self.cleaned_data['file']
225
225
226 if file:
226 if file:
227 validate_file_size(file.size)
227 validate_file_size(file.size)
228 self._update_file_extension(file)
228 self._update_file_extension(file)
229
229
230 return file
230 return file
231
231
232 def clean_file_url(self):
232 def clean_file_url(self):
233 url = self.cleaned_data['file_url']
233 url = self.cleaned_data['file_url']
234
234
235 file = None
235 file = None
236
236
237 if url:
237 if url:
238 file = get_image_by_alias(url, self.session)
238 file = get_image_by_alias(url, self.session)
239 self.image = file
239 self.image = file
240
240
241 if file is not None:
241 if file is not None:
242 return
242 return
243
243
244 if file is None:
244 if file is None:
245 file = self._get_file_from_url(url)
245 file = self._get_file_from_url(url)
246 if not file:
246 if not file:
247 raise forms.ValidationError(_('Invalid URL'))
247 raise forms.ValidationError(_('Invalid URL'))
248 else:
248 else:
249 validate_file_size(file.size)
249 validate_file_size(file.size)
250 self._update_file_extension(file)
250 self._update_file_extension(file)
251
251
252 return file
252 return file
253
253
254 def clean_threads(self):
254 def clean_threads(self):
255 threads_str = self.cleaned_data['threads']
255 threads_str = self.cleaned_data['threads']
256
256
257 if len(threads_str) > 0:
257 if len(threads_str) > 0:
258 threads_id_list = threads_str.split(' ')
258 threads_id_list = threads_str.split(' ')
259
259
260 threads = list()
260 threads = list()
261
261
262 for thread_id in threads_id_list:
262 for thread_id in threads_id_list:
263 try:
263 try:
264 thread = Post.objects.get(id=int(thread_id))
264 thread = Post.objects.get(id=int(thread_id))
265 if not thread.is_opening() or thread.get_thread().is_archived():
265 if not thread.is_opening() or thread.get_thread().is_archived():
266 raise ObjectDoesNotExist()
266 raise ObjectDoesNotExist()
267 threads.append(thread)
267 threads.append(thread)
268 except (ObjectDoesNotExist, ValueError):
268 except (ObjectDoesNotExist, ValueError):
269 raise forms.ValidationError(_('Invalid additional thread list'))
269 raise forms.ValidationError(_('Invalid additional thread list'))
270
270
271 return threads
271 return threads
272
272
273 def clean(self):
273 def clean(self):
274 cleaned_data = super(PostForm, self).clean()
274 cleaned_data = super(PostForm, self).clean()
275
275
276 if cleaned_data['email']:
276 if cleaned_data['email']:
277 if board_settings.get_bool('Forms', 'Autoban'):
277 if board_settings.get_bool('Forms', 'Autoban'):
278 self.need_to_ban = True
278 self.need_to_ban = True
279 raise forms.ValidationError('A human cannot enter a hidden field')
279 raise forms.ValidationError('A human cannot enter a hidden field')
280
280
281 if not self.errors:
281 if not self.errors:
282 self._clean_text_file()
282 self._clean_text_file()
283
283
284 limit_speed = board_settings.get_bool('Forms', 'LimitPostingSpeed')
284 limit_speed = board_settings.get_bool('Forms', 'LimitPostingSpeed')
285 limit_first = board_settings.get_bool('Forms', 'LimitFirstPosting')
285 limit_first = board_settings.get_bool('Forms', 'LimitFirstPosting')
286
286
287 settings_manager = get_settings_manager(self)
287 settings_manager = get_settings_manager(self)
288 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
288 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
289 pow_difficulty = board_settings.get_int('Forms', 'PowDifficulty')
289 pow_difficulty = board_settings.get_int('Forms', 'PowDifficulty')
290 if pow_difficulty > 0:
290 if pow_difficulty > 0:
291 # PoW-based
291 # PoW-based
292 if cleaned_data['timestamp'] \
292 if cleaned_data['timestamp'] \
293 and cleaned_data['iteration'] and cleaned_data['guess'] \
293 and cleaned_data['iteration'] and cleaned_data['guess'] \
294 and not settings_manager.get_setting('confirmed_user'):
294 and not settings_manager.get_setting('confirmed_user'):
295 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
295 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
296 else:
296 else:
297 # Time-based
297 # Time-based
298 self._validate_posting_speed()
298 self._validate_posting_speed()
299 settings_manager.set_setting('confirmed_user', True)
299 settings_manager.set_setting('confirmed_user', True)
300
300
301
301
302 return cleaned_data
302 return cleaned_data
303
303
304 def get_file(self):
304 def get_file(self):
305 """
305 """
306 Gets file from form or URL.
306 Gets file from form or URL.
307 """
307 """
308
308
309 file = self.cleaned_data['file']
309 file = self.cleaned_data['file']
310 return file or self.cleaned_data['file_url']
310 return file or self.cleaned_data['file_url']
311
311
312 def get_tripcode(self):
312 def get_tripcode(self):
313 title = self.cleaned_data['title']
313 title = self.cleaned_data['title']
314 if title is not None and TRIPCODE_DELIM in title:
314 if title is not None and TRIPCODE_DELIM in title:
315 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
315 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
316 tripcode = hashlib.md5(code.encode()).hexdigest()
316 tripcode = hashlib.md5(code.encode()).hexdigest()
317 else:
317 else:
318 tripcode = ''
318 tripcode = ''
319 return tripcode
319 return tripcode
320
320
321 def get_title(self):
321 def get_title(self):
322 title = self.cleaned_data['title']
322 title = self.cleaned_data['title']
323 if title is not None and TRIPCODE_DELIM in title:
323 if title is not None and TRIPCODE_DELIM in title:
324 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
324 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
325 else:
325 else:
326 return title
326 return title
327
327
328 def get_images(self):
328 def get_images(self):
329 if self.image:
329 if self.image:
330 return [self.image]
330 return [self.image]
331 else:
331 else:
332 return []
332 return []
333
333
334 def is_subscribe(self):
334 def is_subscribe(self):
335 return self.cleaned_data['subscribe']
335 return self.cleaned_data['subscribe']
336
336
337 def _clean_text_file(self):
337 def _clean_text_file(self):
338 text = self.cleaned_data.get('text')
338 text = self.cleaned_data.get('text')
339 file = self.get_file()
339 file = self.get_file()
340 images = self.get_images()
340 images = self.get_images()
341
341
342 if (not text) and (not file) and len(images) == 0:
342 if (not text) and (not file) and len(images) == 0:
343 error_message = _('Either text or file must be entered.')
343 error_message = _('Either text or file must be entered.')
344 self._errors['text'] = self.error_class([error_message])
344 self._errors['text'] = self.error_class([error_message])
345
345
346 def _validate_posting_speed(self):
346 def _validate_posting_speed(self):
347 can_post = True
347 can_post = True
348
348
349 posting_delay = board_settings.get_int('Forms', 'PostingDelay')
349 posting_delay = board_settings.get_int('Forms', 'PostingDelay')
350
350
351 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
351 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
352 now = time.time()
352 now = time.time()
353
353
354 current_delay = 0
354 current_delay = 0
355
355
356 if LAST_POST_TIME not in self.session:
356 if LAST_POST_TIME not in self.session:
357 self.session[LAST_POST_TIME] = now
357 self.session[LAST_POST_TIME] = now
358
358
359 need_delay = True
359 need_delay = True
360 else:
360 else:
361 last_post_time = self.session.get(LAST_POST_TIME)
361 last_post_time = self.session.get(LAST_POST_TIME)
362 current_delay = int(now - last_post_time)
362 current_delay = int(now - last_post_time)
363
363
364 need_delay = current_delay < posting_delay
364 need_delay = current_delay < posting_delay
365
365
366 if need_delay:
366 if need_delay:
367 delay = posting_delay - current_delay
367 delay = posting_delay - current_delay
368 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
368 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
369 delay) % {'delay': delay}
369 delay) % {'delay': delay}
370 self._errors['text'] = self.error_class([error_message])
370 self._errors['text'] = self.error_class([error_message])
371
371
372 can_post = False
372 can_post = False
373
373
374 if can_post:
374 if can_post:
375 self.session[LAST_POST_TIME] = now
375 self.session[LAST_POST_TIME] = now
376
376
377 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
377 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
378 """
378 """
379 Gets an file file from URL.
379 Gets an file file from URL.
380 """
380 """
381
381
382 try:
382 try:
383 return download(url)
383 return download(url)
384 except forms.ValidationError as e:
384 except forms.ValidationError as e:
385 raise e
385 raise e
386 except Exception as e:
386 except Exception as e:
387 raise forms.ValidationError(e)
387 raise forms.ValidationError(e)
388
388
389 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
389 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
390 post_time = timezone.datetime.fromtimestamp(
390 post_time = timezone.datetime.fromtimestamp(
391 int(timestamp[:-3]), tz=timezone.get_current_timezone())
391 int(timestamp[:-3]), tz=timezone.get_current_timezone())
392
392
393 payload = timestamp + message.replace('\r\n', '\n')
393 payload = timestamp + message.replace('\r\n', '\n')
394 difficulty = board_settings.get_int('Forms', 'PowDifficulty')
394 difficulty = board_settings.get_int('Forms', 'PowDifficulty')
395 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
395 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
396 if len(target) < POW_HASH_LENGTH:
396 if len(target) < POW_HASH_LENGTH:
397 target = '0' * (POW_HASH_LENGTH - len(target)) + target
397 target = '0' * (POW_HASH_LENGTH - len(target)) + target
398
398
399 computed_guess = hashlib.sha256((payload + iteration).encode())\
399 computed_guess = hashlib.sha256((payload + iteration).encode())\
400 .hexdigest()[0:POW_HASH_LENGTH]
400 .hexdigest()[0:POW_HASH_LENGTH]
401 if guess != computed_guess or guess > target:
401 if guess != computed_guess or guess > target:
402 self._errors['text'] = self.error_class(
402 self._errors['text'] = self.error_class(
403 [_('Invalid PoW.')])
403 [_('Invalid PoW.')])
404
404
405
405
406
406
407 class ThreadForm(PostForm):
407 class ThreadForm(PostForm):
408
408
409 tags = forms.CharField(
409 tags = forms.CharField(
410 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
410 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
411 max_length=100, label=_('Tags'), required=True)
411 max_length=100, label=_('Tags'), required=True)
412 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
412 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
413
413
414 def clean_tags(self):
414 def clean_tags(self):
415 tags = self.cleaned_data['tags'].strip()
415 tags = self.cleaned_data['tags'].strip()
416
416
417 if not tags or not REGEX_TAGS.match(tags):
417 if not tags or not REGEX_TAGS.match(tags):
418 raise forms.ValidationError(
418 raise forms.ValidationError(
419 _('Inappropriate characters in tags.'))
419 _('Inappropriate characters in tags.'))
420
420
421 required_tag_exists = False
421 required_tag_exists = False
422 tag_set = set()
422 tag_set = set()
423 for tag_string in tags.split():
423 for tag_string in tags.split():
424 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
424 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
425 tag_set.add(tag)
425 tag_set.add(tag)
426
426
427 # If this is a new tag, don't check for its parents because nobody
427 # If this is a new tag, don't check for its parents because nobody
428 # added them yet
428 # added them yet
429 if not created:
429 if not created:
430 tag_set |= set(tag.get_all_parents())
430 tag_set |= set(tag.get_all_parents())
431
431
432 for tag in tag_set:
432 for tag in tag_set:
433 if tag.required:
433 if tag.required:
434 required_tag_exists = True
434 required_tag_exists = True
435 break
435 break
436
436
437 if not required_tag_exists:
437 if not required_tag_exists:
438 raise forms.ValidationError(
438 default_tag_name = board_settings.get('Forms', 'DefaultTag')\
439 _('Need at least one section.'))
439 .strip().lower()
440 default_tag, created = Tag.objects.get_or_create(
441 name=default_tag_name, required=True)
442 tag_set.add(default_tag)
440
443
441 return tag_set
444 return tag_set
442
445
443 def clean(self):
446 def clean(self):
444 cleaned_data = super(ThreadForm, self).clean()
447 cleaned_data = super(ThreadForm, self).clean()
445
448
446 return cleaned_data
449 return cleaned_data
447
450
448 def is_monochrome(self):
451 def is_monochrome(self):
449 return self.cleaned_data['monochrome']
452 return self.cleaned_data['monochrome']
450
453
451
454
452 class SettingsForm(NeboardForm):
455 class SettingsForm(NeboardForm):
453
456
454 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
457 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
455 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
458 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
456 username = forms.CharField(label=_('User name'), required=False)
459 username = forms.CharField(label=_('User name'), required=False)
457 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
460 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
458
461
459 def clean_username(self):
462 def clean_username(self):
460 username = self.cleaned_data['username']
463 username = self.cleaned_data['username']
461
464
462 if username and not REGEX_USERNAMES.match(username):
465 if username and not REGEX_USERNAMES.match(username):
463 raise forms.ValidationError(_('Inappropriate characters.'))
466 raise forms.ValidationError(_('Inappropriate characters.'))
464
467
465 return username
468 return username
466
469
467
470
468 class SearchForm(NeboardForm):
471 class SearchForm(NeboardForm):
469 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
472 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