##// END OF EJS Templates
Merged with default
neko259 -
r1384:ecedb03a merge decentral
parent child Browse files
Show More
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5
6
7 class Migration(migrations.Migration):
8
9 dependencies = [
10 ('boards', '0030_auto_20150929_1816'),
11 ]
12
13 operations = [
14 migrations.AddField(
15 model_name='post',
16 name='hidden',
17 field=models.BooleanField(default=False),
18 ),
19 ]
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5
6
7 class Migration(migrations.Migration):
8
9 dependencies = [
10 ('boards', '0031_post_hidden'),
11 ]
12
13 operations = [
14 migrations.AlterField(
15 model_name='post',
16 name='tripcode',
17 field=models.CharField(default='', blank=True, max_length=50),
18 ),
19 ]
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5
6
7 class Migration(migrations.Migration):
8
9 dependencies = [
10 ('boards', '0032_auto_20151014_2222'),
11 ]
12
13 operations = [
14 migrations.AlterField(
15 model_name='tag',
16 name='parent',
17 field=models.ForeignKey(blank=True, related_name='children', to='boards.Tag', null=True),
18 ),
19 ]
@@ -0,0 +1,26 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5 import boards.thumbs
6 import boards.utils
7
8
9 class Migration(migrations.Migration):
10
11 dependencies = [
12 ('boards', '0033_auto_20151014_2224'),
13 ]
14
15 operations = [
16 migrations.AlterField(
17 model_name='attachment',
18 name='file',
19 field=models.FileField(upload_to=boards.utils.get_upload_filename),
20 ),
21 migrations.AlterField(
22 model_name='postimage',
23 name='image',
24 field=boards.thumbs.ImageWithThumbsField(blank=True, width_field='width', height_field='height', upload_to=boards.utils.get_upload_filename),
25 ),
26 ]
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5
6
7 class Migration(migrations.Migration):
8
9 dependencies = [
10 ('boards', '0034_auto_20151014_2253'),
11 ]
12
13 operations = [
14 migrations.AlterField(
15 model_name='post',
16 name='opening',
17 field=models.BooleanField(db_index=True),
18 ),
19 ]
@@ -0,0 +1,15 b''
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals
3
4 from django.db import migrations, models
5
6
7 class Migration(migrations.Migration):
8
9 dependencies = [
10 ('boards', '0031_merge'),
11 ('boards', '0035_auto_20151021_1346'),
12 ]
13
14 operations = [
15 ]
@@ -59,3 +59,16 b' class DividedPaginator(Paginator):'
59 prev_page = page
59 prev_page = page
60
60
61 return dividers
61 return dividers
62
63 def set_url(self, link, params):
64 self.link = link
65 self.params = params
66
67 def get_page_url(self, page):
68 self.params['page'] = page
69 url_params = '?' + '&'.join(['{}={}'.format(key, self.params[key])
70 for key in self.params.keys()])
71 return self.link + url_params
72
73 def supports_urls(self):
74 return self.link is not None and self.params is not None
@@ -17,6 +17,7 b' LimitPostingSpeed = false'
17 MaxPostsPerThread = 10
17 MaxPostsPerThread = 10
18 # Old posts will be archived or deleted if this value is reached
18 # Old posts will be archived or deleted if this value is reached
19 MaxThreadCount = 5
19 MaxThreadCount = 5
20 AnonymousMode = false
20
21
21 [View]
22 [View]
22 DefaultTheme = md
23 DefaultTheme = md
@@ -1,8 +1,9 b''
1 import hashlib
1 import hashlib
2 import re
2 import re
3 import time
3 import time
4 import logging
5 import pytz
4
6
5 import pytz
6 from django import forms
7 from django import forms
7 from django.core.files.uploadedfile import SimpleUploadedFile
8 from django.core.files.uploadedfile import SimpleUploadedFile
8 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.exceptions import ObjectDoesNotExist
@@ -13,7 +14,8 b' from boards.mdx_neboard import formatter'
13 from boards.models.attachment.downloaders import Downloader
14 from boards.models.attachment.downloaders import Downloader
14 from boards.models.post import TITLE_MAX_LENGTH
15 from boards.models.post import TITLE_MAX_LENGTH
15 from boards.models import Tag, Post
16 from boards.models import Tag, Post
16 from boards.utils import validate_file_size
17 from boards.utils import validate_file_size, get_file_mimetype, \
18 FILE_EXTENSION_DELIMITER
17 from neboard import settings
19 from neboard import settings
18 import boards.settings as board_settings
20 import boards.settings as board_settings
19 import neboard
21 import neboard
@@ -42,6 +44,20 b' TAG_MAX_LENGTH = 20'
42
44
43 TEXTAREA_ROWS = 4
45 TEXTAREA_ROWS = 4
44
46
47 TRIPCODE_DELIM = '#'
48
49 # TODO Maybe this may be converted into the database table?
50 MIMETYPE_EXTENSIONS = {
51 'image/jpeg': 'jpeg',
52 'image/png': 'png',
53 'image/gif': 'gif',
54 'video/webm': 'webm',
55 'application/pdf': 'pdf',
56 'x-diff': 'diff',
57 'image/svg+xml': 'svg',
58 'application/x-shockwave-flash': 'swf',
59 }
60
45
61
46 def get_timezones():
62 def get_timezones():
47 timezones = []
63 timezones = []
@@ -155,6 +171,20 b' class PostForm(NeboardForm):'
155 session = None
171 session = None
156 need_to_ban = False
172 need_to_ban = False
157
173
174 def _update_file_extension(self, file):
175 if file:
176 mimetype = get_file_mimetype(file)
177 extension = MIMETYPE_EXTENSIONS.get(mimetype)
178 if extension:
179 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
180 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
181
182 file.name = new_filename
183 else:
184 logger = logging.getLogger('boards.forms.extension')
185
186 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
187
158 def clean_title(self):
188 def clean_title(self):
159 title = self.cleaned_data['title']
189 title = self.cleaned_data['title']
160 if title:
190 if title:
@@ -178,6 +208,7 b' class PostForm(NeboardForm):'
178
208
179 if file:
209 if file:
180 validate_file_size(file.size)
210 validate_file_size(file.size)
211 self._update_file_extension(file)
181
212
182 return file
213 return file
183
214
@@ -192,6 +223,7 b' class PostForm(NeboardForm):'
192 raise forms.ValidationError(_('Invalid URL'))
223 raise forms.ValidationError(_('Invalid URL'))
193 else:
224 else:
194 validate_file_size(file.size)
225 validate_file_size(file.size)
226 self._update_file_extension(file)
195
227
196 return file
228 return file
197
229
@@ -239,14 +271,17 b' class PostForm(NeboardForm):'
239
271
240 def get_tripcode(self):
272 def get_tripcode(self):
241 title = self.cleaned_data['title']
273 title = self.cleaned_data['title']
242 if title is not None and '#' in title:
274 if title is not None and TRIPCODE_DELIM in title:
243 code = title.split('#', maxsplit=1)[1] + neboard.settings.SECRET_KEY
275 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
244 return hashlib.md5(code.encode()).hexdigest()
276 tripcode = hashlib.md5(code.encode()).hexdigest()
277 else:
278 tripcode = ''
279 return tripcode
245
280
246 def get_title(self):
281 def get_title(self):
247 title = self.cleaned_data['title']
282 title = self.cleaned_data['title']
248 if title is not None and '#' in title:
283 if title is not None and TRIPCODE_DELIM in title:
249 return title.split('#', maxsplit=1)[0]
284 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
250 else:
285 else:
251 return title
286 return title
252
287
@@ -332,7 +367,7 b' class ThreadForm(PostForm):'
332 # If this is a new tag, don't check for its parents because nobody
367 # If this is a new tag, don't check for its parents because nobody
333 # added them yet
368 # added them yet
334 if not created:
369 if not created:
335 tag_set |= tag.get_all_parents()
370 tag_set |= set(tag.get_all_parents())
336
371
337 for tag in tag_set:
372 for tag in tag_set:
338 if tag.required:
373 if tag.required:
@@ -340,7 +375,6 b' class ThreadForm(PostForm):'
340 break
375 break
341
376
342 if not required_tag_exists:
377 if not required_tag_exists:
343 all_tags = Tag.objects.filter(required=True)
344 raise forms.ValidationError(
378 raise forms.ValidationError(
345 _('Need at least one section.'))
379 _('Need at least one section.'))
346
380
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -7,7 +7,7 b' msgid ""'
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2015-09-12 12:48+0300\n"
10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -101,7 +101,7 b' msgstr "\xd0\x9d\xd0\xb5\xd0\xb2\xd0\xb5\xd1\x80\xd0\xbd\xd1\x8b\xd0\xb9 \xd1\x81\xd0\xbf\xd0\xb8\xd1\x81\xd0\xbe\xd0\xba \xd0\xb4\xd0\xbe\xd0\xbf\xd0\xbe\xd0\xbb\xd0\xbd\xd0\xb8\xd1\x82\xd0\xb5\xd0\xbb\xd1\x8c\xd0\xbd\xd1\x8b\xd1\x85 \xd1\x82\xd0\xb5\xd0\xbc"'
101 msgid "Either text or file must be entered."
101 msgid "Either text or file must be entered."
102 msgstr "ВСкст ΠΈΠ»ΠΈ Ρ„Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Ρ‹."
102 msgstr "ВСкст ΠΈΠ»ΠΈ Ρ„Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Ρ‹."
103
103
104 #: forms.py:317 templates/boards/all_threads.html:148
104 #: forms.py:317 templates/boards/all_threads.html:153
105 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
105 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
106 msgid "Tags"
106 msgid "Tags"
107 msgstr "ΠœΠ΅Ρ‚ΠΊΠΈ"
107 msgstr "ΠœΠ΅Ρ‚ΠΊΠΈ"
@@ -110,27 +110,27 b' msgstr "\xd0\x9c\xd0\xb5\xd1\x82\xd0\xba\xd0\xb8"'
110 msgid "Inappropriate characters in tags."
110 msgid "Inappropriate characters in tags."
111 msgstr "НСдопустимыС символы Π² ΠΌΠ΅Ρ‚ΠΊΠ°Ρ…."
111 msgstr "НСдопустимыС символы Π² ΠΌΠ΅Ρ‚ΠΊΠ°Ρ…."
112
112
113 #: forms.py:338
113 #: forms.py:344
114 msgid "Need at least one section."
114 msgid "Need at least one section."
115 msgstr "НуТСн хотя Π±Ρ‹ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·Π΄Π΅Π»."
115 msgstr "НуТСн хотя Π±Ρ‹ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·Π΄Π΅Π»."
116
116
117 #: forms.py:350
117 #: forms.py:356
118 msgid "Theme"
118 msgid "Theme"
119 msgstr "Π’Π΅ΠΌΠ°"
119 msgstr "Π’Π΅ΠΌΠ°"
120
120
121 #: forms.py:351
121 #: forms.py:357
122 msgid "Image view mode"
122 msgid "Image view mode"
123 msgstr "Π Π΅ΠΆΠΈΠΌ просмотра ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
123 msgstr "Π Π΅ΠΆΠΈΠΌ просмотра ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
124
124
125 #: forms.py:352
125 #: forms.py:358
126 msgid "User name"
126 msgid "User name"
127 msgstr "Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
127 msgstr "Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
128
128
129 #: forms.py:353
129 #: forms.py:359
130 msgid "Time zone"
130 msgid "Time zone"
131 msgstr "Часовой пояс"
131 msgstr "Часовой пояс"
132
132
133 #: forms.py:359
133 #: forms.py:365
134 msgid "Inappropriate characters."
134 msgid "Inappropriate characters."
135 msgstr "НСдопустимыС символы."
135 msgstr "НСдопустимыС символы."
136
136
@@ -150,70 +150,89 b' msgstr "\xd0\xa1\xd0\xb2\xd1\x8f\xd0\xb7\xd0\xb0\xd0\xbd\xd0\xbd\xd0\xbe\xd0\xb5 \xd1\x81\xd0\xbe\xd0\xbe\xd0\xb1\xd1\x89\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5"'
150 msgid "Edit tag"
150 msgid "Edit tag"
151 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΡƒ"
151 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΡƒ"
152
152
153 #: templates/boards/all_threads.html:75
153 #: templates/boards/all_threads.html:76
154 #, python-format
154 #, python-format
155 msgid ""
155 msgid "%(count)s active thread"
156 "This tag has %(thread_count)s threads (%(active_thread_count)s active) and "
156 msgid_plural "%(count)s active threads"
157 "%(post_count)s posts."
157 msgstr[0] "%(count)s активная Ρ‚Π΅ΠΌΠ°"
158 msgstr ""
158 msgstr[1] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
159 "Π‘ этой ΠΌΠ΅Ρ‚ΠΊΠΎΠΉ Π΅ΡΡ‚ΡŒ %(thread_count)s Ρ‚Π΅ΠΌ (%(active_thread_count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ…) ΠΈ "
159 msgstr[2] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
160 "%(post_count)s сообщСний."
160
161 #: templates/boards/all_threads.html:76
162 #, python-format
163 msgid "%(count)s thread in bumplimit"
164 msgid_plural "%(count)s threads in bumplimit"
165 msgstr[0] "%(count)s Ρ‚Π΅ΠΌΠ° Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
166 msgstr[1] "%(count)s Ρ‚Π΅ΠΌΡ‹ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
167 msgstr[2] "%(count)s Ρ‚Π΅ΠΌ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
161
168
162 #: templates/boards/all_threads.html:77
169 #: templates/boards/all_threads.html:77
163 msgid "Related tags:"
170 #, python-format
164 msgstr "ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠΈ:"
171 msgid "%(count)s archived thread"
172 msgid_plural "%(count)s archived thread"
173 msgstr[0] "%(count)s архивная Ρ‚Π΅ΠΌΠ°"
174 msgstr[1] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
175 msgstr[2] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
165
176
166 #: templates/boards/all_threads.html:90 templates/boards/feed.html:30
177 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
178 #, python-format
179 #| msgid "%(count)s message"
180 #| msgid_plural "%(count)s messages"
181 msgid "%(count)s message"
182 msgid_plural "%(count)s messages"
183 msgstr[0] "%(count)s сообщСниС"
184 msgstr[1] "%(count)s сообщСния"
185 msgstr[2] "%(count)s сообщСний"
186
187 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
167 #: templates/boards/notifications.html:17 templates/search/search.html:26
188 #: templates/boards/notifications.html:17 templates/search/search.html:26
168 msgid "Previous page"
189 msgid "Previous page"
169 msgstr "ΠŸΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π°Ρ страница"
190 msgstr "ΠŸΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π°Ρ страница"
170
191
171 #: templates/boards/all_threads.html:104
192 #: templates/boards/all_threads.html:109
172 #, python-format
193 #, python-format
173 #| msgid "Skipped %(count)s replies. Open thread to see all replies."
174 msgid "Skipped %(count)s reply. Open thread to see all replies."
194 msgid "Skipped %(count)s reply. Open thread to see all replies."
175 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
195 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
176 msgstr[0] ""
196 msgstr[0] "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ %(count)s ΠΎΡ‚Π²Π΅Ρ‚. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
177 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ %(count)s ΠΎΡ‚Π²Π΅Ρ‚. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
178 msgstr[1] ""
197 msgstr[1] ""
179 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚Π°. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
198 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚Π°. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
180 msgstr[2] ""
199 msgstr[2] ""
181 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
200 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
182
201
183 #: templates/boards/all_threads.html:122 templates/boards/feed.html:40
202 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
184 #: templates/boards/notifications.html:27 templates/search/search.html:37
203 #: templates/boards/notifications.html:27 templates/search/search.html:37
185 msgid "Next page"
204 msgid "Next page"
186 msgstr "Π‘Π»Π΅Π΄ΡƒΡŽΡ‰Π°Ρ страница"
205 msgstr "Π‘Π»Π΅Π΄ΡƒΡŽΡ‰Π°Ρ страница"
187
206
188 #: templates/boards/all_threads.html:127
207 #: templates/boards/all_threads.html:132
189 msgid "No threads exist. Create the first one!"
208 msgid "No threads exist. Create the first one!"
190 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
209 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
191
210
192 #: templates/boards/all_threads.html:133
211 #: templates/boards/all_threads.html:138
193 msgid "Create new thread"
212 msgid "Create new thread"
194 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
213 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
195
214
196 #: templates/boards/all_threads.html:138 templates/boards/preview.html:16
215 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
197 #: templates/boards/thread_normal.html:51
216 #: templates/boards/thread_normal.html:51
198 msgid "Post"
217 msgid "Post"
199 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
218 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
200
219
201 #: templates/boards/all_threads.html:139 templates/boards/preview.html:6
220 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
202 #: templates/boards/staticpages/help.html:21
221 #: templates/boards/staticpages/help.html:21
203 #: templates/boards/thread_normal.html:52
222 #: templates/boards/thread_normal.html:52
204 msgid "Preview"
223 msgid "Preview"
205 msgstr "ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
224 msgstr "ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
206
225
207 #: templates/boards/all_threads.html:144
226 #: templates/boards/all_threads.html:149
208 msgid "Tags must be delimited by spaces. Text or image is required."
227 msgid "Tags must be delimited by spaces. Text or image is required."
209 msgstr ""
228 msgstr ""
210 "ΠœΠ΅Ρ‚ΠΊΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
229 "ΠœΠ΅Ρ‚ΠΊΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
211
230
212 #: templates/boards/all_threads.html:147 templates/boards/thread_normal.html:58
231 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
213 msgid "Text syntax"
232 msgid "Text syntax"
214 msgstr "Бинтаксис тСкста"
233 msgstr "Бинтаксис тСкста"
215
234
216 #: templates/boards/all_threads.html:161 templates/boards/feed.html:53
235 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
217 msgid "Pages:"
236 msgid "Pages:"
218 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
237 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
219
238
@@ -269,25 +288,33 b' msgstr "\xd0\xa1\xd0\xbb\xd1\x83\xd1\x87\xd0\xb0\xd0\xb9\xd0\xbd\xd1\x8b\xd0\xb5 \xd0\xb8\xd0\xb7\xd0\xbe\xd0\xb1\xd1\x80\xd0\xb0\xd0\xb6\xd0\xb5\xd0\xbd\xd0\xb8\xd1\x8f"'
269 msgid "random"
288 msgid "random"
270 msgstr "случайныС"
289 msgstr "случайныС"
271
290
272 #: templates/boards/base.html:45 templates/boards/base.html.py:46
291 #: templates/boards/base.html:44
292 msgid "favorites"
293 msgstr "ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
294
295 #: templates/boards/base.html:48 templates/boards/base.html.py:49
273 #: templates/boards/notifications.html:8
296 #: templates/boards/notifications.html:8
274 msgid "Notifications"
297 msgid "Notifications"
275 msgstr "УвСдомлСния"
298 msgstr "УвСдомлСния"
276
299
277 #: templates/boards/base.html:53 templates/boards/settings.html:8
300 #: templates/boards/base.html:56 templates/boards/settings.html:8
278 msgid "Settings"
301 msgid "Settings"
279 msgstr "Настройки"
302 msgstr "Настройки"
280
303
281 #: templates/boards/base.html:79
304 #: templates/boards/base.html:59
305 msgid "Loading..."
306 msgstr "Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°..."
307
308 #: templates/boards/base.html:71
282 msgid "Admin"
309 msgid "Admin"
283 msgstr "АдминистрированиС"
310 msgstr "АдминистрированиС"
284
311
285 #: templates/boards/base.html:81
312 #: templates/boards/base.html:73
286 #, python-format
313 #, python-format
287 msgid "Speed: %(ppd)s posts per day"
314 msgid "Speed: %(ppd)s posts per day"
288 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
315 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
289
316
290 #: templates/boards/base.html:83
317 #: templates/boards/base.html:75
291 msgid "Up"
318 msgid "Up"
292 msgstr "Π’Π²Π΅Ρ€Ρ…"
319 msgstr "Π’Π²Π΅Ρ€Ρ…"
293
320
@@ -295,46 +322,31 b' msgstr "\xd0\x92\xd0\xb2\xd0\xb5\xd1\x80\xd1\x85"'
295 msgid "No posts exist. Create the first one!"
322 msgid "No posts exist. Create the first one!"
296 msgstr "НСт сообщСний. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΠΎΠ΅!"
323 msgstr "НСт сообщСний. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΠΎΠ΅!"
297
324
298 #: templates/boards/post.html:32
325 #: templates/boards/post.html:33
299 msgid "Open"
326 msgid "Open"
300 msgstr "ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ"
327 msgstr "ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ"
301
328
302 #: templates/boards/post.html:34 templates/boards/post.html.py:45
329 #: templates/boards/post.html:35 templates/boards/post.html.py:46
303 msgid "Reply"
330 msgid "Reply"
304 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ"
331 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ"
305
332
306 #: templates/boards/post.html:40
333 #: templates/boards/post.html:41
307 msgid " in "
334 msgid " in "
308 msgstr " Π² "
335 msgstr " Π² "
309
336
310 #: templates/boards/post.html:50
337 #: templates/boards/post.html:51
311 msgid "Edit"
338 msgid "Edit"
312 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ"
339 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ"
313
340
314 #: templates/boards/post.html:52
341 #: templates/boards/post.html:53
315 msgid "Edit thread"
342 msgid "Edit thread"
316 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
343 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
317
344
318 #: templates/boards/post.html:94
345 #: templates/boards/post.html:91
319 msgid "Replies"
346 msgid "Replies"
320 msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
347 msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
321
348
322 #: templates/boards/post.html:105
349 #: templates/boards/post.html:103
323 #, python-format
324 msgid "%(count)s message"
325 msgid_plural "%(count)s messages"
326 msgstr[0] "%(count)s сообщСниС"
327 msgstr[1] "%(count)s сообщСния"
328 msgstr[2] "%(count)s сообщСний"
329
330 #, python-format
331 msgid "Please wait %(delay)d second before sending message"
332 msgid_plural "Please wait %(delay)d seconds before sending message"
333 msgstr[0] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунду ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
334 msgstr[1] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунды ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
335 msgstr[2] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунд ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
336
337 #: templates/boards/post.html:106
338 #, python-format
350 #, python-format
339 msgid "%(count)s image"
351 msgid "%(count)s image"
340 msgid_plural "%(count)s images"
352 msgid_plural "%(count)s images"
@@ -420,33 +432,33 b' msgstr "\xd0\x94\xd1\x80\xd1\x83\xd0\xb3\xd0\xb8\xd0\xb5 \xd0\xbc\xd0\xb5\xd1\x82\xd0\xba\xd0\xb8:"'
420 msgid "All tags..."
432 msgid "All tags..."
421 msgstr "ВсС ΠΌΠ΅Ρ‚ΠΊΠΈ..."
433 msgstr "ВсС ΠΌΠ΅Ρ‚ΠΊΠΈ..."
422
434
423 #: templates/boards/thread.html:15
435 #: templates/boards/thread.html:14
424 msgid "Normal"
436 msgid "Normal"
425 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ"
437 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ"
426
438
427 #: templates/boards/thread.html:16
439 #: templates/boards/thread.html:15
428 msgid "Gallery"
440 msgid "Gallery"
429 msgstr "ГалСрСя"
441 msgstr "ГалСрСя"
430
442
431 #: templates/boards/thread.html:17
443 #: templates/boards/thread.html:16
432 msgid "Tree"
444 msgid "Tree"
433 msgstr "Π”Π΅Ρ€Π΅Π²ΠΎ"
445 msgstr "Π”Π΅Ρ€Π΅Π²ΠΎ"
434
446
435 #: templates/boards/thread.html:36
447 #: templates/boards/thread.html:35
436 msgid "message"
448 msgid "message"
437 msgid_plural "messages"
449 msgid_plural "messages"
438 msgstr[0] "сообщСниС"
450 msgstr[0] "сообщСниС"
439 msgstr[1] "сообщСния"
451 msgstr[1] "сообщСния"
440 msgstr[2] "сообщСний"
452 msgstr[2] "сообщСний"
441
453
442 #: templates/boards/thread.html:39
454 #: templates/boards/thread.html:38
443 msgid "image"
455 msgid "image"
444 msgid_plural "images"
456 msgid_plural "images"
445 msgstr[0] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
457 msgstr[0] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
446 msgstr[1] "изобраТСния"
458 msgstr[1] "изобраТСния"
447 msgstr[2] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
459 msgstr[2] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
448
460
449 #: templates/boards/thread.html:41
461 #: templates/boards/thread.html:40
450 msgid "Last update: "
462 msgid "Last update: "
451 msgstr "ПослСднСС обновлСниС: "
463 msgstr "ПослСднСС обновлСниС: "
452
464
@@ -474,19 +486,16 b' msgstr "\xd0\x97\xd0\xb0\xd0\xba\xd1\x80\xd1\x8b\xd1\x82\xd1\x8c \xd1\x84\xd0\xbe\xd1\x80\xd0\xbc\xd1\x83"'
474 msgid "Ok"
486 msgid "Ok"
475 msgstr "Ок"
487 msgstr "Ок"
476
488
477 #: utils.py:102
489 #: utils.py:120
478 #, python-format
490 #, python-format
479 msgid "File must be less than %s bytes"
491 msgid "File must be less than %s bytes"
480 msgstr "Π€Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΌΠ΅Π½Π΅Π΅ %s Π±Π°ΠΉΡ‚"
492 msgstr "Π€Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΌΠ΅Π½Π΅Π΅ %s Π±Π°ΠΉΡ‚"
481
493
482 msgid "favorites"
494 msgid "Please wait %(delay)d second before sending message"
483 msgstr "ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
495 msgid_plural "Please wait %(delay)d seconds before sending message"
484
496 msgstr[0] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунду ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
485 msgid "Loading..."
497 msgstr[1] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунды ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
486 msgstr "Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°..."
498 msgstr[2] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунд ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
487
499
488 msgid "Category:"
500 msgid "New threads"
489 msgstr "ΠšΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΡ:"
501 msgstr "НовыС Ρ‚Π΅ΠΌΡ‹"
490
491 msgid "Subcategories:"
492 msgstr "ΠŸΠΎΠ΄ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΠΈ:"
@@ -52,7 +52,7 b' class Migration(migrations.Migration):'
52 ('height', models.IntegerField(default=0)),
52 ('height', models.IntegerField(default=0)),
53 ('pre_width', models.IntegerField(default=0)),
53 ('pre_width', models.IntegerField(default=0)),
54 ('pre_height', models.IntegerField(default=0)),
54 ('pre_height', models.IntegerField(default=0)),
55 ('image', boards.thumbs.ImageWithThumbsField(height_field='height', width_field='width', upload_to=boards.models.image.PostImage._update_image_filename, blank=True)),
55 ('image', boards.thumbs.ImageWithThumbsField(height_field='height', width_field='width', blank=True)),
56 ('hash', models.CharField(max_length=36)),
56 ('hash', models.CharField(max_length=36)),
57 ],
57 ],
58 options={
58 options={
@@ -16,7 +16,7 b' class Migration(migrations.Migration):'
16 name='Attachment',
16 name='Attachment',
17 fields=[
17 fields=[
18 ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
18 ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
19 ('file', models.FileField(upload_to=boards.models.attachment.Attachment._update_filename)),
19 ('file', models.FileField()),
20 ('mimetype', models.CharField(max_length=50)),
20 ('mimetype', models.CharField(max_length=50)),
21 ('hash', models.CharField(max_length=36)),
21 ('hash', models.CharField(max_length=36)),
22 ],
22 ],
@@ -1,14 +1,8 b''
1 import os
2 import time
3 from random import random
4
5 from django.db import models
1 from django.db import models
6
2
7 from boards import utils
3 from boards import utils
8 from boards.models.attachment.viewers import get_viewers, AbstractViewer
4 from boards.models.attachment.viewers import get_viewers, AbstractViewer
9
5 from boards.utils import get_upload_filename, get_file_mimetype, get_extension
10 FILES_DIRECTORY = 'files/'
11 FILE_EXTENSION_DELIMITER = '.'
12
6
13
7
14 class AttachmentManager(models.Manager):
8 class AttachmentManager(models.Manager):
@@ -18,9 +12,10 b' class AttachmentManager(models.Manager):'
18 if len(existing) > 0:
12 if len(existing) > 0:
19 attachment = existing[0]
13 attachment = existing[0]
20 else:
14 else:
21 file_type = file.name.split(FILE_EXTENSION_DELIMITER)[-1].lower()
15 # FIXME Use full mimetype here, need to modify viewers too
22 attachment = Attachment.objects.create(
16 file_type = get_extension(file.name)
23 file=file, mimetype=file_type, hash=file_hash)
17 attachment = self.create(file=file, mimetype=file_type,
18 hash=file_hash)
24
19
25 return attachment
20 return attachment
26
21
@@ -28,21 +23,7 b' class AttachmentManager(models.Manager):'
28 class Attachment(models.Model):
23 class Attachment(models.Model):
29 objects = AttachmentManager()
24 objects = AttachmentManager()
30
25
31 # TODO Dedup the method
26 file = models.FileField(upload_to=get_upload_filename)
32 def _update_filename(self, filename):
33 """
34 Gets unique filename
35 """
36
37 # TODO Use something other than random number in file name
38 new_name = '{}{}.{}'.format(
39 str(int(time.mktime(time.gmtime()))),
40 str(int(random() * 1000)),
41 filename.split(FILE_EXTENSION_DELIMITER)[-1:][0])
42
43 return os.path.join(FILES_DIRECTORY, new_name)
44
45 file = models.FileField(upload_to=_update_filename)
46 mimetype = models.CharField(max_length=50)
27 mimetype = models.CharField(max_length=50)
47 hash = models.CharField(max_length=36)
28 hash = models.CharField(max_length=36)
48
29
@@ -1,21 +1,15 b''
1 import hashlib
2 import os
3 from random import random
4 import time
5
6 from django.db import models
1 from django.db import models
7 from django.template.defaultfilters import filesizeformat
2 from django.template.defaultfilters import filesizeformat
8
3
9 from boards import thumbs, utils
4 from boards import thumbs, utils
10 import boards
5 import boards
11 from boards.models.base import Viewable
6 from boards.models.base import Viewable
7 from boards.utils import get_upload_filename
12
8
13 __author__ = 'neko259'
9 __author__ = 'neko259'
14
10
15
11
16 IMAGE_THUMB_SIZE = (200, 150)
12 IMAGE_THUMB_SIZE = (200, 150)
17 IMAGES_DIRECTORY = 'images/'
18 FILE_EXTENSION_DELIMITER = '.'
19 HASH_LENGTH = 36
13 HASH_LENGTH = 36
20
14
21 CSS_CLASS_IMAGE = 'image'
15 CSS_CLASS_IMAGE = 'image'
@@ -47,26 +41,13 b' class PostImage(models.Model, Viewable):'
47 app_label = 'boards'
41 app_label = 'boards'
48 ordering = ('id',)
42 ordering = ('id',)
49
43
50 def _update_image_filename(self, filename):
51 """
52 Gets unique image filename
53 """
54
55 # TODO Use something other than random number in file name
56 new_name = '{}{}.{}'.format(
57 str(int(time.mktime(time.gmtime()))),
58 str(int(random() * 1000)),
59 filename.split(FILE_EXTENSION_DELIMITER)[-1:][0])
60
61 return os.path.join(IMAGES_DIRECTORY, new_name)
62
63 width = models.IntegerField(default=0)
44 width = models.IntegerField(default=0)
64 height = models.IntegerField(default=0)
45 height = models.IntegerField(default=0)
65
46
66 pre_width = models.IntegerField(default=0)
47 pre_width = models.IntegerField(default=0)
67 pre_height = models.IntegerField(default=0)
48 pre_height = models.IntegerField(default=0)
68
49
69 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
50 image = thumbs.ImageWithThumbsField(upload_to=get_upload_filename,
70 blank=True, sizes=(IMAGE_THUMB_SIZE,),
51 blank=True, sizes=(IMAGE_THUMB_SIZE,),
71 width_field='width',
52 width_field='width',
72 height_field='height',
53 height_field='height',
@@ -18,6 +18,13 b' from boards.models.post.export import ge'
18 from boards.models.post.manager import PostManager
18 from boards.models.post.manager import PostManager
19 from boards.models.user import Notification
19 from boards.models.user import Notification
20
20
21 CSS_CLS_HIDDEN_POST = 'hidden_post'
22 CSS_CLS_DEAD_POST = 'dead_post'
23 CSS_CLS_ARCHIVE_POST = 'archive_post'
24 CSS_CLS_POST = 'post'
25
26 TITLE_MAX_WORDS = 10
27
21 APP_LABEL_BOARDS = 'boards'
28 APP_LABEL_BOARDS = 'boards'
22
29
23 BAN_REASON_AUTO = 'Auto'
30 BAN_REASON_AUTO = 'Auto'
@@ -95,8 +102,9 b' class Post(models.Model, Viewable):'
95 # server, this indicates the server.
102 # server, this indicates the server.
96 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
103 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
97
104
98 tripcode = models.CharField(max_length=50, null=True)
105 tripcode = models.CharField(max_length=50, blank=True, default='')
99 opening = models.BooleanField()
106 opening = models.BooleanField(db_index=True)
107 hidden = models.BooleanField(default=False)
100
108
101 def __str__(self):
109 def __str__(self):
102 return 'P#{}/{}'.format(self.id, self.get_title())
110 return 'P#{}/{}'.format(self.id, self.get_title())
@@ -137,15 +145,24 b' class Post(models.Model, Viewable):'
137
145
138 return self.opening
146 return self.opening
139
147
140 def get_absolute_url(self):
148 def get_absolute_url(self, thread=None):
141 if self.url:
149 url = None
142 return self.url
150
143 else:
151 if thread is None:
144 opening_id = self.get_thread().get_opening_post_id()
152 thread = self.get_thread()
145 post_url = reverse('thread', kwargs={'post_id': opening_id})
153
154 # Url is cached only for the "main" thread. When getting url
155 # for other threads, do it manually.
156 if self.url:
157 url = self.url
158
159 if url is None:
160 opening_id = thread.get_opening_post_id()
161 url = reverse('thread', kwargs={'post_id': opening_id})
146 if self.id != opening_id:
162 if self.id != opening_id:
147 post_url += '#' + str(self.id)
163 url += '#' + str(self.id)
148 return post_url
164
165 return url
149
166
150 def get_thread(self):
167 def get_thread(self):
151 return self.thread
168 return self.thread
@@ -166,11 +183,13 b' class Post(models.Model, Viewable):'
166
183
167 thread = self.get_thread()
184 thread = self.get_thread()
168
185
169 css_class = 'post'
186 css_classes = [CSS_CLS_POST]
170 if thread.archived:
187 if thread.archived:
171 css_class += ' archive_post'
188 css_classes.append(CSS_CLS_ARCHIVE_POST)
172 elif not thread.can_bump():
189 elif not thread.can_bump():
173 css_class += ' dead_post'
190 css_classes.append(CSS_CLS_DEAD_POST)
191 if self.is_hidden():
192 css_classes.append(CSS_CLS_HIDDEN_POST)
174
193
175 params = dict()
194 params = dict()
176 for param in POST_VIEW_PARAMS:
195 for param in POST_VIEW_PARAMS:
@@ -181,7 +200,7 b' class Post(models.Model, Viewable):'
181 PARAMETER_POST: self,
200 PARAMETER_POST: self,
182 PARAMETER_IS_OPENING: self.is_opening(),
201 PARAMETER_IS_OPENING: self.is_opening(),
183 PARAMETER_THREAD: thread,
202 PARAMETER_THREAD: thread,
184 PARAMETER_CSS_CLASS: css_class,
203 PARAMETER_CSS_CLASS: ' '.join(css_classes),
185 })
204 })
186
205
187 return render_to_string('boards/post.html', params)
206 return render_to_string('boards/post.html', params)
@@ -402,10 +421,15 b' class Post(models.Model, Viewable):'
402 """
421 """
403 Gets view of a reflink to the post.
422 Gets view of a reflink to the post.
404 """
423 """
405
406 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
424 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
407 self.id)
425 self.id)
408 if self.is_opening():
426 if self.is_opening():
409 result = '<b>{}</b>'.format(result)
427 result = '<b>{}</b>'.format(result)
410
428
411 return result
429 return result
430
431 def is_hidden(self) -> bool:
432 return self.hidden
433
434 def set_hidden(self, hidden):
435 self.hidden = hidden
@@ -1,19 +1,19 b''
1 import logging
2
1 from datetime import datetime, timedelta, date
3 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
4 from datetime import time as dtime
3 import logging
5
4 from django.db import models, transaction
6 from django.db import models, transaction
5 from django.utils import timezone
7 from django.utils import timezone
6 from boards import utils
8
9 import boards
10
11 from boards.models.user import Ban
7 from boards.mdx_neboard import Parser
12 from boards.mdx_neboard import Parser
8 from boards.models import PostImage, Attachment
13 from boards.models import PostImage, Attachment
9 from boards.models.user import Ban
14 from boards import utils
10 import boards.models
11
15
12 __author__ = 'vurdalak'
16 __author__ = 'neko259'
13
14
15 NO_IP = '0.0.0.0'
16 POSTS_PER_DAY_RANGE = 7
17
17
18 IMAGE_TYPES = (
18 IMAGE_TYPES = (
19 'jpeg',
19 'jpeg',
@@ -23,16 +23,21 b' IMAGE_TYPES = ('
23 'gif',
23 'gif',
24 )
24 )
25
25
26 POSTS_PER_DAY_RANGE = 7
27 NO_IP = '0.0.0.0'
28
26
29
27 class PostManager(models.Manager):
30 class PostManager(models.Manager):
28 @transaction.atomic
31 @transaction.atomic
29 def create_post(self, title: str, text: str, file=None, thread=None,
32 def create_post(self, title: str, text: str, file=None, thread=None,
30 ip=NO_IP, tags: list=None, opening_posts: list=None, tripcode=None):
33 ip=NO_IP, tags: list=None, opening_posts: list=None,
34 tripcode=''):
31 """
35 """
32 Creates new post
36 Creates new post
33 """
37 """
34
38
35 is_banned = Ban.objects.filter(ip=ip).exists()
39 if not utils.is_anonymous_mode():
40 is_banned = Ban.objects.filter(ip=ip).exists()
36
41
37 # TODO Raise specific exception and catch it in the views
42 # TODO Raise specific exception and catch it in the views
38 if is_banned:
43 if is_banned:
@@ -66,7 +71,8 b' class PostManager(models.Manager):'
66
71
67 logger = logging.getLogger('boards.post.create')
72 logger = logging.getLogger('boards.post.create')
68
73
69 logger.info('Created post {} by {}'.format(post, post.poster_ip))
74 logger.info('Created post [{}] with text [{}] by {}'.format(post,
75 post.get_text(),post.poster_ip))
70
76
71 # TODO Move this to other place
77 # TODO Move this to other place
72 if file:
78 if file:
@@ -48,7 +48,8 b' class Tag(models.Model, Viewable):'
48 required = models.BooleanField(default=False, db_index=True)
48 required = models.BooleanField(default=False, db_index=True)
49 description = models.TextField(blank=True)
49 description = models.TextField(blank=True)
50
50
51 parent = models.ForeignKey('Tag', null=True, related_name='children')
51 parent = models.ForeignKey('Tag', null=True, blank=True,
52 related_name='children')
52
53
53 def __str__(self):
54 def __str__(self):
54 return self.name
55 return self.name
@@ -60,14 +61,22 b' class Tag(models.Model, Viewable):'
60
61
61 return self.get_thread_count() == 0
62 return self.get_thread_count() == 0
62
63
63 def get_thread_count(self, archived=None) -> int:
64 def get_thread_count(self, archived=None, bumpable=None) -> int:
64 threads = self.get_threads()
65 threads = self.get_threads()
65 if archived is not None:
66 if archived is not None:
66 threads = threads.filter(archived=archived)
67 threads = threads.filter(archived=archived)
68 if bumpable is not None:
69 threads = threads.filter(bumpable=bumpable)
67 return threads.count()
70 return threads.count()
68
71
69 def get_active_thread_count(self) -> int:
72 def get_active_thread_count(self) -> int:
70 return self.get_thread_count(archived=False)
73 return self.get_thread_count(archived=False, bumpable=True)
74
75 def get_bumplimit_thread_count(self) -> int:
76 return self.get_thread_count(archived=False, bumpable=False)
77
78 def get_archived_thread_count(self) -> int:
79 return self.get_thread_count(archived=True)
71
80
72 def get_absolute_url(self):
81 def get_absolute_url(self):
73 return reverse('tag', kwargs={'tag_name': self.name})
82 return reverse('tag', kwargs={'tag_name': self.name})
@@ -122,11 +131,11 b' class Tag(models.Model, Viewable):'
122 return self.parent
131 return self.parent
123
132
124 def get_all_parents(self):
133 def get_all_parents(self):
125 parents = set()
134 parents = list()
126 parent = self.get_parent()
135 parent = self.get_parent()
127 if parent and parent not in parents:
136 if parent and parent not in parents:
128 parents.add(parent)
137 parents.insert(0, parent)
129 parents |= parent.get_all_parents()
138 parents = parent.get_all_parents() + parents
130
139
131 return parents
140 return parents
132
141
@@ -194,7 +194,7 b' class Thread(models.Model):'
194 Gets the first post of the thread
194 Gets the first post of the thread
195 """
195 """
196
196
197 query = self.get_replies().order_by('pub_time')
197 query = self.get_replies().filter(opening=True)
198 if only_id:
198 if only_id:
199 query = query.only('id')
199 query = query.only('id')
200 opening_post = query.first()
200 opening_post = query.first()
@@ -142,4 +142,12 b' textarea, input {'
142
142
143 #new-fav-post-count {
143 #new-fav-post-count {
144 display: none;
144 display: none;
145 } No newline at end of file
145 }
146
147 .hidden_post {
148 opacity: 0.2;
149 }
150
151 .hidden_post:hover {
152 opacity: 1;
153 }
@@ -112,13 +112,13 b' PopupImageViewer.prototype.view = functi'
112 .attr('id', thumb_id)
112 .attr('id', thumb_id)
113 .attr('src', postNode.attr('href'))
113 .attr('src', postNode.attr('href'))
114 .attr(ATTR_SCALE, scale)
114 .attr(ATTR_SCALE, scale)
115 .appendTo(postNode)
116 .css({
115 .css({
117 'width': img_w,
116 'width': img_w,
118 'height': img_h,
117 'height': img_h,
119 'left': (win_w - img_w) / 2,
118 'left': (win_w - img_w) / 2,
120 'top': ((win_h - img_h) / 2)
119 'top': ((win_h - img_h) / 2)
121 })
120 })
121 .appendTo(postNode)
122 //scaling preview
122 //scaling preview
123 .mousewheel(function(event, delta) {
123 .mousewheel(function(event, delta) {
124 var cx = event.originalEvent.clientX;
124 var cx = event.originalEvent.clientX;
@@ -72,12 +72,17 b''
72 {% if tag.get_description %}
72 {% if tag.get_description %}
73 <p>{{ tag.get_description|safe }}</p>
73 <p>{{ tag.get_description|safe }}</p>
74 {% endif %}
74 {% endif %}
75 <p>{% blocktrans with active_thread_count=tag.get_active_thread_count thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ thread_count }} threads ({{ active_thread_count}} active) and {{ post_count }} posts.{% endblocktrans %}</p>
75 <p>
76 {% if tag.get_parent %}
76 {% blocktrans count count=tag.get_active_thread_count %}{{ count }} active thread{% plural %}active threads{% endblocktrans %},
77 {% blocktrans count count=tag.get_bumplimit_thread_count %}{{ count }} thread in bumplimit{% plural %} threads in bumplimit{% endblocktrans %},
78 {% blocktrans count count=tag.get_archived_thread_count %}{{ count }} archived thread{% plural %}archived threads{% endblocktrans %},
79 {% blocktrans count count=tag.get_post_count %}{{ count }} message{% plural %}messages{% endblocktrans %}.
80 </p>
81 {% if tag.get_all_parents %}
77 <p>
82 <p>
78 {% if tag.get_parent %}
83 {% for parent in tag.get_all_parents %}
79 {{ tag.get_parent.get_view|safe }} /
84 {{ parent.get_view|safe }} &gt;
80 {% endif %}
85 {% endfor %}
81 {{ tag.get_view|safe }}
86 {{ tag.get_view|safe }}
82 </p>
87 </p>
83 {% endif %}
88 {% endif %}
@@ -170,13 +175,7 b''
170 {% ifequal page current_page.number %}
175 {% ifequal page current_page.number %}
171 class="current_page"
176 class="current_page"
172 {% endifequal %}
177 {% endifequal %}
173 href="
178 href="{% page_url paginator page %}">{{ page }}</a>
174 {% if tag %}
175 {% url "tag" tag_name=tag.name %}?page={{ page }}
176 {% else %}
177 {% url "index" %}?page={{ page }}
178 {% endif %}
179 ">{{ page }}</a>
180 {% if not forloop.last %},{% endif %}
179 {% if not forloop.last %},{% endif %}
181 {% endfor %}
180 {% endfor %}
182 {% endwith %}
181 {% endwith %}
@@ -69,6 +69,7 b''
69 <div class="navigation_panel footer">
69 <div class="navigation_panel footer">
70 {% block metapanel %}{% endblock %}
70 {% block metapanel %}{% endblock %}
71 [<a href="{% url 'admin:index' %}">{% trans 'Admin' %}</a>]
71 [<a href="{% url 'admin:index' %}">{% trans 'Admin' %}</a>]
72 [<a href="{% url 'index' %}?order=pub">{% trans 'New threads' %}</a>]
72 {% with ppd=posts_per_day|floatformat:2 %}
73 {% with ppd=posts_per_day|floatformat:2 %}
73 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
74 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
74 {% endwith %}
75 {% endwith %}
@@ -61,7 +61,7 b''
61 {% ifequal page current_page.number %}
61 {% ifequal page current_page.number %}
62 class="current_page"
62 class="current_page"
63 {% endifequal %}
63 {% endifequal %}
64 href="{% url "feed" %}?page={{ page }}{{ additional_attrs }}">{{ page }}</a>
64 href="{% page_url paginator page %}">{{ page }}</a>
65 {% if not forloop.last %},{% endif %}
65 {% if not forloop.last %},{% endif %}
66 {% endfor %}
66 {% endfor %}
67 {% endwith %}
67 {% endwith %}
@@ -9,6 +9,7 b''
9 <span class="title">{{ post.title }}</span>
9 <span class="title">{{ post.title }}</span>
10 <span class="pub_time"><time datetime="{{ post.pub_time|date:'c' }}">{{ post.pub_time }}</time></span>
10 <span class="pub_time"><time datetime="{{ post.pub_time|date:'c' }}">{{ post.pub_time }}</time></span>
11 {% if post.tripcode %}
11 {% if post.tripcode %}
12 /
12 {% with tripcode=post.get_tripcode %}
13 {% with tripcode=post.get_tripcode %}
13 <a href="{% url 'feed' %}?tripcode={{ tripcode.get_full_text }}"
14 <a href="{% url 'feed' %}?tripcode={{ tripcode.get_full_text }}"
14 class="tripcode" title="{{ tripcode.get_full_text }}"
15 class="tripcode" title="{{ tripcode.get_full_text }}"
@@ -54,6 +55,10 b''
54 {% if is_opening %}
55 {% if is_opening %}
55 | <a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>
56 | <a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>
56 {% endif %}
57 {% endif %}
58 <form action="{% url 'thread' thread.get_opening_post_id %}?post_id={{ post.id }}" method="post" class="post-button-form">
59 | <button name="method" value="toggle_hide_post">H</button>
60 </form>
61 </form>
57 </span>
62 </span>
58 {% endif %}
63 {% endif %}
59 </div>
64 </div>
@@ -43,3 +43,7 b' def image_actions(*args, **kwargs):'
43 def post_view(post, *args, **kwargs):
43 def post_view(post, *args, **kwargs):
44 return post.get_view(*args, **kwargs)
44 return post.get_view(*args, **kwargs)
45
45
46 @register.simple_tag(name='page_url')
47 def page_url(paginator, page_number, *args, **kwargs):
48 if paginator.supports_urls():
49 return paginator.get_page_url(page_number)
@@ -2,29 +2,53 b''
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import hashlib
4 import hashlib
5 from random import random
5 import time
6 import time
6 import hmac
7 import hmac
7
8
8 from django.core.cache import cache
9 from django.core.cache import cache
9 from django.db.models import Model
10 from django.db.models import Model
10 from django import forms
11 from django import forms
11
12 from django.utils import timezone
12 from django.utils import timezone
13 from django.utils.translation import ugettext_lazy as _
13 from django.utils.translation import ugettext_lazy as _
14 import boards
14 import magic
15 from portage import os
15
16
17 import boards
18 from boards.settings import get_bool
16 from neboard import settings
19 from neboard import settings
17
20
18
19 CACHE_KEY_DELIMITER = '_'
21 CACHE_KEY_DELIMITER = '_'
20 PERMISSION_MODERATE = 'moderation'
22 PERMISSION_MODERATE = 'moderation'
21
23
24 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
25 META_REMOTE_ADDR = 'REMOTE_ADDR'
26
27 SETTING_MESSAGES = 'Messages'
28 SETTING_ANON_MODE = 'AnonymousMode'
29
30 ANON_IP = '127.0.0.1'
31
32 UPLOAD_DIRS ={
33 'PostImage': 'images/',
34 'Attachment': 'files/',
35 }
36 FILE_EXTENSION_DELIMITER = '.'
37
38
39 def is_anonymous_mode():
40 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
41
42
22 def get_client_ip(request):
43 def get_client_ip(request):
23 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
44 if is_anonymous_mode():
24 if x_forwarded_for:
45 ip = ANON_IP
25 ip = x_forwarded_for.split(',')[-1].strip()
26 else:
46 else:
27 ip = request.META.get('REMOTE_ADDR')
47 x_forwarded_for = request.META.get(HTTP_FORWARDED)
48 if x_forwarded_for:
49 ip = x_forwarded_for.split(',')[-1].strip()
50 else:
51 ip = request.META.get(META_REMOTE_ADDR)
28 return ip
52 return ip
29
53
30
54
@@ -101,3 +125,24 b' def validate_file_size(size: int):'
101 raise forms.ValidationError(
125 raise forms.ValidationError(
102 _('File must be less than %s bytes')
126 _('File must be less than %s bytes')
103 % str(max_size))
127 % str(max_size))
128
129
130 def get_extension(filename):
131 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
132
133
134 def get_upload_filename(model_instance, old_filename):
135 # TODO Use something other than random number in file name
136 extension = get_extension(old_filename)
137 new_name = '{}{}.{}'.format(
138 str(int(time.mktime(time.gmtime()))),
139 str(int(random() * 1000)),
140 extension)
141
142 directory = UPLOAD_DIRS[type(model_instance).__name__]
143
144 return os.path.join(directory, new_name)
145
146
147 def get_file_mimetype(file) -> str:
148 return magic.from_buffer(file.chunks().__next__(), mime=True).decode()
@@ -29,6 +29,7 b" PARAMETER_CURRENT_PAGE = 'current_page'"
29 PARAMETER_PAGINATOR = 'paginator'
29 PARAMETER_PAGINATOR = 'paginator'
30 PARAMETER_THREADS = 'threads'
30 PARAMETER_THREADS = 'threads'
31 PARAMETER_BANNERS = 'banners'
31 PARAMETER_BANNERS = 'banners'
32 PARAMETER_ADDITIONAL = 'additional_params'
32
33
33 PARAMETER_PREV_LINK = 'prev_page_link'
34 PARAMETER_PREV_LINK = 'prev_page_link'
34 PARAMETER_NEXT_LINK = 'next_page_link'
35 PARAMETER_NEXT_LINK = 'next_page_link'
@@ -52,7 +53,16 b' class AllThreadsView(PostMixin, BaseBoar'
52 form = ThreadForm(error_class=PlainErrorList)
53 form = ThreadForm(error_class=PlainErrorList)
53
54
54 self.settings_manager = get_settings_manager(request)
55 self.settings_manager = get_settings_manager(request)
55 paginator = get_paginator(self.get_threads(),
56
57 threads = self.get_threads()
58
59 order = request.GET.get('order', 'bump')
60 if order == 'bump':
61 threads = threads.order_by('-bump_time')
62 else:
63 threads = threads.filter(multi_replies__opening=True).order_by('-multi_replies__pub_time')
64
65 paginator = get_paginator(threads,
56 settings.get_int('View', 'ThreadsPerPage'))
66 settings.get_int('View', 'ThreadsPerPage'))
57 paginator.current_page = int(page)
67 paginator.current_page = int(page)
58
68
@@ -65,6 +75,7 b' class AllThreadsView(PostMixin, BaseBoar'
65 params[CONTEXT_FORM] = form
75 params[CONTEXT_FORM] = form
66 params[PARAMETER_BANNERS] = Banner.objects.order_by('-id').all()
76 params[PARAMETER_BANNERS] = Banner.objects.order_by('-id').all()
67
77
78 paginator.set_url(self.get_reverse_url(), request.GET.dict())
68 self.get_page_context(paginator, params, page)
79 self.get_page_context(paginator, params, page)
69
80
70 return render(request, TEMPLATE, params)
81 return render(request, TEMPLATE, params)
@@ -91,18 +102,14 b' class AllThreadsView(PostMixin, BaseBoar'
91 current_page = paginator.page(int(page))
102 current_page = paginator.page(int(page))
92 params[PARAMETER_CURRENT_PAGE] = current_page
103 params[PARAMETER_CURRENT_PAGE] = current_page
93 if current_page.has_previous():
104 if current_page.has_previous():
94 params[PARAMETER_PREV_LINK] = self.get_previous_page_link(
105 params[PARAMETER_PREV_LINK] = paginator.get_page_url(
95 current_page)
106 current_page.previous_page_number())
96 if current_page.has_next():
107 if current_page.has_next():
97 params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page)
108 params[PARAMETER_NEXT_LINK] = paginator.get_page_url(
109 current_page.next_page_number())
98
110
99 def get_previous_page_link(self, current_page):
111 def get_reverse_url(self):
100 return reverse('index') + '?page=' \
112 return reverse('index')
101 + str(current_page.previous_page_number())
102
103 def get_next_page_link(self, current_page):
104 return reverse('index') + '?page=' \
105 + str(current_page.next_page_number())
106
113
107 @transaction.atomic
114 @transaction.atomic
108 def create_thread(self, request, form: ThreadForm, html_response=True):
115 def create_thread(self, request, form: ThreadForm, html_response=True):
@@ -146,5 +153,5 b' class AllThreadsView(PostMixin, BaseBoar'
146 Gets list of threads that will be shown on a page.
153 Gets list of threads that will be shown on a page.
147 """
154 """
148
155
149 return Thread.objects.order_by('-bump_time')\
156 return Thread.objects\
150 .exclude(tags__in=self.settings_manager.get_hidden_tags())
157 .exclude(tags__in=self.settings_manager.get_hidden_tags())
@@ -276,7 +276,7 b' def api_get_new_posts(request):'
276 new_post_count = thread.new_post_count
276 new_post_count = thread.new_post_count
277 fav_thread_dict['newest_post_link'] = thread.get_replies()\
277 fav_thread_dict['newest_post_link'] = thread.get_replies()\
278 .filter(id__gt=fav_threads[str(op.id)])\
278 .filter(id__gt=fav_threads[str(op.id)])\
279 .first().get_absolute_url()
279 .first().get_absolute_url(thread=thread)
280 else:
280 else:
281 new_post_count = 0
281 new_post_count = 0
282 fav_thread_dict['new_post_count'] = new_post_count
282 fav_thread_dict['new_post_count'] = new_post_count
@@ -47,12 +47,14 b' class FeedView(PostMixin, BaseBoardView)'
47 additional_params['tripcode'] = tripcode
47 additional_params['tripcode'] = tripcode
48 params[PARAMETER_ADDITONAL_ATTRS] = '&tripcode=' + tripcode
48 params[PARAMETER_ADDITONAL_ATTRS] = '&tripcode=' + tripcode
49
49
50 self.get_page_context(paginator, params, page, additional_params)
50 paginator.set_url(reverse('feed'), request.GET.dict())
51
52 self.get_page_context(paginator, params, page)
51
53
52 return render(request, TEMPLATE, params)
54 return render(request, TEMPLATE, params)
53
55
54 # TODO Dedup this into PagedMixin
56 # TODO Dedup this into PagedMixin
55 def get_page_context(self, paginator, params, page, additional_params):
57 def get_page_context(self, paginator, params, page):
56 """
58 """
57 Get pagination context variables
59 Get pagination context variables
58 """
60 """
@@ -61,21 +63,9 b' class FeedView(PostMixin, BaseBoardView)'
61 current_page = paginator.page(int(page))
63 current_page = paginator.page(int(page))
62 params[PARAMETER_CURRENT_PAGE] = current_page
64 params[PARAMETER_CURRENT_PAGE] = current_page
63 if current_page.has_previous():
65 if current_page.has_previous():
64 params[PARAMETER_PREV_LINK] = self.get_previous_page_link(
66 params[PARAMETER_PREV_LINK] = paginator.get_page_url(
65 current_page)
67 current_page.previous_page_number())
66 for param in additional_params.keys():
67 params[PARAMETER_PREV_LINK] += '&{}={}'.format(
68 param, additional_params[param])
69 if current_page.has_next():
68 if current_page.has_next():
70 params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page)
69 params[PARAMETER_NEXT_LINK] = paginator.get_page_url(
71 for param in additional_params.keys():
70 current_page.next_page_number())
72 params[PARAMETER_NEXT_LINK] += '&{}={}'.format(
73 param, additional_params[param])
74
71
75 def get_previous_page_link(self, current_page):
76 return reverse('feed') + '?page={}'.format(
77 current_page.previous_page_number())
78
79 def get_next_page_link(self, current_page):
80 return reverse('feed') + '?page={}'.format(
81 current_page.next_page_number())
@@ -8,9 +8,14 b' class DispatcherMixin:'
8 'method' request parameter.
8 'method' request parameter.
9 """
9 """
10
10
11 def __init__(self):
12 self.user = None
13
11 def dispatch_method(self, *args, **kwargs):
14 def dispatch_method(self, *args, **kwargs):
12 request = args[0]
15 request = args[0]
13
16
17 self.user = request.user
18
14 method_name = None
19 method_name = None
15 if PARAMETER_METHOD in request.GET:
20 if PARAMETER_METHOD in request.GET:
16 method_name = request.GET[PARAMETER_METHOD]
21 method_name = request.GET[PARAMETER_METHOD]
@@ -5,7 +5,7 b' from boards.abstracts.settingsmanager im'
5 SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS
5 SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS
6 from boards.models import Tag, PostImage
6 from boards.models import Tag, PostImage
7 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
7 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
8 from boards.views.mixins import DispatcherMixin
8 from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD
9 from boards.forms import ThreadForm, PlainErrorList
9 from boards.forms import ThreadForm, PlainErrorList
10
10
11 PARAM_HIDDEN_TAGS = 'hidden_tags'
11 PARAM_HIDDEN_TAGS = 'hidden_tags'
@@ -55,15 +55,8 b' class TagView(AllThreadsView, Dispatcher'
55
55
56 return params
56 return params
57
57
58 def get_previous_page_link(self, current_page):
58 def get_reverse_url(self):
59 return reverse('tag', kwargs={
59 return reverse('tag', kwargs={'tag_name': self.tag_name})
60 'tag_name': self.tag_name,
61 }) + '?page=' + str(current_page.previous_page_number())
62
63 def get_next_page_link(self, current_page):
64 return reverse('tag', kwargs={
65 'tag_name': self.tag_name,
66 }) + '?page=' + str(current_page.next_page_number())
67
60
68 def get(self, request, tag_name, form=None):
61 def get(self, request, tag_name, form=None):
69 self.tag_name = tag_name
62 self.tag_name = tag_name
@@ -74,9 +67,8 b' class TagView(AllThreadsView, Dispatcher'
74 def post(self, request, tag_name):
67 def post(self, request, tag_name):
75 self.tag_name = tag_name
68 self.tag_name = tag_name
76
69
77 if 'method' in request.POST:
70 if PARAMETER_METHOD in request.POST:
78 self.dispatch_method(request)
71 self.dispatch_method(request)
79 form = None
80
72
81 return redirect('tag', tag_name)
73 return redirect('tag', tag_name)
82 else:
74 else:
@@ -1,4 +1,5 b''
1 import hashlib
1 from django.contrib.auth.decorators import permission_required
2
2 from django.core.exceptions import ObjectDoesNotExist
3 from django.core.exceptions import ObjectDoesNotExist
3 from django.http import Http404
4 from django.http import Http404
4 from django.shortcuts import get_object_or_404, render, redirect
5 from django.shortcuts import get_object_or_404, render, redirect
@@ -11,11 +12,11 b' from boards.abstracts.settingsmanager im'
11 from boards.forms import PostForm, PlainErrorList
12 from boards.forms import PostForm, PlainErrorList
12 from boards.models import Post
13 from boards.models import Post
13 from boards.views.base import BaseBoardView, CONTEXT_FORM
14 from boards.views.base import BaseBoardView, CONTEXT_FORM
14 from boards.views.mixins import DispatcherMixin
15 from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD
15 from boards.views.posting_mixin import PostMixin
16 from boards.views.posting_mixin import PostMixin
16
17 import neboard
17 import neboard
18
18
19 REQ_POST_ID = 'post_id'
19
20
20 CONTEXT_LASTUPDATE = "last_update"
21 CONTEXT_LASTUPDATE = "last_update"
21 CONTEXT_THREAD = 'thread'
22 CONTEXT_THREAD = 'thread'
@@ -88,7 +89,7 b' class ThreadView(BaseBoardView, PostMixi'
88 if not opening_post.is_opening():
89 if not opening_post.is_opening():
89 raise Http404
90 raise Http404
90
91
91 if 'method' in request.POST:
92 if PARAMETER_METHOD in request.POST:
92 self.dispatch_method(request, opening_post)
93 self.dispatch_method(request, opening_post)
93
94
94 return redirect('thread', post_id) # FIXME Different for different modes
95 return redirect('thread', post_id) # FIXME Different for different modes
@@ -161,3 +162,12 b' class ThreadView(BaseBoardView, PostMixi'
161 def unsubscribe(self, request, opening_post):
162 def unsubscribe(self, request, opening_post):
162 settings_manager = get_settings_manager(request)
163 settings_manager = get_settings_manager(request)
163 settings_manager.del_fav_thread(opening_post)
164 settings_manager.del_fav_thread(opening_post)
165
166 @permission_required('boards.post_hide_unhide')
167 def toggle_hide_post(self, request, opening_post):
168 post_id = request.GET.get(REQ_POST_ID)
169
170 if post_id:
171 post = get_object_or_404(Post, id=post_id)
172 post.set_hidden(not post.is_hidden())
173 post.save(update_fields=['hidden'])
@@ -109,7 +109,7 b' TEMPLATE_CONTEXT_PROCESSORS = ('
109 'boards.context_processors.user_and_ui_processor',
109 'boards.context_processors.user_and_ui_processor',
110 )
110 )
111
111
112 MIDDLEWARE_CLASSES = (
112 MIDDLEWARE_CLASSES = [
113 'django.middleware.http.ConditionalGetMiddleware',
113 'django.middleware.http.ConditionalGetMiddleware',
114 'django.contrib.sessions.middleware.SessionMiddleware',
114 'django.contrib.sessions.middleware.SessionMiddleware',
115 'django.middleware.locale.LocaleMiddleware',
115 'django.middleware.locale.LocaleMiddleware',
@@ -118,7 +118,7 b' MIDDLEWARE_CLASSES = ('
118 'django.contrib.messages.middleware.MessageMiddleware',
118 'django.contrib.messages.middleware.MessageMiddleware',
119 'boards.middlewares.BanMiddleware',
119 'boards.middlewares.BanMiddleware',
120 'boards.middlewares.TimezoneMiddleware',
120 'boards.middlewares.TimezoneMiddleware',
121 )
121 ]
122
122
123 ROOT_URLCONF = 'neboard.urls'
123 ROOT_URLCONF = 'neboard.urls'
124
124
@@ -208,6 +208,8 b' IMAGE_VIEWERS = ['
208 ('popup', 'Popup'),
208 ('popup', 'Popup'),
209 ]
209 ]
210
210
211 ALLOWED_HOSTS = ['*']
212
211 POSTING_DELAY = 20 # seconds
213 POSTING_DELAY = 20 # seconds
212
214
213 # Websocket settins
215 # Websocket settins
@@ -219,21 +221,20 b" CENTRIFUGE_PROJECT_ID = '<project id her"
219 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
221 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
220 CENTRIFUGE_TIMEOUT = 5
222 CENTRIFUGE_TIMEOUT = 5
221
223
222 # Debug mode middlewares
224 # Debug middlewares
223 if DEBUG:
225 MIDDLEWARE_CLASSES += [
224 MIDDLEWARE_CLASSES += (
226 'debug_toolbar.middleware.DebugToolbarMiddleware',
225 'debug_toolbar.middleware.DebugToolbarMiddleware',
227 ]
226 )
227
228
228 def custom_show_toolbar(request):
229 def custom_show_toolbar(request):
229 return True
230 return request.user.has_perm('admin.debug')
230
231
231 DEBUG_TOOLBAR_CONFIG = {
232 DEBUG_TOOLBAR_CONFIG = {
232 'ENABLE_STACKTRACES': True,
233 'ENABLE_STACKTRACES': True,
233 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
234 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
234 }
235 }
235
236
236 # FIXME Uncommenting this fails somehow. Need to investigate this
237 # FIXME Uncommenting this fails somehow. Need to investigate this
237 #DEBUG_TOOLBAR_PANELS += (
238 #DEBUG_TOOLBAR_PANELS += (
238 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
239 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
239 #)
240 #)
@@ -1,5 +1,6 b''
1 httplib2
1 httplib2
2 simplejson
2 simplejson
3 magic
3 pytube
4 pytube
4 requests
5 requests
5 adjacent
6 adjacent
General Comments 0
You need to be logged in to leave comments. Login now