##// END OF EJS Templates
Some more constant extracts
neko259 -
r2003:3c9f075c default
parent child Browse files
Show More
@@ -1,227 +1,228 b''
1 from boards import settings
1 from boards import settings
2 from boards.models import Tag, TagAlias, Attachment
2 from boards.models import Tag, TagAlias, Attachment
3 from boards.models.attachment import AttachmentSticker
3 from boards.models.attachment import AttachmentSticker
4 from boards.models.thread import FAV_THREAD_NO_UPDATES
4 from boards.models.thread import FAV_THREAD_NO_UPDATES
5 from boards.models.tag import DEFAULT_LOCALE
5 from boards.models.tag import DEFAULT_LOCALE
6 from boards.settings import SECTION_VIEW
6
7
7 MAX_TRIPCODE_COLLISIONS = 50
8 MAX_TRIPCODE_COLLISIONS = 50
8
9
9 __author__ = 'neko259'
10 __author__ = 'neko259'
10
11
11 SESSION_SETTING = 'setting'
12 SESSION_SETTING = 'setting'
12
13
13 # Remove this, it is not used any more cause there is a user's permission
14 # Remove this, it is not used any more cause there is a user's permission
14 PERMISSION_MODERATE = 'moderator'
15 PERMISSION_MODERATE = 'moderator'
15
16
16 SETTING_THEME = 'theme'
17 SETTING_THEME = 'theme'
17 SETTING_FAVORITE_TAGS = 'favorite_tags'
18 SETTING_FAVORITE_TAGS = 'favorite_tags'
18 SETTING_FAVORITE_THREADS = 'favorite_threads'
19 SETTING_FAVORITE_THREADS = 'favorite_threads'
19 SETTING_HIDDEN_TAGS = 'hidden_tags'
20 SETTING_HIDDEN_TAGS = 'hidden_tags'
20 SETTING_PERMISSIONS = 'permissions'
21 SETTING_PERMISSIONS = 'permissions'
21 SETTING_USERNAME = 'username'
22 SETTING_USERNAME = 'username'
22 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
23 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
23 SETTING_IMAGE_VIEWER = 'image_viewer'
24 SETTING_IMAGE_VIEWER = 'image_viewer'
24 SETTING_TRIPCODE = 'tripcode'
25 SETTING_TRIPCODE = 'tripcode'
25 SETTING_IMAGES = 'images_aliases'
26 SETTING_IMAGES = 'images_aliases'
26 SETTING_ONLY_FAVORITES = 'only_favorites'
27 SETTING_ONLY_FAVORITES = 'only_favorites'
27
28
28 DEFAULT_THEME = 'md'
29 DEFAULT_THEME = 'md'
29
30
30
31
31 class SettingsManager:
32 class SettingsManager:
32 """
33 """
33 Base settings manager class. get_setting and set_setting methods should
34 Base settings manager class. get_setting and set_setting methods should
34 be overriden.
35 be overriden.
35 """
36 """
36 def __init__(self):
37 def __init__(self):
37 pass
38 pass
38
39
39 def get_theme(self) -> str:
40 def get_theme(self) -> str:
40 theme = self.get_setting(SETTING_THEME)
41 theme = self.get_setting(SETTING_THEME)
41 if not theme:
42 if not theme:
42 theme = DEFAULT_THEME
43 theme = DEFAULT_THEME
43 self.set_setting(SETTING_THEME, theme)
44 self.set_setting(SETTING_THEME, theme)
44
45
45 return theme
46 return theme
46
47
47 def set_theme(self, theme):
48 def set_theme(self, theme):
48 self.set_setting(SETTING_THEME, theme)
49 self.set_setting(SETTING_THEME, theme)
49
50
50 def has_permission(self, permission):
51 def has_permission(self, permission):
51 permissions = self.get_setting(SETTING_PERMISSIONS)
52 permissions = self.get_setting(SETTING_PERMISSIONS)
52 if permissions:
53 if permissions:
53 return permission in permissions
54 return permission in permissions
54 else:
55 else:
55 return False
56 return False
56
57
57 def get_setting(self, setting, default=None):
58 def get_setting(self, setting, default=None):
58 pass
59 pass
59
60
60 def set_setting(self, setting, value):
61 def set_setting(self, setting, value):
61 pass
62 pass
62
63
63 def add_permission(self, permission):
64 def add_permission(self, permission):
64 permissions = self.get_setting(SETTING_PERMISSIONS)
65 permissions = self.get_setting(SETTING_PERMISSIONS)
65 if not permissions:
66 if not permissions:
66 permissions = [permission]
67 permissions = [permission]
67 else:
68 else:
68 permissions.append(permission)
69 permissions.append(permission)
69 self.set_setting(SETTING_PERMISSIONS, permissions)
70 self.set_setting(SETTING_PERMISSIONS, permissions)
70
71
71 def del_permission(self, permission):
72 def del_permission(self, permission):
72 permissions = self.get_setting(SETTING_PERMISSIONS)
73 permissions = self.get_setting(SETTING_PERMISSIONS)
73 if not permissions:
74 if not permissions:
74 permissions = []
75 permissions = []
75 else:
76 else:
76 permissions.remove(permission)
77 permissions.remove(permission)
77 self.set_setting(SETTING_PERMISSIONS, permissions)
78 self.set_setting(SETTING_PERMISSIONS, permissions)
78
79
79 def get_fav_tags(self) -> list:
80 def get_fav_tags(self) -> list:
80 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
81 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
81 tags = []
82 tags = []
82 if tag_names:
83 if tag_names:
83 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
84 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
84 .filter_localized(parent__aliases__name__in=tag_names))
85 .filter_localized(parent__aliases__name__in=tag_names))
85 .order_by('aliases__name'))
86 .order_by('aliases__name'))
86 return tags
87 return tags
87
88
88 def add_fav_tag(self, tag):
89 def add_fav_tag(self, tag):
89 tags = self.get_setting(SETTING_FAVORITE_TAGS)
90 tags = self.get_setting(SETTING_FAVORITE_TAGS)
90 if not tags:
91 if not tags:
91 tags = [tag.get_name()]
92 tags = [tag.get_name()]
92 else:
93 else:
93 if not tag.get_name() in tags:
94 if not tag.get_name() in tags:
94 tags.append(tag.get_name())
95 tags.append(tag.get_name())
95
96
96 tags.sort()
97 tags.sort()
97 self.set_setting(SETTING_FAVORITE_TAGS, tags)
98 self.set_setting(SETTING_FAVORITE_TAGS, tags)
98
99
99 def del_fav_tag(self, tag):
100 def del_fav_tag(self, tag):
100 tags = self.get_setting(SETTING_FAVORITE_TAGS)
101 tags = self.get_setting(SETTING_FAVORITE_TAGS)
101 if tag.get_name() in tags:
102 if tag.get_name() in tags:
102 tags.remove(tag.get_name())
103 tags.remove(tag.get_name())
103 self.set_setting(SETTING_FAVORITE_TAGS, tags)
104 self.set_setting(SETTING_FAVORITE_TAGS, tags)
104
105
105 def get_hidden_tags(self) -> list:
106 def get_hidden_tags(self) -> list:
106 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
107 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
107 tags = []
108 tags = []
108 if tag_names:
109 if tag_names:
109 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
110 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
110 .filter_localized(parent__aliases__name__in=tag_names))
111 .filter_localized(parent__aliases__name__in=tag_names))
111 .order_by('aliases__name'))
112 .order_by('aliases__name'))
112
113
113 return tags
114 return tags
114
115
115 def add_hidden_tag(self, tag):
116 def add_hidden_tag(self, tag):
116 tags = self.get_setting(SETTING_HIDDEN_TAGS)
117 tags = self.get_setting(SETTING_HIDDEN_TAGS)
117 if not tags:
118 if not tags:
118 tags = [tag.get_name()]
119 tags = [tag.get_name()]
119 else:
120 else:
120 if not tag.get_name() in tags:
121 if not tag.get_name() in tags:
121 tags.append(tag.get_name())
122 tags.append(tag.get_name())
122
123
123 tags.sort()
124 tags.sort()
124 self.set_setting(SETTING_HIDDEN_TAGS, tags)
125 self.set_setting(SETTING_HIDDEN_TAGS, tags)
125
126
126 def del_hidden_tag(self, tag):
127 def del_hidden_tag(self, tag):
127 tags = self.get_setting(SETTING_HIDDEN_TAGS)
128 tags = self.get_setting(SETTING_HIDDEN_TAGS)
128 if tag.get_name() in tags:
129 if tag.get_name() in tags:
129 tags.remove(tag.get_name())
130 tags.remove(tag.get_name())
130 self.set_setting(SETTING_HIDDEN_TAGS, tags)
131 self.set_setting(SETTING_HIDDEN_TAGS, tags)
131
132
132 def get_fav_threads(self) -> dict:
133 def get_fav_threads(self) -> dict:
133 return self.get_setting(SETTING_FAVORITE_THREADS, default=dict())
134 return self.get_setting(SETTING_FAVORITE_THREADS, default=dict())
134
135
135 def add_or_read_fav_thread(self, opening_post):
136 def add_or_read_fav_thread(self, opening_post):
136 threads = self.get_fav_threads()
137 threads = self.get_fav_threads()
137
138
138 max_fav_threads = settings.get_int('View', 'MaxFavoriteThreads')
139 max_fav_threads = settings.get_int(SECTION_VIEW, 'MaxFavoriteThreads')
139 if (str(opening_post.id) in threads) or (len(threads) < max_fav_threads):
140 if (str(opening_post.id) in threads) or (len(threads) < max_fav_threads):
140 thread = opening_post.get_thread()
141 thread = opening_post.get_thread()
141 # Don't check for new posts if the thread is archived already
142 # Don't check for new posts if the thread is archived already
142 if thread.is_archived():
143 if thread.is_archived():
143 last_id = FAV_THREAD_NO_UPDATES
144 last_id = FAV_THREAD_NO_UPDATES
144 else:
145 else:
145 last_id = thread.get_replies().last().id
146 last_id = thread.get_replies().last().id
146 threads[str(opening_post.id)] = last_id
147 threads[str(opening_post.id)] = last_id
147 self.set_setting(SETTING_FAVORITE_THREADS, threads)
148 self.set_setting(SETTING_FAVORITE_THREADS, threads)
148
149
149 def del_fav_thread(self, opening_post):
150 def del_fav_thread(self, opening_post):
150 threads = self.get_fav_threads()
151 threads = self.get_fav_threads()
151 if self.thread_is_fav(opening_post):
152 if self.thread_is_fav(opening_post):
152 del threads[str(opening_post.id)]
153 del threads[str(opening_post.id)]
153 self.set_setting(SETTING_FAVORITE_THREADS, threads)
154 self.set_setting(SETTING_FAVORITE_THREADS, threads)
154
155
155 def thread_is_fav(self, opening_post):
156 def thread_is_fav(self, opening_post):
156 return str(opening_post.id) in self.get_fav_threads()
157 return str(opening_post.id) in self.get_fav_threads()
157
158
158 def get_notification_usernames(self):
159 def get_notification_usernames(self):
159 names = set()
160 names = set()
160 name_list = self.get_setting(SETTING_USERNAME)
161 name_list = self.get_setting(SETTING_USERNAME)
161 if name_list is not None:
162 if name_list is not None:
162 name_list = name_list.strip()
163 name_list = name_list.strip()
163 if len(name_list) > 0:
164 if len(name_list) > 0:
164 names = name_list.lower().split(',')
165 names = name_list.lower().split(',')
165 names = set(name.strip() for name in names)
166 names = set(name.strip() for name in names)
166 return names
167 return names
167
168
168 def get_attachment_by_alias(self, alias):
169 def get_attachment_by_alias(self, alias):
169 images = self.get_setting(SETTING_IMAGES)
170 images = self.get_setting(SETTING_IMAGES)
170 if images and alias in images:
171 if images and alias in images:
171 try:
172 try:
172 return Attachment.objects.get(id=images.get(alias))
173 return Attachment.objects.get(id=images.get(alias))
173 except Attachment.DoesNotExist:
174 except Attachment.DoesNotExist:
174 self.remove_attachment_alias(alias)
175 self.remove_attachment_alias(alias)
175
176
176 def add_attachment_alias(self, alias, attachment):
177 def add_attachment_alias(self, alias, attachment):
177 images = self.get_setting(SETTING_IMAGES)
178 images = self.get_setting(SETTING_IMAGES)
178 if images is None:
179 if images is None:
179 images = dict()
180 images = dict()
180 images[alias] = attachment.id
181 images[alias] = attachment.id
181 self.set_setting(SETTING_IMAGES, images)
182 self.set_setting(SETTING_IMAGES, images)
182
183
183 def remove_attachment_alias(self, alias):
184 def remove_attachment_alias(self, alias):
184 images = self.get_setting(SETTING_IMAGES)
185 images = self.get_setting(SETTING_IMAGES)
185 del images[alias]
186 del images[alias]
186 self.set_setting(SETTING_IMAGES, images)
187 self.set_setting(SETTING_IMAGES, images)
187
188
188 def get_stickers(self):
189 def get_stickers(self):
189 images = self.get_setting(SETTING_IMAGES)
190 images = self.get_setting(SETTING_IMAGES)
190 stickers = []
191 stickers = []
191 if images:
192 if images:
192 for key, value in images.items():
193 for key, value in images.items():
193 try:
194 try:
194 attachment = Attachment.objects.get(id=value)
195 attachment = Attachment.objects.get(id=value)
195 stickers.append(AttachmentSticker(name=key, attachment=attachment))
196 stickers.append(AttachmentSticker(name=key, attachment=attachment))
196 except Attachment.DoesNotExist:
197 except Attachment.DoesNotExist:
197 self.remove_attachment_alias(key)
198 self.remove_attachment_alias(key)
198 return stickers
199 return stickers
199
200
200
201
201 class SessionSettingsManager(SettingsManager):
202 class SessionSettingsManager(SettingsManager):
202 """
203 """
203 Session-based settings manager. All settings are saved to the user's
204 Session-based settings manager. All settings are saved to the user's
204 session.
205 session.
205 """
206 """
206 def __init__(self, session):
207 def __init__(self, session):
207 SettingsManager.__init__(self)
208 SettingsManager.__init__(self)
208 self.session = session
209 self.session = session
209
210
210 def get_setting(self, setting, default=None):
211 def get_setting(self, setting, default=None):
211 if setting in self.session:
212 if setting in self.session:
212 return self.session[setting]
213 return self.session[setting]
213 else:
214 else:
214 self.set_setting(setting, default)
215 self.set_setting(setting, default)
215 return default
216 return default
216
217
217 def set_setting(self, setting, value):
218 def set_setting(self, setting, value):
218 self.session[setting] = value
219 self.session[setting] = value
219
220
220
221
221 def get_settings_manager(request) -> SettingsManager:
222 def get_settings_manager(request) -> SettingsManager:
222 """
223 """
223 Get settings manager based on the request object. Currently only
224 Get settings manager based on the request object. Currently only
224 session-based manager is supported. In the future, cookie-based or
225 session-based manager is supported. In the future, cookie-based or
225 database-based managers could be implemented.
226 database-based managers could be implemented.
226 """
227 """
227 return SessionSettingsManager(request.session)
228 return SessionSettingsManager(request.session)
@@ -1,588 +1,588 b''
1 import logging
1 import logging
2 import time
2 import time
3
3
4 import hashlib
4 import hashlib
5 import pytz
5 import pytz
6 import re
6 import re
7 from PIL import Image
7 from PIL import Image
8 from django import forms
8 from django import forms
9 from django.core.cache import cache
9 from django.core.cache import cache
10 from django.core.files.images import get_image_dimensions
10 from django.core.files.images import get_image_dimensions
11 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
11 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
12 from django.forms.utils import ErrorList
12 from django.forms.utils import ErrorList
13 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
13 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
14
14
15 import boards.settings as board_settings
15 import boards.settings as board_settings
16 from boards import utils
16 from boards import utils
17 from boards.abstracts.constants import REGEX_TAGS
17 from boards.abstracts.constants import REGEX_TAGS
18 from boards.abstracts.settingsmanager import get_settings_manager
18 from boards.abstracts.settingsmanager import get_settings_manager
19 from boards.abstracts.sticker_factory import get_attachment_by_alias
19 from boards.abstracts.sticker_factory import get_attachment_by_alias
20 from boards.forms.fields import UrlFileField
20 from boards.forms.fields import UrlFileField
21 from boards.mdx_neboard import formatters
21 from boards.mdx_neboard import formatters
22 from boards.models import Attachment
22 from boards.models import Attachment
23 from boards.models import Tag
23 from boards.models import Tag
24 from boards.models.attachment import StickerPack
24 from boards.models.attachment import StickerPack
25 from boards.models.attachment.downloaders import download, REGEX_MAGNET
25 from boards.models.attachment.downloaders import download, REGEX_MAGNET
26 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
26 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
27 from boards.models.post import TITLE_MAX_LENGTH
27 from boards.models.post import TITLE_MAX_LENGTH
28 from boards.utils import validate_file_size, get_file_mimetype, \
28 from boards.utils import validate_file_size, get_file_mimetype, \
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
30 from boards.settings import SECTION_FORMS
30
31
31 SECTION_FORMS = 'Forms'
32
32
33 POW_HASH_LENGTH = 16
33 POW_HASH_LENGTH = 16
34 POW_LIFE_MINUTES = 5
34 POW_LIFE_MINUTES = 5
35
35
36 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
36 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
37 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
37 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
38
38
39 VETERAN_POSTING_DELAY = 5
39 VETERAN_POSTING_DELAY = 5
40
40
41 ATTRIBUTE_PLACEHOLDER = 'placeholder'
41 ATTRIBUTE_PLACEHOLDER = 'placeholder'
42 ATTRIBUTE_ROWS = 'rows'
42 ATTRIBUTE_ROWS = 'rows'
43
43
44 LAST_POST_TIME = 'last_post_time'
44 LAST_POST_TIME = 'last_post_time'
45 LAST_LOGIN_TIME = 'last_login_time'
45 LAST_LOGIN_TIME = 'last_login_time'
46 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
46 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
47 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
47 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
48
48
49 LABEL_TITLE = _('Title')
49 LABEL_TITLE = _('Title')
50 LABEL_TEXT = _('Text')
50 LABEL_TEXT = _('Text')
51 LABEL_TAG = _('Tag')
51 LABEL_TAG = _('Tag')
52 LABEL_SEARCH = _('Search')
52 LABEL_SEARCH = _('Search')
53 LABEL_FILE = _('File')
53 LABEL_FILE = _('File')
54 LABEL_DUPLICATES = _('Check for duplicates')
54 LABEL_DUPLICATES = _('Check for duplicates')
55 LABEL_URL = _('Do not download URLs')
55 LABEL_URL = _('Do not download URLs')
56
56
57 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
57 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
58 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
58 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
59 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
59 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
60 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
60 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
61 ERROR_DUPLICATES = 'Some files are already present on the board.'
61 ERROR_DUPLICATES = 'Some files are already present on the board.'
62
62
63 TAG_MAX_LENGTH = 20
63 TAG_MAX_LENGTH = 20
64
64
65 TEXTAREA_ROWS = 4
65 TEXTAREA_ROWS = 4
66
66
67 TRIPCODE_DELIM = '##'
67 TRIPCODE_DELIM = '##'
68
68
69 # TODO Maybe this may be converted into the database table?
69 # TODO Maybe this may be converted into the database table?
70 MIMETYPE_EXTENSIONS = {
70 MIMETYPE_EXTENSIONS = {
71 'image/jpeg': 'jpeg',
71 'image/jpeg': 'jpeg',
72 'image/png': 'png',
72 'image/png': 'png',
73 'image/gif': 'gif',
73 'image/gif': 'gif',
74 'video/webm': 'webm',
74 'video/webm': 'webm',
75 'application/pdf': 'pdf',
75 'application/pdf': 'pdf',
76 'x-diff': 'diff',
76 'x-diff': 'diff',
77 'image/svg+xml': 'svg',
77 'image/svg+xml': 'svg',
78 'application/x-shockwave-flash': 'swf',
78 'application/x-shockwave-flash': 'swf',
79 'image/x-ms-bmp': 'bmp',
79 'image/x-ms-bmp': 'bmp',
80 'image/bmp': 'bmp',
80 'image/bmp': 'bmp',
81 }
81 }
82
82
83 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
83 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
84 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
84 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
85 DOWN_MODE_URL = 'URL'
85 DOWN_MODE_URL = 'URL'
86 DOWN_MODE_TRY = 'TRY'
86 DOWN_MODE_TRY = 'TRY'
87
87
88
88
89 logger = logging.getLogger('boards.forms')
89 logger = logging.getLogger('boards.forms')
90
90
91
91
92 def get_timezones():
92 def get_timezones():
93 timezones = []
93 timezones = []
94 for tz in pytz.common_timezones:
94 for tz in pytz.common_timezones:
95 timezones.append((tz, tz),)
95 timezones.append((tz, tz),)
96 return timezones
96 return timezones
97
97
98
98
99 class FormatPanel(forms.Textarea):
99 class FormatPanel(forms.Textarea):
100 """
100 """
101 Panel for text formatting. Consists of buttons to add different tags to the
101 Panel for text formatting. Consists of buttons to add different tags to the
102 form text area.
102 form text area.
103 """
103 """
104
104
105 def render(self, name, value, attrs=None):
105 def render(self, name, value, attrs=None):
106 output = '<div id="mark-panel">'
106 output = '<div id="mark-panel">'
107 for formatter in formatters:
107 for formatter in formatters:
108 output += '<span class="mark_btn"' + \
108 output += '<span class="mark_btn"' + \
109 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
109 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
110 '\', \'' + formatter.format_right + '\')">' + \
110 '\', \'' + formatter.format_right + '\')">' + \
111 formatter.preview_left + formatter.name + \
111 formatter.preview_left + formatter.name + \
112 formatter.preview_right + '</span>'
112 formatter.preview_right + '</span>'
113
113
114 output += '</div>'
114 output += '</div>'
115 output += super(FormatPanel, self).render(name, value, attrs=attrs)
115 output += super(FormatPanel, self).render(name, value, attrs=attrs)
116
116
117 return output
117 return output
118
118
119
119
120 class PlainErrorList(ErrorList):
120 class PlainErrorList(ErrorList):
121 def __unicode__(self):
121 def __unicode__(self):
122 return self.as_text()
122 return self.as_text()
123
123
124 def as_text(self):
124 def as_text(self):
125 return ''.join(['(!) %s ' % e for e in self])
125 return ''.join(['(!) %s ' % e for e in self])
126
126
127
127
128 class NeboardForm(forms.Form):
128 class NeboardForm(forms.Form):
129 """
129 """
130 Form with neboard-specific formatting.
130 Form with neboard-specific formatting.
131 """
131 """
132 required_css_class = 'required-field'
132 required_css_class = 'required-field'
133
133
134 def as_div(self):
134 def as_div(self):
135 """
135 """
136 Returns this form rendered as HTML <as_div>s.
136 Returns this form rendered as HTML <as_div>s.
137 """
137 """
138
138
139 return self._html_output(
139 return self._html_output(
140 # TODO Do not show hidden rows in the list here
140 # TODO Do not show hidden rows in the list here
141 normal_row='<div class="form-row">'
141 normal_row='<div class="form-row">'
142 '<div class="form-label">'
142 '<div class="form-label">'
143 '%(label)s'
143 '%(label)s'
144 '</div>'
144 '</div>'
145 '<div class="form-input">'
145 '<div class="form-input">'
146 '%(field)s'
146 '%(field)s'
147 '</div>'
147 '</div>'
148 '</div>'
148 '</div>'
149 '<div class="form-row">'
149 '<div class="form-row">'
150 '%(help_text)s'
150 '%(help_text)s'
151 '</div>',
151 '</div>',
152 error_row='<div class="form-row">'
152 error_row='<div class="form-row">'
153 '<div class="form-label"></div>'
153 '<div class="form-label"></div>'
154 '<div class="form-errors">%s</div>'
154 '<div class="form-errors">%s</div>'
155 '</div>',
155 '</div>',
156 row_ender='</div>',
156 row_ender='</div>',
157 help_text_html='%s',
157 help_text_html='%s',
158 errors_on_separate_row=True)
158 errors_on_separate_row=True)
159
159
160 def as_json_errors(self):
160 def as_json_errors(self):
161 errors = []
161 errors = []
162
162
163 for name, field in list(self.fields.items()):
163 for name, field in list(self.fields.items()):
164 if self[name].errors:
164 if self[name].errors:
165 errors.append({
165 errors.append({
166 'field': name,
166 'field': name,
167 'errors': self[name].errors.as_text(),
167 'errors': self[name].errors.as_text(),
168 })
168 })
169
169
170 return errors
170 return errors
171
171
172
172
173 class PostForm(NeboardForm):
173 class PostForm(NeboardForm):
174
174
175 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
175 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
176 label=LABEL_TITLE,
176 label=LABEL_TITLE,
177 widget=forms.TextInput(
177 widget=forms.TextInput(
178 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
178 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
179 text = forms.CharField(
179 text = forms.CharField(
180 widget=FormatPanel(attrs={
180 widget=FormatPanel(attrs={
181 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
181 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
182 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
182 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
183 }),
183 }),
184 required=False, label=LABEL_TEXT)
184 required=False, label=LABEL_TEXT)
185 download_mode = forms.ChoiceField(
185 download_mode = forms.ChoiceField(
186 choices=(
186 choices=(
187 (DOWN_MODE_TRY, _('Download or insert as URLs')),
187 (DOWN_MODE_TRY, _('Download or insert as URLs')),
188 (DOWN_MODE_DOWNLOAD, _('Download')),
188 (DOWN_MODE_DOWNLOAD, _('Download')),
189 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
189 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
190 (DOWN_MODE_URL, _('Insert as URLs')),
190 (DOWN_MODE_URL, _('Insert as URLs')),
191 ),
191 ),
192 initial=DOWN_MODE_TRY,
192 initial=DOWN_MODE_TRY,
193 label=_('File process mode'))
193 label=_('File process mode'))
194 file = UrlFileField(required=False, label=LABEL_FILE)
194 file = UrlFileField(required=False, label=LABEL_FILE)
195
195
196 # This field is for spam prevention only
196 # This field is for spam prevention only
197 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
197 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
198 widget=forms.TextInput(attrs={
198 widget=forms.TextInput(attrs={
199 'class': 'form-email'}))
199 'class': 'form-email'}))
200 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
200 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
201
201
202 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
202 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
203 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
203 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
204 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
204 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
205
205
206 session = None
206 session = None
207 need_to_ban = False
207 need_to_ban = False
208
208
209 def clean_title(self):
209 def clean_title(self):
210 title = self.cleaned_data['title']
210 title = self.cleaned_data['title']
211 if title:
211 if title:
212 if len(title) > TITLE_MAX_LENGTH:
212 if len(title) > TITLE_MAX_LENGTH:
213 raise forms.ValidationError(_('Title must have less than %s '
213 raise forms.ValidationError(_('Title must have less than %s '
214 'characters') %
214 'characters') %
215 str(TITLE_MAX_LENGTH))
215 str(TITLE_MAX_LENGTH))
216 return title
216 return title
217
217
218 def clean_text(self):
218 def clean_text(self):
219 text = self.cleaned_data['text'].strip()
219 text = self.cleaned_data['text'].strip()
220 if text:
220 if text:
221 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
221 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
222 if len(text) > max_length:
222 if len(text) > max_length:
223 raise forms.ValidationError(_('Text must have less than %s '
223 raise forms.ValidationError(_('Text must have less than %s '
224 'characters') % str(max_length))
224 'characters') % str(max_length))
225 return text
225 return text
226
226
227 def clean_file(self):
227 def clean_file(self):
228 return self._clean_files(self.cleaned_data['file'])
228 return self._clean_files(self.cleaned_data['file'])
229
229
230 def clean(self):
230 def clean(self):
231 cleaned_data = super(PostForm, self).clean()
231 cleaned_data = super(PostForm, self).clean()
232
232
233 if cleaned_data['email']:
233 if cleaned_data['email']:
234 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
234 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
235 self.need_to_ban = True
235 self.need_to_ban = True
236 raise forms.ValidationError('A human cannot enter a hidden field')
236 raise forms.ValidationError('A human cannot enter a hidden field')
237
237
238 if not self.errors:
238 if not self.errors:
239 self._clean_text_file()
239 self._clean_text_file()
240
240
241 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
241 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
242 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
242 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
243
243
244 settings_manager = get_settings_manager(self)
244 settings_manager = get_settings_manager(self)
245 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
245 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
246 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
246 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
247 if pow_difficulty > 0:
247 if pow_difficulty > 0:
248 # PoW-based
248 # PoW-based
249 if cleaned_data['timestamp'] \
249 if cleaned_data['timestamp'] \
250 and cleaned_data['iteration'] and cleaned_data['guess'] \
250 and cleaned_data['iteration'] and cleaned_data['guess'] \
251 and not settings_manager.get_setting('confirmed_user'):
251 and not settings_manager.get_setting('confirmed_user'):
252 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
252 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
253 else:
253 else:
254 # Time-based
254 # Time-based
255 self._validate_posting_speed()
255 self._validate_posting_speed()
256 settings_manager.set_setting('confirmed_user', True)
256 settings_manager.set_setting('confirmed_user', True)
257 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
257 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
258 self._check_file_duplicates(self.get_files())
258 self._check_file_duplicates(self.get_files())
259
259
260 return cleaned_data
260 return cleaned_data
261
261
262 def get_files(self):
262 def get_files(self):
263 """
263 """
264 Gets file from form or URL.
264 Gets file from form or URL.
265 """
265 """
266
266
267 files = []
267 files = []
268 for file in self.cleaned_data['file']:
268 for file in self.cleaned_data['file']:
269 if isinstance(file, UploadedFile):
269 if isinstance(file, UploadedFile):
270 files.append(file)
270 files.append(file)
271
271
272 return files
272 return files
273
273
274 def get_file_urls(self):
274 def get_file_urls(self):
275 files = []
275 files = []
276 for file in self.cleaned_data['file']:
276 for file in self.cleaned_data['file']:
277 if type(file) == str:
277 if type(file) == str:
278 files.append(file)
278 files.append(file)
279
279
280 return files
280 return files
281
281
282 def get_tripcode(self):
282 def get_tripcode(self):
283 title = self.cleaned_data['title']
283 title = self.cleaned_data['title']
284 if title is not None and TRIPCODE_DELIM in title:
284 if title is not None and TRIPCODE_DELIM in title:
285 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
285 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
286 else:
286 else:
287 tripcode = ''
287 tripcode = ''
288 return tripcode
288 return tripcode
289
289
290 def get_title(self):
290 def get_title(self):
291 title = self.cleaned_data['title']
291 title = self.cleaned_data['title']
292 if title is not None and TRIPCODE_DELIM in title:
292 if title is not None and TRIPCODE_DELIM in title:
293 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
293 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
294 else:
294 else:
295 return title
295 return title
296
296
297 def get_images(self):
297 def get_images(self):
298 return self.cleaned_data.get('stickers', [])
298 return self.cleaned_data.get('stickers', [])
299
299
300 def is_subscribe(self):
300 def is_subscribe(self):
301 return self.cleaned_data['subscribe']
301 return self.cleaned_data['subscribe']
302
302
303 def _update_file_extension(self, file):
303 def _update_file_extension(self, file):
304 if file:
304 if file:
305 mimetype = get_file_mimetype(file)
305 mimetype = get_file_mimetype(file)
306 extension = MIMETYPE_EXTENSIONS.get(mimetype)
306 extension = MIMETYPE_EXTENSIONS.get(mimetype)
307 if extension:
307 if extension:
308 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
308 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
309 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
309 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
310
310
311 file.name = new_filename
311 file.name = new_filename
312 else:
312 else:
313 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
313 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
314
314
315 def _clean_files(self, inputs):
315 def _clean_files(self, inputs):
316 files = []
316 files = []
317
317
318 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
318 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
319 if len(inputs) > max_file_count:
319 if len(inputs) > max_file_count:
320 raise forms.ValidationError(
320 raise forms.ValidationError(
321 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
321 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
322 max_file_count) % {'files': max_file_count})
322 max_file_count) % {'files': max_file_count})
323
323
324 size = 0
324 size = 0
325 for file_input in inputs:
325 for file_input in inputs:
326 if isinstance(file_input, UploadedFile):
326 if isinstance(file_input, UploadedFile):
327 file = self._clean_file_file(file_input)
327 file = self._clean_file_file(file_input)
328 size += file.size
328 size += file.size
329 files.append(file)
329 files.append(file)
330 else:
330 else:
331 files.append(self._clean_file_url(file_input))
331 files.append(self._clean_file_url(file_input))
332
332
333 for file in files:
333 for file in files:
334 self._validate_image_dimensions(file)
334 self._validate_image_dimensions(file)
335 validate_file_size(size)
335 validate_file_size(size)
336
336
337 return files
337 return files
338
338
339 def _validate_image_dimensions(self, file):
339 def _validate_image_dimensions(self, file):
340 if isinstance(file, UploadedFile):
340 if isinstance(file, UploadedFile):
341 mimetype = get_file_mimetype(file)
341 mimetype = get_file_mimetype(file)
342 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
342 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
343 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
343 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
344 try:
344 try:
345 print(get_image_dimensions(file))
345 print(get_image_dimensions(file))
346 except Exception:
346 except Exception:
347 raise forms.ValidationError('Possible decompression bomb or large image.')
347 raise forms.ValidationError('Possible decompression bomb or large image.')
348
348
349 def _clean_file_file(self, file):
349 def _clean_file_file(self, file):
350 self._update_file_extension(file)
350 self._update_file_extension(file)
351
351
352 return file
352 return file
353
353
354 def _clean_file_url(self, url):
354 def _clean_file_url(self, url):
355 file = None
355 file = None
356
356
357 if url:
357 if url:
358 mode = self.cleaned_data['download_mode']
358 mode = self.cleaned_data['download_mode']
359 if mode == DOWN_MODE_URL:
359 if mode == DOWN_MODE_URL:
360 return url
360 return url
361
361
362 try:
362 try:
363 image = get_attachment_by_alias(url, self.session)
363 image = get_attachment_by_alias(url, self.session)
364 if image is not None:
364 if image is not None:
365 if 'stickers' not in self.cleaned_data:
365 if 'stickers' not in self.cleaned_data:
366 self.cleaned_data['stickers'] = []
366 self.cleaned_data['stickers'] = []
367 self.cleaned_data['stickers'].append(image)
367 self.cleaned_data['stickers'].append(image)
368 return
368 return
369
369
370 if file is None:
370 if file is None:
371 file = self._get_file_from_url(url)
371 file = self._get_file_from_url(url)
372 if not file:
372 if not file:
373 raise forms.ValidationError(_('Invalid URL'))
373 raise forms.ValidationError(_('Invalid URL'))
374 self._update_file_extension(file)
374 self._update_file_extension(file)
375 except forms.ValidationError as e:
375 except forms.ValidationError as e:
376 # Assume we will get the plain URL instead of a file and save it
376 # Assume we will get the plain URL instead of a file and save it
377 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
377 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
378 logger.info('Error in forms: {}'.format(e))
378 logger.info('Error in forms: {}'.format(e))
379 return url
379 return url
380 else:
380 else:
381 raise e
381 raise e
382
382
383 return file
383 return file
384
384
385 def _clean_text_file(self):
385 def _clean_text_file(self):
386 text = self.cleaned_data.get('text')
386 text = self.cleaned_data.get('text')
387 file = self.get_files()
387 file = self.get_files()
388 file_url = self.get_file_urls()
388 file_url = self.get_file_urls()
389 images = self.get_images()
389 images = self.get_images()
390
390
391 if (not text) and (not file) and (not file_url) and len(images) == 0:
391 if (not text) and (not file) and (not file_url) and len(images) == 0:
392 error_message = _('Either text or file must be entered.')
392 error_message = _('Either text or file must be entered.')
393 self._add_general_error(error_message)
393 self._add_general_error(error_message)
394
394
395 def _get_cache_key(self, key):
395 def _get_cache_key(self, key):
396 return '{}_{}'.format(self.session.session_key, key)
396 return '{}_{}'.format(self.session.session_key, key)
397
397
398 def _set_session_cache(self, key, value):
398 def _set_session_cache(self, key, value):
399 cache.set(self._get_cache_key(key), value)
399 cache.set(self._get_cache_key(key), value)
400
400
401 def _get_session_cache(self, key):
401 def _get_session_cache(self, key):
402 return cache.get(self._get_cache_key(key))
402 return cache.get(self._get_cache_key(key))
403
403
404 def _get_last_post_time(self):
404 def _get_last_post_time(self):
405 last = self._get_session_cache(LAST_POST_TIME)
405 last = self._get_session_cache(LAST_POST_TIME)
406 if last is None:
406 if last is None:
407 last = self.session.get(LAST_POST_TIME)
407 last = self.session.get(LAST_POST_TIME)
408 return last
408 return last
409
409
410 def _validate_posting_speed(self):
410 def _validate_posting_speed(self):
411 can_post = True
411 can_post = True
412
412
413 posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay')
413 posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay')
414
414
415 if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'):
415 if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'):
416 now = time.time()
416 now = time.time()
417
417
418 current_delay = 0
418 current_delay = 0
419
419
420 if LAST_POST_TIME not in self.session:
420 if LAST_POST_TIME not in self.session:
421 self.session[LAST_POST_TIME] = now
421 self.session[LAST_POST_TIME] = now
422
422
423 need_delay = True
423 need_delay = True
424 else:
424 else:
425 last_post_time = self._get_last_post_time()
425 last_post_time = self._get_last_post_time()
426 current_delay = int(now - last_post_time)
426 current_delay = int(now - last_post_time)
427
427
428 need_delay = current_delay < posting_delay
428 need_delay = current_delay < posting_delay
429
429
430 self._set_session_cache(LAST_POST_TIME, now)
430 self._set_session_cache(LAST_POST_TIME, now)
431
431
432 if need_delay:
432 if need_delay:
433 delay = posting_delay - current_delay
433 delay = posting_delay - current_delay
434 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
434 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
435 delay) % {'delay': delay}
435 delay) % {'delay': delay}
436 self._add_general_error(error_message)
436 self._add_general_error(error_message)
437
437
438 can_post = False
438 can_post = False
439
439
440 if can_post:
440 if can_post:
441 self.session[LAST_POST_TIME] = now
441 self.session[LAST_POST_TIME] = now
442 else:
442 else:
443 # Reset the time since posting failed
443 # Reset the time since posting failed
444 self._set_session_cache(LAST_POST_TIME, self.session[LAST_POST_TIME])
444 self._set_session_cache(LAST_POST_TIME, self.session[LAST_POST_TIME])
445
445
446 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
446 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
447 """
447 """
448 Gets an file file from URL.
448 Gets an file file from URL.
449 """
449 """
450
450
451 try:
451 try:
452 return download(url)
452 return download(url)
453 except forms.ValidationError as e:
453 except forms.ValidationError as e:
454 raise e
454 raise e
455 except Exception as e:
455 except Exception as e:
456 raise forms.ValidationError(e)
456 raise forms.ValidationError(e)
457
457
458 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
458 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
459 payload = timestamp + message.replace('\r\n', '\n')
459 payload = timestamp + message.replace('\r\n', '\n')
460 difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
460 difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
461 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
461 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
462 if len(target) < POW_HASH_LENGTH:
462 if len(target) < POW_HASH_LENGTH:
463 target = '0' * (POW_HASH_LENGTH - len(target)) + target
463 target = '0' * (POW_HASH_LENGTH - len(target)) + target
464
464
465 computed_guess = hashlib.sha256((payload + iteration).encode())\
465 computed_guess = hashlib.sha256((payload + iteration).encode())\
466 .hexdigest()[0:POW_HASH_LENGTH]
466 .hexdigest()[0:POW_HASH_LENGTH]
467 if guess != computed_guess or guess > target:
467 if guess != computed_guess or guess > target:
468 self._add_general_error(_('Invalid PoW.'))
468 self._add_general_error(_('Invalid PoW.'))
469
469
470 def _check_file_duplicates(self, files):
470 def _check_file_duplicates(self, files):
471 for file in files:
471 for file in files:
472 file_hash = utils.get_file_hash(file)
472 file_hash = utils.get_file_hash(file)
473 if Attachment.objects.get_existing_duplicate(file_hash, file):
473 if Attachment.objects.get_existing_duplicate(file_hash, file):
474 self._add_general_error(_(ERROR_DUPLICATES))
474 self._add_general_error(_(ERROR_DUPLICATES))
475
475
476 def _add_general_error(self, message):
476 def _add_general_error(self, message):
477 self.add_error('text', forms.ValidationError(message))
477 self.add_error('text', forms.ValidationError(message))
478
478
479
479
480 class ThreadForm(PostForm):
480 class ThreadForm(PostForm):
481
481
482 tags = forms.CharField(
482 tags = forms.CharField(
483 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
483 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
484 max_length=100, label=_('Tags'), required=True)
484 max_length=100, label=_('Tags'), required=True)
485 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
485 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
486 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
486 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
487
487
488 def clean_tags(self):
488 def clean_tags(self):
489 tags = self.cleaned_data['tags'].strip()
489 tags = self.cleaned_data['tags'].strip()
490
490
491 if not tags or not REGEX_TAGS.match(tags):
491 if not tags or not REGEX_TAGS.match(tags):
492 raise forms.ValidationError(
492 raise forms.ValidationError(
493 _('Inappropriate characters in tags.'))
493 _('Inappropriate characters in tags.'))
494
494
495 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
495 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
496 .strip().lower()
496 .strip().lower()
497
497
498 required_tag_exists = False
498 required_tag_exists = False
499 tag_set = set()
499 tag_set = set()
500 for tag_string in tags.split():
500 for tag_string in tags.split():
501 tag_name = tag_string.strip().lower()
501 tag_name = tag_string.strip().lower()
502 if tag_name == default_tag_name:
502 if tag_name == default_tag_name:
503 required_tag_exists = True
503 required_tag_exists = True
504 tag, created = Tag.objects.get_or_create_with_alias(
504 tag, created = Tag.objects.get_or_create_with_alias(
505 name=tag_name, required=True)
505 name=tag_name, required=True)
506 else:
506 else:
507 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
507 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
508 tag_set.add(tag)
508 tag_set.add(tag)
509
509
510 # If this is a new tag, don't check for its parents because nobody
510 # If this is a new tag, don't check for its parents because nobody
511 # added them yet
511 # added them yet
512 if not created:
512 if not created:
513 tag_set |= set(tag.get_all_parents())
513 tag_set |= set(tag.get_all_parents())
514
514
515 for tag in tag_set:
515 for tag in tag_set:
516 if tag.required:
516 if tag.required:
517 required_tag_exists = True
517 required_tag_exists = True
518 break
518 break
519
519
520 # Use default tag if no section exists
520 # Use default tag if no section exists
521 if not required_tag_exists:
521 if not required_tag_exists:
522 default_tag, created = Tag.objects.get_or_create_with_alias(
522 default_tag, created = Tag.objects.get_or_create_with_alias(
523 name=default_tag_name, required=True)
523 name=default_tag_name, required=True)
524 tag_set.add(default_tag)
524 tag_set.add(default_tag)
525
525
526 return tag_set
526 return tag_set
527
527
528 def clean(self):
528 def clean(self):
529 cleaned_data = super(ThreadForm, self).clean()
529 cleaned_data = super(ThreadForm, self).clean()
530
530
531 return cleaned_data
531 return cleaned_data
532
532
533 def is_monochrome(self):
533 def is_monochrome(self):
534 return self.cleaned_data['monochrome']
534 return self.cleaned_data['monochrome']
535
535
536 def clean_stickerpack(self):
536 def clean_stickerpack(self):
537 stickerpack = self.cleaned_data['stickerpack']
537 stickerpack = self.cleaned_data['stickerpack']
538 if stickerpack:
538 if stickerpack:
539 tripcode = self.get_tripcode()
539 tripcode = self.get_tripcode()
540 if not tripcode:
540 if not tripcode:
541 raise forms.ValidationError(_(
541 raise forms.ValidationError(_(
542 'Tripcode should be specified to own a stickerpack.'))
542 'Tripcode should be specified to own a stickerpack.'))
543 title = self.get_title()
543 title = self.get_title()
544 if not title:
544 if not title:
545 raise forms.ValidationError(_(
545 raise forms.ValidationError(_(
546 'Title should be specified as a stickerpack name.'))
546 'Title should be specified as a stickerpack name.'))
547 if not REGEX_TAGS.match(title):
547 if not REGEX_TAGS.match(title):
548 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
548 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
549
549
550 existing_pack = StickerPack.objects.filter(name=title).first()
550 existing_pack = StickerPack.objects.filter(name=title).first()
551 if existing_pack:
551 if existing_pack:
552 if existing_pack.tripcode != tripcode:
552 if existing_pack.tripcode != tripcode:
553 raise forms.ValidationError(_(
553 raise forms.ValidationError(_(
554 'A sticker pack with this name already exists and is'
554 'A sticker pack with this name already exists and is'
555 ' owned by another tripcode.'))
555 ' owned by another tripcode.'))
556 if not existing_pack.tripcode:
556 if not existing_pack.tripcode:
557 raise forms.ValidationError(_(
557 raise forms.ValidationError(_(
558 'This sticker pack can only be updated by an '
558 'This sticker pack can only be updated by an '
559 'administrator.'))
559 'administrator.'))
560
560
561 return stickerpack
561 return stickerpack
562
562
563 def is_stickerpack(self):
563 def is_stickerpack(self):
564 return self.cleaned_data['stickerpack']
564 return self.cleaned_data['stickerpack']
565
565
566
566
567 class SettingsForm(NeboardForm):
567 class SettingsForm(NeboardForm):
568
568
569 theme = forms.ChoiceField(
569 theme = forms.ChoiceField(
570 choices=board_settings.get_list_dict('View', 'Themes'),
570 choices=board_settings.get_list_dict('View', 'Themes'),
571 label=_('Theme'))
571 label=_('Theme'))
572 image_viewer = forms.ChoiceField(
572 image_viewer = forms.ChoiceField(
573 choices=board_settings.get_list_dict('View', 'ImageViewers'),
573 choices=board_settings.get_list_dict('View', 'ImageViewers'),
574 label=_('Image view mode'))
574 label=_('Image view mode'))
575 username = forms.CharField(label=_('User name'), required=False)
575 username = forms.CharField(label=_('User name'), required=False)
576 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
576 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
577
577
578 def clean_username(self):
578 def clean_username(self):
579 username = self.cleaned_data['username']
579 username = self.cleaned_data['username']
580
580
581 if username and not REGEX_USERNAMES.match(username):
581 if username and not REGEX_USERNAMES.match(username):
582 raise forms.ValidationError(_('Inappropriate characters.'))
582 raise forms.ValidationError(_('Inappropriate characters.'))
583
583
584 return username
584 return username
585
585
586
586
587 class SearchForm(NeboardForm):
587 class SearchForm(NeboardForm):
588 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
588 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,41 +1,42 b''
1 import configparser
1 import configparser
2
2
3
3
4 CONFIG_DEFAULT_SETTINGS = 'boards/config/default_settings.ini'
4 CONFIG_DEFAULT_SETTINGS = 'boards/config/default_settings.ini'
5 CONFIG_SETTINGS = 'boards/config/settings.ini'
5 CONFIG_SETTINGS = 'boards/config/settings.ini'
6
6
7
7
8 SECTION_FORMS = 'Forms'
8 SECTION_FORMS = 'Forms'
9 SECTION_VIEW = 'View'
9
10
10 VALUE_TRUE = 'true'
11 VALUE_TRUE = 'true'
11
12
12 LIST_DELIMITER = ','
13 LIST_DELIMITER = ','
13 DICT_DELIMITER = ':'
14 DICT_DELIMITER = ':'
14
15
15
16
16 config = configparser.ConfigParser()
17 config = configparser.ConfigParser()
17 config.read(CONFIG_DEFAULT_SETTINGS)
18 config.read(CONFIG_DEFAULT_SETTINGS)
18 config.read(CONFIG_SETTINGS)
19 config.read(CONFIG_SETTINGS)
19
20
20
21
21 def get(section, name):
22 def get(section, name):
22 return config[section][name]
23 return config[section][name]
23
24
24
25
25 def get_int(section, name):
26 def get_int(section, name):
26 return int(get(section, name))
27 return int(get(section, name))
27
28
28
29
29 def get_bool(section, name):
30 def get_bool(section, name):
30 return get(section, name) == VALUE_TRUE
31 return get(section, name) == VALUE_TRUE
31
32
32
33
33 def get_list_dict(section, name):
34 def get_list_dict(section, name):
34 str_dict = get(section, name)
35 str_dict = get(section, name)
35 return [item.split(DICT_DELIMITER) for item in str_dict.split(LIST_DELIMITER)]
36 return [item.split(DICT_DELIMITER) for item in str_dict.split(LIST_DELIMITER)]
36
37
37
38
38 def get_list(section, name):
39 def get_list(section, name):
39 str_list = get(section, name)
40 str_list = get(section, name)
40 return str_list.split(LIST_DELIMITER)
41 return str_list.split(LIST_DELIMITER)
41
42
@@ -1,161 +1,161 b''
1 """
1 """
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import time
4 import time
5 import uuid
5 import uuid
6
6
7 import hashlib
7 import hashlib
8 import magic
8 import magic
9 import os
9 import os
10 from django import forms
10 from django import forms
11 from django.core.cache import cache
11 from django.core.cache import cache
12 from django.db.models import Model
12 from django.db.models import Model
13 from django.template.defaultfilters import filesizeformat
13 from django.template.defaultfilters import filesizeformat
14 from django.utils import timezone
14 from django.utils import timezone
15 from django.utils.translation import ugettext_lazy as _
15 from django.utils.translation import ugettext_lazy as _
16
16
17 import boards
17 import boards
18 from neboard import settings
18 from neboard import settings
19 from boards.abstracts.constants import FILE_DIRECTORY
19 from boards.abstracts.constants import FILE_DIRECTORY
20 from boards.settings import get_bool
20 from boards.settings import get_bool, SECTION_FORMS
21
21
22 CACHE_KEY_DELIMITER = '_'
22 CACHE_KEY_DELIMITER = '_'
23
23
24 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
24 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
25 META_REMOTE_ADDR = 'REMOTE_ADDR'
25 META_REMOTE_ADDR = 'REMOTE_ADDR'
26
26
27 SETTING_MESSAGES = 'Messages'
27 SETTING_MESSAGES = 'Messages'
28 SETTING_ANON_MODE = 'AnonymousMode'
28 SETTING_ANON_MODE = 'AnonymousMode'
29
29
30 ANON_IP = '127.0.0.1'
30 ANON_IP = '127.0.0.1'
31
31
32 FILE_EXTENSION_DELIMITER = '.'
32 FILE_EXTENSION_DELIMITER = '.'
33 URL_DELIMITER = '/'
33 URL_DELIMITER = '/'
34 CACHE_KEY_DELIMITER = ':'
34 CACHE_KEY_DELIMITER = ':'
35
35
36 DEFAULT_MIMETYPE = 'application/octet-stream'
36 DEFAULT_MIMETYPE = 'application/octet-stream'
37
37
38
38
39 def is_anonymous_mode():
39 def is_anonymous_mode():
40 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
40 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
41
41
42
42
43 def get_client_ip(request):
43 def get_client_ip(request):
44 if is_anonymous_mode():
44 if is_anonymous_mode():
45 ip = ANON_IP
45 ip = ANON_IP
46 else:
46 else:
47 x_forwarded_for = request.META.get(HTTP_FORWARDED)
47 x_forwarded_for = request.META.get(HTTP_FORWARDED)
48 if x_forwarded_for:
48 if x_forwarded_for:
49 ip = x_forwarded_for.split(',')[-1].strip()
49 ip = x_forwarded_for.split(',')[-1].strip()
50 else:
50 else:
51 ip = request.META.get(META_REMOTE_ADDR)
51 ip = request.META.get(META_REMOTE_ADDR)
52 return ip
52 return ip
53
53
54
54
55 # TODO The output format is not epoch because it includes microseconds
55 # TODO The output format is not epoch because it includes microseconds
56 def datetime_to_epoch(datetime):
56 def datetime_to_epoch(datetime):
57 return int(time.mktime(timezone.localtime(
57 return int(time.mktime(timezone.localtime(
58 datetime,timezone.get_current_timezone()).timetuple())
58 datetime,timezone.get_current_timezone()).timetuple())
59 * 1000000 + datetime.microsecond)
59 * 1000000 + datetime.microsecond)
60
60
61
61
62 # TODO Test this carefully
62 # TODO Test this carefully
63 def cached_result(key_method=None):
63 def cached_result(key_method=None):
64 """
64 """
65 Caches method result in the Django's cache system, persisted by object name,
65 Caches method result in the Django's cache system, persisted by object name,
66 object name, model id if object is a Django model, args and kwargs if any.
66 object name, model id if object is a Django model, args and kwargs if any.
67 """
67 """
68 def _cached_result(function):
68 def _cached_result(function):
69 def inner_func(obj, *args, **kwargs):
69 def inner_func(obj, *args, **kwargs):
70 cache_key_params = [obj.__class__.__name__, function.__name__]
70 cache_key_params = [obj.__class__.__name__, function.__name__]
71
71
72 cache_key_params += args
72 cache_key_params += args
73 for key, value in kwargs:
73 for key, value in kwargs:
74 cache_key_params.append(key + CACHE_KEY_DELIMITER + value)
74 cache_key_params.append(key + CACHE_KEY_DELIMITER + value)
75
75
76 if isinstance(obj, Model):
76 if isinstance(obj, Model):
77 cache_key_params.append(str(obj.id))
77 cache_key_params.append(str(obj.id))
78
78
79 if key_method is not None:
79 if key_method is not None:
80 cache_key_params += [str(arg) for arg in key_method(obj)]
80 cache_key_params += [str(arg) for arg in key_method(obj)]
81
81
82 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
82 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
83
83
84 persisted_result = cache.get(cache_key)
84 persisted_result = cache.get(cache_key)
85 if persisted_result is not None:
85 if persisted_result is not None:
86 result = persisted_result
86 result = persisted_result
87 else:
87 else:
88 result = function(obj, *args, **kwargs)
88 result = function(obj, *args, **kwargs)
89 if result is not None:
89 if result is not None:
90 cache.set(cache_key, result)
90 cache.set(cache_key, result)
91
91
92 return result
92 return result
93
93
94 return inner_func
94 return inner_func
95 return _cached_result
95 return _cached_result
96
96
97
97
98 def get_file_hash(file) -> str:
98 def get_file_hash(file) -> str:
99 md5 = hashlib.md5()
99 md5 = hashlib.md5()
100 for chunk in file.chunks():
100 for chunk in file.chunks():
101 md5.update(chunk)
101 md5.update(chunk)
102 return md5.hexdigest()
102 return md5.hexdigest()
103
103
104
104
105 def validate_file_size(size: int):
105 def validate_file_size(size: int):
106 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
106 max_size = boards.settings.get_int(SECTION_FORMS, 'MaxFileSize')
107 if 0 < max_size < size:
107 if 0 < max_size < size:
108 raise forms.ValidationError(
108 raise forms.ValidationError(
109 _('Total file size must be less than %s but is %s.')
109 _('Total file size must be less than %s but is %s.')
110 % (filesizeformat(max_size), filesizeformat(size)))
110 % (filesizeformat(max_size), filesizeformat(size)))
111
111
112
112
113 def get_extension(filename):
113 def get_extension(filename):
114 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
114 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
115
115
116
116
117 def get_upload_filename(model_instance, old_filename):
117 def get_upload_filename(model_instance, old_filename):
118 extension = get_extension(old_filename)
118 extension = get_extension(old_filename)
119 new_name = '{}.{}'.format(uuid.uuid4(), extension)
119 new_name = '{}.{}'.format(uuid.uuid4(), extension)
120
120
121 # Create 2 directories to split the files because holding many files in
121 # Create 2 directories to split the files because holding many files in
122 # one directory may impact performance
122 # one directory may impact performance
123 dir1 = new_name[0]
123 dir1 = new_name[0]
124 dir2 = new_name[1]
124 dir2 = new_name[1]
125
125
126 return os.path.join(FILE_DIRECTORY, dir1, dir2, new_name)
126 return os.path.join(FILE_DIRECTORY, dir1, dir2, new_name)
127
127
128
128
129 def get_file_mimetype(file) -> str:
129 def get_file_mimetype(file) -> str:
130 buf = b''
130 buf = b''
131 for chunk in file.chunks():
131 for chunk in file.chunks():
132 buf += chunk
132 buf += chunk
133
133
134 file_type = magic.from_buffer(buf, mime=True)
134 file_type = magic.from_buffer(buf, mime=True)
135 if file_type is None:
135 if file_type is None:
136 file_type = DEFAULT_MIMETYPE
136 file_type = DEFAULT_MIMETYPE
137 elif type(file_type) == bytes:
137 elif type(file_type) == bytes:
138 file_type = file_type.decode()
138 file_type = file_type.decode()
139 return file_type
139 return file_type
140
140
141
141
142 def get_domain(url: str) -> str:
142 def get_domain(url: str) -> str:
143 """
143 """
144 Gets domain from an URL with random number of domain levels.
144 Gets domain from an URL with random number of domain levels.
145 """
145 """
146 domain_parts = url.split(URL_DELIMITER)
146 domain_parts = url.split(URL_DELIMITER)
147 if len(domain_parts) >= 2:
147 if len(domain_parts) >= 2:
148 full_domain = domain_parts[2]
148 full_domain = domain_parts[2]
149 else:
149 else:
150 full_domain = ''
150 full_domain = ''
151
151
152 return full_domain
152 return full_domain
153
153
154
154
155 def get_tripcode_from_text(text: str) -> str:
155 def get_tripcode_from_text(text: str) -> str:
156 tripcode = ''
156 tripcode = ''
157 if text:
157 if text:
158 code = text + settings.SECRET_KEY
158 code = text + settings.SECRET_KEY
159 tripcode = hashlib.md5(code.encode()).hexdigest()
159 tripcode = hashlib.md5(code.encode()).hexdigest()
160 return tripcode
160 return tripcode
161
161
@@ -1,132 +1,133 b''
1 from django.core.paginator import EmptyPage
1 from django.core.paginator import EmptyPage
2 from django.http import Http404
2 from django.http import Http404
3 from django.shortcuts import render, redirect
3 from django.shortcuts import render, redirect
4 from django.urls import reverse
4 from django.urls import reverse
5 from django.utils.decorators import method_decorator
5 from django.utils.decorators import method_decorator
6 from django.views.decorators.csrf import csrf_protect
6 from django.views.decorators.csrf import csrf_protect
7
7
8 from boards import settings
8 from boards import settings
9 from boards.abstracts.paginator import get_paginator
9 from boards.abstracts.paginator import get_paginator
10 from boards.abstracts.settingsmanager import get_settings_manager, \
10 from boards.abstracts.settingsmanager import get_settings_manager, \
11 SETTING_ONLY_FAVORITES
11 SETTING_ONLY_FAVORITES
12 from boards.forms import ThreadForm, PlainErrorList
12 from boards.forms import ThreadForm, PlainErrorList
13 from boards.models import Post, Thread
13 from boards.models import Post, Thread
14 from boards.views.base import BaseBoardView, CONTEXT_FORM
14 from boards.views.base import BaseBoardView, CONTEXT_FORM
15 from boards.views.mixins import FileUploadMixin, PaginatedMixin, \
15 from boards.views.mixins import FileUploadMixin, PaginatedMixin, \
16 DispatcherMixin, PARAMETER_METHOD
16 DispatcherMixin, PARAMETER_METHOD
17 from boards.settings import SECTION_VIEW, SECTION_FORMS
17
18
18 FORM_TAGS = 'tags'
19 FORM_TAGS = 'tags'
19 FORM_TEXT = 'text'
20 FORM_TEXT = 'text'
20 FORM_TITLE = 'title'
21 FORM_TITLE = 'title'
21 FORM_IMAGE = 'image'
22 FORM_IMAGE = 'image'
22 FORM_THREADS = 'threads'
23 FORM_THREADS = 'threads'
23
24
24 TAG_DELIMITER = ' '
25 TAG_DELIMITER = ' '
25
26
26 PARAMETER_CURRENT_PAGE = 'current_page'
27 PARAMETER_CURRENT_PAGE = 'current_page'
27 PARAMETER_PAGINATOR = 'paginator'
28 PARAMETER_PAGINATOR = 'paginator'
28 PARAMETER_THREADS = 'threads'
29 PARAMETER_THREADS = 'threads'
29 PARAMETER_ADDITIONAL = 'additional_params'
30 PARAMETER_ADDITIONAL = 'additional_params'
30 PARAMETER_MAX_FILE_SIZE = 'max_file_size'
31 PARAMETER_MAX_FILE_SIZE = 'max_file_size'
31 PARAMETER_RSS_URL = 'rss_url'
32 PARAMETER_RSS_URL = 'rss_url'
32 PARAMETER_MAX_FILES = 'max_files'
33 PARAMETER_MAX_FILES = 'max_files'
33
34
34 TEMPLATE = 'boards/all_threads.html'
35 TEMPLATE = 'boards/all_threads.html'
35 DEFAULT_PAGE = 1
36 DEFAULT_PAGE = 1
36
37
37
38
38 class AllThreadsView(FileUploadMixin, BaseBoardView, PaginatedMixin,
39 class AllThreadsView(FileUploadMixin, BaseBoardView, PaginatedMixin,
39 DispatcherMixin):
40 DispatcherMixin):
40
41
41 tag_name = ''
42 tag_name = ''
42
43
43 def __init__(self):
44 def __init__(self):
44 self.settings_manager = None
45 self.settings_manager = None
45 super(AllThreadsView, self).__init__()
46 super(AllThreadsView, self).__init__()
46
47
47 @method_decorator(csrf_protect)
48 @method_decorator(csrf_protect)
48 def get(self, request, form: ThreadForm=None):
49 def get(self, request, form: ThreadForm=None):
49 page = request.GET.get('page', DEFAULT_PAGE)
50 page = request.GET.get('page', DEFAULT_PAGE)
50
51
51 params = self.get_context_data(request=request)
52 params = self.get_context_data(request=request)
52
53
53 if not form:
54 if not form:
54 form = ThreadForm(error_class=PlainErrorList,
55 form = ThreadForm(error_class=PlainErrorList,
55 initial={FORM_TAGS: self.tag_name})
56 initial={FORM_TAGS: self.tag_name})
56
57
57 self.settings_manager = get_settings_manager(request)
58 self.settings_manager = get_settings_manager(request)
58
59
59 threads = self.get_threads()
60 threads = self.get_threads()
60
61
61 order = request.GET.get('order', 'bump')
62 order = request.GET.get('order', 'bump')
62 if order == 'bump':
63 if order == 'bump':
63 threads = threads.order_by('-bump_time')
64 threads = threads.order_by('-bump_time')
64 else:
65 else:
65 threads = threads.filter(replies__opening=True)\
66 threads = threads.filter(replies__opening=True)\
66 .order_by('-replies__pub_time')
67 .order_by('-replies__pub_time')
67 filter = request.GET.get('filter')
68 filter = request.GET.get('filter')
68 threads = threads.distinct()
69 threads = threads.distinct()
69
70
70 paginator = get_paginator(threads,
71 paginator = get_paginator(threads,
71 settings.get_int('View', 'ThreadsPerPage'))
72 settings.get_int(SECTION_VIEW, 'ThreadsPerPage'))
72 paginator.current_page = int(page)
73 paginator.current_page = int(page)
73
74
74 try:
75 try:
75 threads = paginator.page(page).object_list
76 threads = paginator.page(page).object_list
76 except EmptyPage:
77 except EmptyPage:
77 raise Http404()
78 raise Http404()
78
79
79 params[PARAMETER_THREADS] = threads
80 params[PARAMETER_THREADS] = threads
80 params[CONTEXT_FORM] = form
81 params[CONTEXT_FORM] = form
81 params[PARAMETER_MAX_FILE_SIZE] = self.get_max_upload_size()
82 params[PARAMETER_MAX_FILE_SIZE] = self.get_max_upload_size()
82 params[PARAMETER_RSS_URL] = self.get_rss_url()
83 params[PARAMETER_RSS_URL] = self.get_rss_url()
83 params[PARAMETER_MAX_FILES] = settings.get_int('Forms', 'MaxFileCount')
84 params[PARAMETER_MAX_FILES] = settings.get_int(SECTION_FORMS, 'MaxFileCount')
84
85
85 paginator.set_url(self.get_reverse_url(), request.GET.dict())
86 paginator.set_url(self.get_reverse_url(), request.GET.dict())
86 params.update(self.get_page_context(paginator, page))
87 params.update(self.get_page_context(paginator, page))
87
88
88 return render(request, TEMPLATE, params)
89 return render(request, TEMPLATE, params)
89
90
90 @method_decorator(csrf_protect)
91 @method_decorator(csrf_protect)
91 def post(self, request):
92 def post(self, request):
92 if PARAMETER_METHOD in request.POST:
93 if PARAMETER_METHOD in request.POST:
93 self.dispatch_method(request)
94 self.dispatch_method(request)
94
95
95 return redirect('index') # FIXME Different for different modes
96 return redirect('index') # FIXME Different for different modes
96
97
97 form = ThreadForm(request.POST, request.FILES,
98 form = ThreadForm(request.POST, request.FILES,
98 error_class=PlainErrorList)
99 error_class=PlainErrorList)
99 form.session = request.session
100 form.session = request.session
100
101
101 if form.is_valid():
102 if form.is_valid():
102 return Post.objects.create_from_form(request, form, None)
103 return Post.objects.create_from_form(request, form, None)
103 if form.need_to_ban:
104 if form.need_to_ban:
104 # Ban user because he is suspected to be a bot
105 # Ban user because he is suspected to be a bot
105 self._ban_current_user(request)
106 self._ban_current_user(request)
106
107
107 return self.get(request, form)
108 return self.get(request, form)
108
109
109 def get_reverse_url(self):
110 def get_reverse_url(self):
110 return reverse('index')
111 return reverse('index')
111
112
112 def get_threads(self):
113 def get_threads(self):
113 """
114 """
114 Gets list of threads that will be shown on a page.
115 Gets list of threads that will be shown on a page.
115 """
116 """
116
117
117 threads = Thread.objects\
118 threads = Thread.objects\
118 .exclude(tags__in=self.settings_manager.get_hidden_tags())
119 .exclude(tags__in=self.settings_manager.get_hidden_tags())
119 if self.settings_manager.get_setting(SETTING_ONLY_FAVORITES):
120 if self.settings_manager.get_setting(SETTING_ONLY_FAVORITES):
120 fav_tags = self.settings_manager.get_fav_tags()
121 fav_tags = self.settings_manager.get_fav_tags()
121 if len(fav_tags) > 0:
122 if len(fav_tags) > 0:
122 threads = threads.filter(tags__in=fav_tags)
123 threads = threads.filter(tags__in=fav_tags)
123
124
124 return threads
125 return threads
125
126
126 def get_rss_url(self):
127 def get_rss_url(self):
127 return self.get_reverse_url() + 'rss/'
128 return self.get_reverse_url() + 'rss/'
128
129
129 def toggle_fav(self, request):
130 def toggle_fav(self, request):
130 settings_manager = get_settings_manager(request)
131 settings_manager = get_settings_manager(request)
131 settings_manager.set_setting(SETTING_ONLY_FAVORITES,
132 settings_manager.set_setting(SETTING_ONLY_FAVORITES,
132 not settings_manager.get_setting(SETTING_ONLY_FAVORITES, False))
133 not settings_manager.get_setting(SETTING_ONLY_FAVORITES, False))
General Comments 0
You need to be logged in to leave comments. Login now