Show More
@@ -17,6 +17,7 b' PowDifficulty = 0' | |||||
17 | PostingDelay = 30 |
|
17 | PostingDelay = 30 | |
18 | Autoban = false |
|
18 | Autoban = false | |
19 | DefaultTag = test |
|
19 | DefaultTag = test | |
|
20 | MaxFileCount = 2 | |||
20 |
|
21 | |||
21 | [Messages] |
|
22 | [Messages] | |
22 | # Thread bumplimit |
|
23 | # Thread bumplimit |
@@ -5,10 +5,8 b' import time' | |||||
5 |
|
5 | |||
6 | import pytz |
|
6 | import pytz | |
7 | from django import forms |
|
7 | from django import forms | |
8 | from django.core.files.uploadedfile import SimpleUploadedFile |
|
8 | from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile | |
9 | from django.core.files.uploadedfile import UploadedFile |
|
|||
10 | from django.forms.utils import ErrorList |
|
9 | from django.forms.utils import ErrorList | |
11 | from django.utils import timezone |
|
|||
12 | from django.utils.translation import ugettext_lazy as _, ungettext_lazy |
|
10 | from django.utils.translation import ugettext_lazy as _, ungettext_lazy | |
13 |
|
11 | |||
14 | import boards.settings as board_settings |
|
12 | import boards.settings as board_settings | |
@@ -24,6 +22,8 b' from boards.utils import validate_file_s' | |||||
24 | FILE_EXTENSION_DELIMITER |
|
22 | FILE_EXTENSION_DELIMITER | |
25 | from neboard import settings |
|
23 | from neboard import settings | |
26 |
|
24 | |||
|
25 | SECTION_FORMS = 'Forms' | |||
|
26 | ||||
27 | POW_HASH_LENGTH = 16 |
|
27 | POW_HASH_LENGTH = 16 | |
28 | POW_LIFE_MINUTES = 5 |
|
28 | POW_LIFE_MINUTES = 5 | |
29 |
|
29 | |||
@@ -45,11 +45,11 b" LABEL_TITLE = _('Title')" | |||||
45 | LABEL_TEXT = _('Text') |
|
45 | LABEL_TEXT = _('Text') | |
46 | LABEL_TAG = _('Tag') |
|
46 | LABEL_TAG = _('Tag') | |
47 | LABEL_SEARCH = _('Search') |
|
47 | LABEL_SEARCH = _('Search') | |
48 |
LABEL_FILE |
|
48 | LABEL_FILE = _('File') | |
49 | LABEL_FILE_2 = _('File 2') |
|
|||
50 |
|
49 | |||
51 | ERROR_SPEED = 'Please wait %(delay)d second before sending message' |
|
50 | ERROR_SPEED = 'Please wait %(delay)d second before sending message' | |
52 | ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message' |
|
51 | ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message' | |
|
52 | ERROR_MANY_FILES = _('Too many files.') | |||
53 |
|
53 | |||
54 | TAG_MAX_LENGTH = 20 |
|
54 | TAG_MAX_LENGTH = 20 | |
55 |
|
55 | |||
@@ -161,16 +161,14 b' class PostForm(NeboardForm):' | |||||
161 | title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False, |
|
161 | title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False, | |
162 | label=LABEL_TITLE, |
|
162 | label=LABEL_TITLE, | |
163 | widget=forms.TextInput( |
|
163 | widget=forms.TextInput( | |
164 | attrs={ATTRIBUTE_PLACEHOLDER: |
|
164 | attrs={ATTRIBUTE_PLACEHOLDER: 'title#tripcode'})) | |
165 | 'test#tripcode'})) |
|
|||
166 | text = forms.CharField( |
|
165 | text = forms.CharField( | |
167 | widget=FormatPanel(attrs={ |
|
166 | widget=FormatPanel(attrs={ | |
168 | ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER, |
|
167 | ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER, | |
169 | ATTRIBUTE_ROWS: TEXTAREA_ROWS, |
|
168 | ATTRIBUTE_ROWS: TEXTAREA_ROWS, | |
170 | }), |
|
169 | }), | |
171 | required=False, label=LABEL_TEXT) |
|
170 | required=False, label=LABEL_TEXT) | |
172 |
file |
|
171 | file = UrlFileField(required=False, label=LABEL_FILE) | |
173 | file_2 = UrlFileField(required=False, label=LABEL_FILE_2) |
|
|||
174 |
|
172 | |||
175 | # This field is for spam prevention only |
|
173 | # This field is for spam prevention only | |
176 | email = forms.CharField(max_length=100, required=False, label=_('e-mail'), |
|
174 | email = forms.CharField(max_length=100, required=False, label=_('e-mail'), | |
@@ -198,35 +196,32 b' class PostForm(NeboardForm):' | |||||
198 | def clean_text(self): |
|
196 | def clean_text(self): | |
199 | text = self.cleaned_data['text'].strip() |
|
197 | text = self.cleaned_data['text'].strip() | |
200 | if text: |
|
198 | if text: | |
201 |
max_length = board_settings.get_int( |
|
199 | max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength') | |
202 | if len(text) > max_length: |
|
200 | if len(text) > max_length: | |
203 | raise forms.ValidationError(_('Text must have less than %s ' |
|
201 | raise forms.ValidationError(_('Text must have less than %s ' | |
204 | 'characters') % str(max_length)) |
|
202 | 'characters') % str(max_length)) | |
205 | return text |
|
203 | return text | |
206 |
|
204 | |||
207 |
def clean_file |
|
205 | def clean_file(self): | |
208 |
return self._clean_file(self.cleaned_data['file |
|
206 | return self._clean_files(self.cleaned_data['file']) | |
209 |
|
||||
210 | def clean_file_2(self): |
|
|||
211 | return self._clean_file(self.cleaned_data['file_2']) |
|
|||
212 |
|
207 | |||
213 | def clean(self): |
|
208 | def clean(self): | |
214 | cleaned_data = super(PostForm, self).clean() |
|
209 | cleaned_data = super(PostForm, self).clean() | |
215 |
|
210 | |||
216 | if cleaned_data['email']: |
|
211 | if cleaned_data['email']: | |
217 |
if board_settings.get_bool( |
|
212 | if board_settings.get_bool(SECTION_FORMS, 'Autoban'): | |
218 | self.need_to_ban = True |
|
213 | self.need_to_ban = True | |
219 | raise forms.ValidationError('A human cannot enter a hidden field') |
|
214 | raise forms.ValidationError('A human cannot enter a hidden field') | |
220 |
|
215 | |||
221 | if not self.errors: |
|
216 | if not self.errors: | |
222 | self._clean_text_file() |
|
217 | self._clean_text_file() | |
223 |
|
218 | |||
224 |
limit_speed = board_settings.get_bool( |
|
219 | limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed') | |
225 |
limit_first = board_settings.get_bool( |
|
220 | limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting') | |
226 |
|
221 | |||
227 | settings_manager = get_settings_manager(self) |
|
222 | settings_manager = get_settings_manager(self) | |
228 | if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')): |
|
223 | if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')): | |
229 |
pow_difficulty = board_settings.get_int( |
|
224 | pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty') | |
230 | if pow_difficulty > 0: |
|
225 | if pow_difficulty > 0: | |
231 | # PoW-based |
|
226 | # PoW-based | |
232 | if cleaned_data['timestamp'] \ |
|
227 | if cleaned_data['timestamp'] \ | |
@@ -245,26 +240,16 b' class PostForm(NeboardForm):' | |||||
245 | Gets file from form or URL. |
|
240 | Gets file from form or URL. | |
246 | """ |
|
241 | """ | |
247 |
|
242 | |||
248 | cleaned_files = [ |
|
|||
249 | self.cleaned_data['file_1'], |
|
|||
250 | self.cleaned_data['file_2'], |
|
|||
251 | ] |
|
|||
252 |
|
||||
253 | files = [] |
|
243 | files = [] | |
254 |
for file in cleaned_ |
|
244 | for file in self.cleaned_data['file']: | |
255 | if isinstance(file, UploadedFile): |
|
245 | if isinstance(file, UploadedFile): | |
256 | files.append(file) |
|
246 | files.append(file) | |
257 |
|
247 | |||
258 | return files |
|
248 | return files | |
259 |
|
249 | |||
260 | def get_file_urls(self): |
|
250 | def get_file_urls(self): | |
261 | cleaned_files = [ |
|
|||
262 | self.cleaned_data['file_1'], |
|
|||
263 | self.cleaned_data['file_2'], |
|
|||
264 | ] |
|
|||
265 |
|
||||
266 | files = [] |
|
251 | files = [] | |
267 |
for file in cleaned_ |
|
252 | for file in self.cleaned_data['file']: | |
268 | if type(file) == str: |
|
253 | if type(file) == str: | |
269 | files.append(file) |
|
254 | files.append(file) | |
270 |
|
255 | |||
@@ -307,13 +292,24 b' class PostForm(NeboardForm):' | |||||
307 | else: |
|
292 | else: | |
308 | logger.info('Unrecognized file mimetype: {}'.format(mimetype)) |
|
293 | logger.info('Unrecognized file mimetype: {}'.format(mimetype)) | |
309 |
|
294 | |||
310 |
def _clean_file(self, |
|
295 | def _clean_files(self, inputs): | |
311 | if isinstance(file, UploadedFile): |
|
296 | files = [] | |
312 | file = self._clean_file_file(file) |
|
|||
313 | else: |
|
|||
314 | file = self._clean_file_url(file) |
|
|||
315 |
|
297 | |||
316 | return file |
|
298 | max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount') | |
|
299 | if type(inputs) == list: | |||
|
300 | if len(inputs) > max_file_count: | |||
|
301 | raise forms.ValidationError(ERROR_MANY_FILES) | |||
|
302 | for file in inputs: | |||
|
303 | files.append(self._clean_file_file(file)) | |||
|
304 | elif inputs: | |||
|
305 | inputs = inputs.replace('\r\n', '\n') | |||
|
306 | url_list = inputs.split('\n') | |||
|
307 | if len(url_list) > max_file_count: | |||
|
308 | raise forms.ValidationError(ERROR_MANY_FILES) | |||
|
309 | for url in url_list: | |||
|
310 | files.append(self._clean_file_url(url)) | |||
|
311 | ||||
|
312 | return files | |||
317 |
|
313 | |||
318 | def _clean_file_file(self, file): |
|
314 | def _clean_file_file(self, file): | |
319 | validate_file_size(file.size) |
|
315 | validate_file_size(file.size) | |
@@ -362,9 +358,9 b' class PostForm(NeboardForm):' | |||||
362 | def _validate_posting_speed(self): |
|
358 | def _validate_posting_speed(self): | |
363 | can_post = True |
|
359 | can_post = True | |
364 |
|
360 | |||
365 |
posting_delay = board_settings.get_int( |
|
361 | posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay') | |
366 |
|
362 | |||
367 |
if board_settings.get_bool( |
|
363 | if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'): | |
368 | now = time.time() |
|
364 | now = time.time() | |
369 |
|
365 | |||
370 | current_delay = 0 |
|
366 | current_delay = 0 | |
@@ -404,7 +400,7 b' class PostForm(NeboardForm):' | |||||
404 |
|
400 | |||
405 | def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str): |
|
401 | def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str): | |
406 | payload = timestamp + message.replace('\r\n', '\n') |
|
402 | payload = timestamp + message.replace('\r\n', '\n') | |
407 |
difficulty = board_settings.get_int( |
|
403 | difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty') | |
408 | target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty)) |
|
404 | target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty)) | |
409 | if len(target) < POW_HASH_LENGTH: |
|
405 | if len(target) < POW_HASH_LENGTH: | |
410 | target = '0' * (POW_HASH_LENGTH - len(target)) + target |
|
406 | target = '0' * (POW_HASH_LENGTH - len(target)) + target | |
@@ -430,7 +426,7 b' class ThreadForm(PostForm):' | |||||
430 | raise forms.ValidationError( |
|
426 | raise forms.ValidationError( | |
431 | _('Inappropriate characters in tags.')) |
|
427 | _('Inappropriate characters in tags.')) | |
432 |
|
428 | |||
433 |
default_tag_name = board_settings.get( |
|
429 | default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\ | |
434 | .strip().lower() |
|
430 | .strip().lower() | |
435 |
|
431 | |||
436 | required_tag_exists = False |
|
432 | required_tag_exists = False |
@@ -3,14 +3,56 b' from django.utils.translation import uge' | |||||
3 |
|
3 | |||
4 |
|
4 | |||
5 | ATTRIBUTE_PLACEHOLDER = 'placeholder' |
|
5 | ATTRIBUTE_PLACEHOLDER = 'placeholder' | |
|
6 | ATTRIBUTE_ROWS = 'rows' | |||
|
7 | ||||
|
8 | URL_ROWS = 2 | |||
|
9 | ||||
|
10 | ||||
|
11 | class MultipleFileInput(forms.FileInput): | |||
|
12 | """ | |||
|
13 | Input that allows to enter many files at once, utilizing the html5 "multiple" | |||
|
14 | attribute of a file input. | |||
|
15 | """ | |||
|
16 | def value_from_datadict(self, data, files, name): | |||
|
17 | if len(files) == 0: | |||
|
18 | return None | |||
|
19 | else: | |||
|
20 | return files.getlist(name) | |||
|
21 | ||||
|
22 | def render(self, name, value, attrs=None): | |||
|
23 | if not attrs: | |||
|
24 | attrs = dict() | |||
|
25 | attrs['multiple'] = '' | |||
|
26 | ||||
|
27 | return super().render(name, None, attrs=attrs) | |||
|
28 | ||||
|
29 | ||||
|
30 | class MultipleFileField(forms.FileField): | |||
|
31 | """ | |||
|
32 | Field that allows to enter multiple files from one input. | |||
|
33 | """ | |||
|
34 | def to_python(self, data): | |||
|
35 | if not data or len(data) == 0: | |||
|
36 | return None | |||
|
37 | ||||
|
38 | for file in data: | |||
|
39 | super().to_python(file) | |||
|
40 | ||||
|
41 | return data | |||
6 |
|
42 | |||
7 |
|
43 | |||
8 | class UrlFileWidget(forms.MultiWidget): |
|
44 | class UrlFileWidget(forms.MultiWidget): | |
|
45 | """ | |||
|
46 | Widget with a file input and a text input that allows to enter either a | |||
|
47 | file from a local system, or a URL from which the file would be downloaded. | |||
|
48 | """ | |||
9 | def __init__(self, *args, **kwargs): |
|
49 | def __init__(self, *args, **kwargs): | |
10 | widgets = ( |
|
50 | widgets = ( | |
11 |
|
|
51 | MultipleFileInput(attrs={'accept': 'file/*'}), | |
12 |
forms.Text |
|
52 | forms.Textarea(attrs={ | |
13 |
'http://example.com/image.png' |
|
53 | ATTRIBUTE_PLACEHOLDER: 'http://example.com/image.png', | |
|
54 | ATTRIBUTE_ROWS: URL_ROWS, | |||
|
55 | }), | |||
14 | ) |
|
56 | ) | |
15 | super().__init__(widgets, *args, **kwargs) |
|
57 | super().__init__(widgets, *args, **kwargs) | |
16 |
|
58 | |||
@@ -19,17 +61,16 b' class UrlFileWidget(forms.MultiWidget):' | |||||
19 |
|
61 | |||
20 |
|
62 | |||
21 | class UrlFileField(forms.MultiValueField): |
|
63 | class UrlFileField(forms.MultiValueField): | |
|
64 | """ | |||
|
65 | Field with a file input and a text input that allows to enter either a | |||
|
66 | file from a local system, or a URL from which the file would be downloaded. | |||
|
67 | """ | |||
22 | widget = UrlFileWidget |
|
68 | widget = UrlFileWidget | |
23 |
|
69 | |||
24 | def __init__(self, *args, **kwargs): |
|
70 | def __init__(self, *args, **kwargs): | |
25 | fields = ( |
|
71 | fields = ( | |
26 |
|
|
72 | MultipleFileField(required=False, label=_('File')), | |
27 | widget=forms.ClearableFileInput( |
|
73 | forms.CharField(required=False, label=_('File URL')), | |
28 | attrs={'accept': 'file/*'})), |
|
|||
29 | forms.CharField(required=False, label=_('File URL'), |
|
|||
30 | widget=forms.TextInput( |
|
|||
31 | attrs={ATTRIBUTE_PLACEHOLDER: |
|
|||
32 | 'http://example.com/image.png'})), |
|
|||
33 | ) |
|
74 | ) | |
34 |
|
75 | |||
35 | super().__init__( |
|
76 | super().__init__( | |
@@ -38,5 +79,5 b' class UrlFileField(forms.MultiValueField' | |||||
38 |
|
79 | |||
39 | def compress(self, data_list): |
|
80 | def compress(self, data_list): | |
40 | if data_list and len(data_list) >= 2: |
|
81 | if data_list and len(data_list) >= 2: | |
41 | return data_list[0] or data_list[1] |
|
82 | return data_list[0] or data_list[1] | |
42 |
|
83 |
@@ -68,7 +68,7 b' function addQuickReply(postId) {' | |||||
68 | resetFormPosition(); |
|
68 | resetFormPosition(); | |
69 | } else { |
|
69 | } else { | |
70 | var blockToInsert = null; |
|
70 | var blockToInsert = null; | |
71 | var textAreaJq = $('textarea'); |
|
71 | var textAreaJq = $('textarea#id_text'); | |
72 | var postLinkRaw = '[post]' + postId + '[/post]' |
|
72 | var postLinkRaw = '[post]' + postId + '[/post]' | |
73 | var textToAdd = ''; |
|
73 | var textToAdd = ''; | |
74 |
|
74 | |||
@@ -101,7 +101,7 b' function addQuickReply(postId) {' | |||||
101 | } |
|
101 | } | |
102 |
|
102 | |||
103 | function addQuickQuote() { |
|
103 | function addQuickQuote() { | |
104 | var textAreaJq = $('textarea'); |
|
104 | var textAreaJq = $('textarea#id_text'); | |
105 |
|
105 | |||
106 | var quoteButton = $("#quote-button"); |
|
106 | var quoteButton = $("#quote-button"); | |
107 | var postId = quoteButton.attr('data-post-id'); |
|
107 | var postId = quoteButton.attr('data-post-id'); |
General Comments 0
You need to be logged in to leave comments.
Login now