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