Show More
@@ -0,0 +1,33 b'' | |||||
|
1 | [Version] | |||
|
2 | Version = 2.7.0 Chani | |||
|
3 | SiteName = Neboard | |||
|
4 | ||||
|
5 | [Cache] | |||
|
6 | # Timeout for caching, if cache is used | |||
|
7 | CacheTimeout = 600 | |||
|
8 | ||||
|
9 | [Forms] | |||
|
10 | # Max post length in characters | |||
|
11 | MaxTextLength = 30000 | |||
|
12 | MaxImageSize = 8000000 | |||
|
13 | LimitPostingSpeed = false | |||
|
14 | ||||
|
15 | [Messages] | |||
|
16 | # Thread bumplimit | |||
|
17 | MaxPostsPerThread = 10 | |||
|
18 | # Old posts will be archived or deleted if this value is reached | |||
|
19 | MaxThreadCount = 5 | |||
|
20 | ||||
|
21 | [View] | |||
|
22 | DefaultTheme = md | |||
|
23 | DefaultImageViewer = simple | |||
|
24 | LastRepliesCount = 3 | |||
|
25 | ThreadsPerPage = 3 | |||
|
26 | ||||
|
27 | [Storage] | |||
|
28 | # Enable archiving threads instead of deletion when the thread limit is reached | |||
|
29 | ArchiveThreads = true | |||
|
30 | ||||
|
31 | [External] | |||
|
32 | # Thread update | |||
|
33 | WebsocketsEnabled = false |
@@ -0,0 +1,23 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | from __future__ import unicode_literals | |||
|
3 | ||||
|
4 | from django.db import models, migrations | |||
|
5 | ||||
|
6 | ||||
|
7 | class Migration(migrations.Migration): | |||
|
8 | ||||
|
9 | dependencies = [ | |||
|
10 | ('boards', '0017_auto_20150503_1847'), | |||
|
11 | ] | |||
|
12 | ||||
|
13 | operations = [ | |||
|
14 | migrations.CreateModel( | |||
|
15 | name='Banner', | |||
|
16 | fields=[ | |||
|
17 | ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), | |||
|
18 | ('title', models.TextField()), | |||
|
19 | ('text', models.TextField()), | |||
|
20 | ('post', models.ForeignKey(to='boards.Post')), | |||
|
21 | ], | |||
|
22 | ), | |||
|
23 | ] |
@@ -0,0 +1,10 b'' | |||||
|
1 | from django.db import models | |||
|
2 | ||||
|
3 | ||||
|
4 | class Banner(models.Model): | |||
|
5 | title = models.TextField() | |||
|
6 | text = models.TextField() | |||
|
7 | post = models.ForeignKey('Post') | |||
|
8 | ||||
|
9 | def __str__(self): | |||
|
10 | return self.title |
@@ -0,0 +1,55 b'' | |||||
|
1 | from boards import utils | |||
|
2 | ||||
|
3 | ||||
|
4 | PARAMETER_TRUNCATED = 'truncated' | |||
|
5 | ||||
|
6 | DIFF_TYPE_HTML = 'html' | |||
|
7 | DIFF_TYPE_JSON = 'json' | |||
|
8 | ||||
|
9 | ||||
|
10 | class Exporter(): | |||
|
11 | @staticmethod | |||
|
12 | def export(post, request, include_last_update) -> str: | |||
|
13 | pass | |||
|
14 | ||||
|
15 | ||||
|
16 | class HtmlExporter(Exporter): | |||
|
17 | @staticmethod | |||
|
18 | def export(post, request, include_last_update): | |||
|
19 | if request is not None and PARAMETER_TRUNCATED in request.GET: | |||
|
20 | truncated = True | |||
|
21 | reply_link = False | |||
|
22 | else: | |||
|
23 | truncated = False | |||
|
24 | reply_link = True | |||
|
25 | ||||
|
26 | return post.get_view(truncated=truncated, reply_link=reply_link, | |||
|
27 | moderator=utils.is_moderator(request)) | |||
|
28 | ||||
|
29 | ||||
|
30 | class JsonExporter(Exporter): | |||
|
31 | @staticmethod | |||
|
32 | def export(post, request, include_last_update): | |||
|
33 | post_json = { | |||
|
34 | 'id': post.id, | |||
|
35 | 'title': post.title, | |||
|
36 | 'text': post.get_raw_text(), | |||
|
37 | } | |||
|
38 | if post.images.exists(): | |||
|
39 | post_image = post.get_first_image() | |||
|
40 | post_json['image'] = post_image.image.url | |||
|
41 | post_json['image_preview'] = post_image.image.url_200x150 | |||
|
42 | if include_last_update: | |||
|
43 | post_json['bump_time'] = utils.datetime_to_epoch( | |||
|
44 | post.get_thread().bump_time) | |||
|
45 | return post_json | |||
|
46 | ||||
|
47 | ||||
|
48 | EXPORTERS = { | |||
|
49 | DIFF_TYPE_HTML: HtmlExporter, | |||
|
50 | DIFF_TYPE_JSON: JsonExporter, | |||
|
51 | } | |||
|
52 | ||||
|
53 | ||||
|
54 | def get_exporter(export_type: str) -> Exporter: | |||
|
55 | return EXPORTERS[export_type]() |
@@ -28,3 +28,4 b' 119fafc5381b933bf30d97be0b278349f6135075' | |||||
28 | d528d76d3242cced614fa11bb63f3d342e4e1d09 2.5.2 |
|
28 | d528d76d3242cced614fa11bb63f3d342e4e1d09 2.5.2 | |
29 | 1b631781ced34fbdeec032e7674bc4e131724699 2.6.0 |
|
29 | 1b631781ced34fbdeec032e7674bc4e131724699 2.6.0 | |
30 | 0f2ef17dc0de678ada279bf7eedf6c5585f1fd7a 2.6.1 |
|
30 | 0f2ef17dc0de678ada279bf7eedf6c5585f1fd7a 2.6.1 | |
|
31 | d53fc814a424d7fd90f23025c87b87baa164450e 2.7.0 |
@@ -1,5 +1,5 b'' | |||||
1 | from django.contrib import admin |
|
1 | from django.contrib import admin | |
2 | from boards.models import Post, Tag, Ban, Thread, KeyPair |
|
2 | from boards.models import Post, Tag, Ban, Thread, KeyPair, Banner | |
3 | from django.utils.translation import ugettext_lazy as _ |
|
3 | from django.utils.translation import ugettext_lazy as _ | |
4 |
|
4 | |||
5 |
|
5 | |||
@@ -64,3 +64,8 b' class BanAdmin(admin.ModelAdmin):' | |||||
64 | list_display = ('ip', 'can_read') |
|
64 | list_display = ('ip', 'can_read') | |
65 | list_filter = ('can_read',) |
|
65 | list_filter = ('can_read',) | |
66 | search_fields = ('ip',) |
|
66 | search_fields = ('ip',) | |
|
67 | ||||
|
68 | ||||
|
69 | @admin.register(Banner) | |||
|
70 | class BannerAdmin(admin.ModelAdmin): | |||
|
71 | list_display = ('title', 'text') |
@@ -51,11 +51,12 b' def user_and_ui_processor(request):' | |||||
51 | # This shows the moderator panel |
|
51 | # This shows the moderator panel | |
52 | context[CONTEXT_MODERATOR] = utils.is_moderator(request) |
|
52 | context[CONTEXT_MODERATOR] = utils.is_moderator(request) | |
53 |
|
53 | |||
54 |
context[CONTEXT_VERSION] = settings. |
|
54 | context[CONTEXT_VERSION] = settings.get('Version', 'Version') | |
55 |
context[CONTEXT_SITE_NAME] = settings. |
|
55 | context[CONTEXT_SITE_NAME] = settings.get('Version', 'SiteName') | |
56 |
|
56 | |||
57 | context[CONTEXT_IMAGE_VIEWER] = settings_manager.get_setting( |
|
57 | context[CONTEXT_IMAGE_VIEWER] = settings_manager.get_setting( | |
58 | SETTING_IMAGE_VIEWER, default=settings.DEFAULT_IMAGE_VIEWER) |
|
58 | SETTING_IMAGE_VIEWER, | |
|
59 | default=settings.get('View', 'DefaultImageViewer')) | |||
59 |
|
60 | |||
60 | get_notifications(context, request) |
|
61 | get_notifications(context, request) | |
61 |
|
62 |
@@ -172,11 +172,10 b' class PostForm(NeboardForm):' | |||||
172 | def clean_text(self): |
|
172 | def clean_text(self): | |
173 | text = self.cleaned_data['text'].strip() |
|
173 | text = self.cleaned_data['text'].strip() | |
174 | if text: |
|
174 | if text: | |
175 | if len(text) > board_settings.MAX_TEXT_LENGTH: |
|
175 | max_length = board_settings.get_int('Forms', 'MaxTextLength') | |
|
176 | if len(text) > max_length: | |||
176 | raise forms.ValidationError(_('Text must have less than %s ' |
|
177 | raise forms.ValidationError(_('Text must have less than %s ' | |
177 | 'characters') % |
|
178 | 'characters') % str(max_length)) | |
178 | str(board_settings |
|
|||
179 | .MAX_TEXT_LENGTH)) |
|
|||
180 | return text |
|
179 | return text | |
181 |
|
180 | |||
182 | def clean_image(self): |
|
181 | def clean_image(self): | |
@@ -256,7 +255,7 b' class PostForm(NeboardForm):' | |||||
256 |
|
255 | |||
257 | posting_delay = settings.POSTING_DELAY |
|
256 | posting_delay = settings.POSTING_DELAY | |
258 |
|
257 | |||
259 | if board_settings.LIMIT_POSTING_SPEED: |
|
258 | if board_settings.get_bool('Forms', 'LimitPostingSpeed'): | |
260 | now = time.time() |
|
259 | now = time.time() | |
261 |
|
260 | |||
262 | current_delay = 0 |
|
261 | current_delay = 0 | |
@@ -283,10 +282,11 b' class PostForm(NeboardForm):' | |||||
283 | self.session[LAST_POST_TIME] = now |
|
282 | self.session[LAST_POST_TIME] = now | |
284 |
|
283 | |||
285 | def validate_image_size(self, size: int): |
|
284 | def validate_image_size(self, size: int): | |
286 | if size > board_settings.MAX_IMAGE_SIZE: |
|
285 | max_size = board_settings.get_int('Forms', 'MaxImageSize') | |
|
286 | if size > max_size: | |||
287 | raise forms.ValidationError( |
|
287 | raise forms.ValidationError( | |
288 | _('Image must be less than %s bytes') |
|
288 | _('Image must be less than %s bytes') | |
289 |
% str( |
|
289 | % str(max_size)) | |
290 |
|
290 | |||
291 | def _get_image_from_url(self, url: str) -> SimpleUploadedFile: |
|
291 | def _get_image_from_url(self, url: str) -> SimpleUploadedFile: | |
292 | """ |
|
292 | """ |
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-0 |
|
10 | "POT-Creation-Date: 2015-05-07 13:10+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" | |
@@ -68,79 +68,79 b' msgstr "\xd0\x9f\xd0\xbe\xd0\xb8\xd1\x81\xd0\xba"' | |||||
68 | msgid "Please wait %s seconds before sending message" |
|
68 | msgid "Please wait %s seconds before sending message" | |
69 | msgstr "Пожалуйста подождите %s секунд перед отправкой сообщения" |
|
69 | msgstr "Пожалуйста подождите %s секунд перед отправкой сообщения" | |
70 |
|
70 | |||
71 |
#: forms.py:14 |
|
71 | #: forms.py:144 | |
72 | msgid "Image" |
|
72 | msgid "Image" | |
73 | msgstr "Изображение" |
|
73 | msgstr "Изображение" | |
74 |
|
74 | |||
75 |
#: forms.py:14 |
|
75 | #: forms.py:147 | |
76 | msgid "Image URL" |
|
76 | msgid "Image URL" | |
77 | msgstr "URL изображения" |
|
77 | msgstr "URL изображения" | |
78 |
|
78 | |||
79 |
#: forms.py:15 |
|
79 | #: forms.py:153 | |
80 | msgid "e-mail" |
|
80 | msgid "e-mail" | |
81 | msgstr "" |
|
81 | msgstr "" | |
82 |
|
82 | |||
83 |
#: forms.py:15 |
|
83 | #: forms.py:156 | |
84 | msgid "Additional threads" |
|
84 | msgid "Additional threads" | |
85 | msgstr "Дополнительные темы" |
|
85 | msgstr "Дополнительные темы" | |
86 |
|
86 | |||
87 |
#: forms.py:16 |
|
87 | #: forms.py:167 | |
88 | #, python-format |
|
88 | #, python-format | |
89 | msgid "Title must have less than %s characters" |
|
89 | msgid "Title must have less than %s characters" | |
90 | msgstr "Заголовок должен иметь меньше %s символов" |
|
90 | msgstr "Заголовок должен иметь меньше %s символов" | |
91 |
|
91 | |||
92 |
#: forms.py:17 |
|
92 | #: forms.py:176 | |
93 | #, python-format |
|
93 | #, python-format | |
94 | msgid "Text must have less than %s characters" |
|
94 | msgid "Text must have less than %s characters" | |
95 | msgstr "Текст должен быть короче %s символов" |
|
95 | msgstr "Текст должен быть короче %s символов" | |
96 |
|
96 | |||
97 |
#: forms.py:19 |
|
97 | #: forms.py:198 | |
98 | msgid "Invalid URL" |
|
98 | msgid "Invalid URL" | |
99 | msgstr "Неверный URL" |
|
99 | msgstr "Неверный URL" | |
100 |
|
100 | |||
101 |
#: forms.py:21 |
|
101 | #: forms.py:219 | |
102 | msgid "Invalid additional thread list" |
|
102 | msgid "Invalid additional thread list" | |
103 | msgstr "Неверный список дополнительных тем" |
|
103 | msgstr "Неверный список дополнительных тем" | |
104 |
|
104 | |||
105 |
#: forms.py:2 |
|
105 | #: forms.py:251 | |
106 | msgid "Either text or image must be entered." |
|
106 | msgid "Either text or image must be entered." | |
107 | msgstr "Текст или картинка должны быть введены." |
|
107 | msgstr "Текст или картинка должны быть введены." | |
108 |
|
108 | |||
109 |
#: forms.py:28 |
|
109 | #: forms.py:288 | |
110 | #, python-format |
|
110 | #, python-format | |
111 | msgid "Image must be less than %s bytes" |
|
111 | msgid "Image must be less than %s bytes" | |
112 | msgstr "Изображение должно быть менее %s байт" |
|
112 | msgstr "Изображение должно быть менее %s байт" | |
113 |
|
113 | |||
114 |
#: forms.py:33 |
|
114 | #: forms.py:335 templates/boards/posting_general.html:129 | |
115 | #: templates/boards/rss/post.html:10 templates/boards/tags.html:7 |
|
115 | #: templates/boards/rss/post.html:10 templates/boards/tags.html:7 | |
116 | msgid "Tags" |
|
116 | msgid "Tags" | |
117 | msgstr "Метки" |
|
117 | msgstr "Метки" | |
118 |
|
118 | |||
119 |
#: forms.py:34 |
|
119 | #: forms.py:342 | |
120 | msgid "Inappropriate characters in tags." |
|
120 | msgid "Inappropriate characters in tags." | |
121 | msgstr "Недопустимые символы в метках." |
|
121 | msgstr "Недопустимые символы в метках." | |
122 |
|
122 | |||
123 |
#: forms.py:35 |
|
123 | #: forms.py:356 | |
124 | msgid "Need at least one of the tags: " |
|
124 | msgid "Need at least one of the tags: " | |
125 | msgstr "Нужна хотя бы одна из меток: " |
|
125 | msgstr "Нужна хотя бы одна из меток: " | |
126 |
|
126 | |||
127 |
#: forms.py:36 |
|
127 | #: forms.py:369 | |
128 | msgid "Theme" |
|
128 | msgid "Theme" | |
129 | msgstr "Тема" |
|
129 | msgstr "Тема" | |
130 |
|
130 | |||
131 |
#: forms.py:3 |
|
131 | #: forms.py:370 | |
132 | msgid "Image view mode" |
|
132 | msgid "Image view mode" | |
133 | msgstr "Режим просмотра изображений" |
|
133 | msgstr "Режим просмотра изображений" | |
134 |
|
134 | |||
135 |
#: forms.py:3 |
|
135 | #: forms.py:371 | |
136 | msgid "User name" |
|
136 | msgid "User name" | |
137 | msgstr "Имя пользователя" |
|
137 | msgstr "Имя пользователя" | |
138 |
|
138 | |||
139 |
#: forms.py:37 |
|
139 | #: forms.py:372 | |
140 | msgid "Time zone" |
|
140 | msgid "Time zone" | |
141 | msgstr "Часовой пояс" |
|
141 | msgstr "Часовой пояс" | |
142 |
|
142 | |||
143 |
#: forms.py:37 |
|
143 | #: forms.py:378 | |
144 | msgid "Inappropriate characters." |
|
144 | msgid "Inappropriate characters." | |
145 | msgstr "Недопустимые символы." |
|
145 | msgstr "Недопустимые символы." | |
146 |
|
146 | |||
@@ -189,7 +189,7 b' msgstr "\xd0\xa3\xd0\xbf\xd1\x80\xd0\xb0\xd0\xb2\xd0\xbb\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 \xd0\xbc\xd0\xb5\xd1\x82\xd0\xba\xd0\xb0\xd0\xbc\xd0\xb8"' | |||||
189 | msgid "Notifications" |
|
189 | msgid "Notifications" | |
190 | msgstr "Уведомления" |
|
190 | msgstr "Уведомления" | |
191 |
|
191 | |||
192 |
#: templates/boards/base.html:52 templates/boards/settings.html: |
|
192 | #: templates/boards/base.html:52 templates/boards/settings.html:8 | |
193 | msgid "Settings" |
|
193 | msgid "Settings" | |
194 | msgstr "Настройки" |
|
194 | msgstr "Настройки" | |
195 |
|
195 | |||
@@ -207,12 +207,12 b' msgid "Up"' | |||||
207 | msgstr "Вверх" |
|
207 | msgstr "Вверх" | |
208 |
|
208 | |||
209 | #: templates/boards/notifications.html:17 |
|
209 | #: templates/boards/notifications.html:17 | |
210 |
#: templates/boards/posting_general.html: |
|
210 | #: templates/boards/posting_general.html:70 templates/search/search.html:26 | |
211 | msgid "Previous page" |
|
211 | msgid "Previous page" | |
212 | msgstr "Предыдущая страница" |
|
212 | msgstr "Предыдущая страница" | |
213 |
|
213 | |||
214 | #: templates/boards/notifications.html:27 |
|
214 | #: templates/boards/notifications.html:27 | |
215 |
#: templates/boards/posting_general.html:1 |
|
215 | #: templates/boards/posting_general.html:102 templates/search/search.html:37 | |
216 | msgid "Next page" |
|
216 | msgid "Next page" | |
217 | msgstr "Следующая страница" |
|
217 | msgstr "Следующая страница" | |
218 |
|
218 | |||
@@ -244,44 +244,49 b' msgstr "\xd1\x81\xd0\xbe\xd0\xbe\xd0\xb1\xd1\x89\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb9"' | |||||
244 | msgid "images" |
|
244 | msgid "images" | |
245 | msgstr "изображений" |
|
245 | msgstr "изображений" | |
246 |
|
246 | |||
247 |
#: templates/boards/posting_general.html: |
|
247 | #: templates/boards/posting_general.html:35 | |
|
248 | #| msgid "messages" | |||
|
249 | msgid "Related message" | |||
|
250 | msgstr "Связанное сообщение" | |||
|
251 | ||||
|
252 | #: templates/boards/posting_general.html:60 | |||
248 | msgid "Edit tag" |
|
253 | msgid "Edit tag" | |
249 | msgstr "Изменить метку" |
|
254 | msgstr "Изменить метку" | |
250 |
|
255 | |||
251 |
#: templates/boards/posting_general.html:6 |
|
256 | #: templates/boards/posting_general.html:63 | |
252 | #, python-format |
|
257 | #, python-format | |
253 | msgid "This tag has %(thread_count)s threads and %(post_count)s posts." |
|
258 | msgid "This tag has %(thread_count)s threads and %(post_count)s posts." | |
254 | msgstr "С этой меткой есть %(thread_count)s тем и %(post_count)s сообщений." |
|
259 | msgstr "С этой меткой есть %(thread_count)s тем и %(post_count)s сообщений." | |
255 |
|
260 | |||
256 |
#: templates/boards/posting_general.html: |
|
261 | #: templates/boards/posting_general.html:84 | |
257 | #, python-format |
|
262 | #, python-format | |
258 | msgid "Skipped %(count)s replies. Open thread to see all replies." |
|
263 | msgid "Skipped %(count)s replies. Open thread to see all replies." | |
259 | msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы." |
|
264 | msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы." | |
260 |
|
265 | |||
261 |
#: templates/boards/posting_general.html:1 |
|
266 | #: templates/boards/posting_general.html:107 | |
262 | msgid "No threads exist. Create the first one!" |
|
267 | msgid "No threads exist. Create the first one!" | |
263 | msgstr "Нет тем. Создайте первую!" |
|
268 | msgstr "Нет тем. Создайте первую!" | |
264 |
|
269 | |||
265 |
#: templates/boards/posting_general.html:1 |
|
270 | #: templates/boards/posting_general.html:113 | |
266 | msgid "Create new thread" |
|
271 | msgid "Create new thread" | |
267 | msgstr "Создать новую тему" |
|
272 | msgstr "Создать новую тему" | |
268 |
|
273 | |||
269 |
#: templates/boards/posting_general.html:1 |
|
274 | #: templates/boards/posting_general.html:118 templates/boards/preview.html:16 | |
270 | #: templates/boards/thread_normal.html:43 |
|
275 | #: templates/boards/thread_normal.html:43 | |
271 | msgid "Post" |
|
276 | msgid "Post" | |
272 | msgstr "Отправить" |
|
277 | msgstr "Отправить" | |
273 |
|
278 | |||
274 |
#: templates/boards/posting_general.html:13 |
|
279 | #: templates/boards/posting_general.html:123 | |
275 | msgid "Tags must be delimited by spaces. Text or image is required." |
|
280 | msgid "Tags must be delimited by spaces. Text or image is required." | |
276 | msgstr "" |
|
281 | msgstr "" | |
277 | "Метки должны быть разделены пробелами. Текст или изображение обязательны." |
|
282 | "Метки должны быть разделены пробелами. Текст или изображение обязательны." | |
278 |
|
283 | |||
279 |
#: templates/boards/posting_general.html:1 |
|
284 | #: templates/boards/posting_general.html:126 | |
280 | #: templates/boards/thread_normal.html:48 |
|
285 | #: templates/boards/thread_normal.html:48 | |
281 | msgid "Text syntax" |
|
286 | msgid "Text syntax" | |
282 | msgstr "Синтаксис текста" |
|
287 | msgstr "Синтаксис текста" | |
283 |
|
288 | |||
284 |
#: templates/boards/posting_general.html:1 |
|
289 | #: templates/boards/posting_general.html:143 | |
285 | msgid "Pages:" |
|
290 | msgid "Pages:" | |
286 | msgstr "Страницы: " |
|
291 | msgstr "Страницы: " | |
287 |
|
292 | |||
@@ -293,19 +298,19 b' msgstr "\xd0\x9f\xd1\x80\xd0\xb5\xd0\xb4\xd0\xbf\xd1\x80\xd0\xbe\xd1\x81\xd0\xbc\xd0\xbe\xd1\x82\xd1\x80"' | |||||
293 | msgid "Post image" |
|
298 | msgid "Post image" | |
294 | msgstr "Изображение сообщения" |
|
299 | msgstr "Изображение сообщения" | |
295 |
|
300 | |||
296 |
#: templates/boards/settings.html:1 |
|
301 | #: templates/boards/settings.html:16 | |
297 | msgid "You are moderator." |
|
302 | msgid "You are moderator." | |
298 | msgstr "Вы модератор." |
|
303 | msgstr "Вы модератор." | |
299 |
|
304 | |||
300 |
#: templates/boards/settings.html:2 |
|
305 | #: templates/boards/settings.html:20 | |
301 | msgid "Hidden tags:" |
|
306 | msgid "Hidden tags:" | |
302 | msgstr "Скрытые метки:" |
|
307 | msgstr "Скрытые метки:" | |
303 |
|
308 | |||
304 |
#: templates/boards/settings.html:2 |
|
309 | #: templates/boards/settings.html:28 | |
305 | msgid "No hidden tags." |
|
310 | msgid "No hidden tags." | |
306 | msgstr "Нет скрытых меток." |
|
311 | msgstr "Нет скрытых меток." | |
307 |
|
312 | |||
308 |
#: templates/boards/settings.html:3 |
|
313 | #: templates/boards/settings.html:37 | |
309 | msgid "Save" |
|
314 | msgid "Save" | |
310 | msgstr "Сохранить" |
|
315 | msgstr "Сохранить" | |
311 |
|
316 | |||
@@ -377,7 +382,7 b' msgstr "\xd0\x9d\xd0\xbe\xd1\x80\xd0\xbc\xd0\xb0\xd0\xbb\xd1\x8c\xd0\xbd\xd1\x8b\xd0\xb9 \xd1\x80\xd0\xb5\xd0\xb6\xd0\xb8\xd0\xbc"' | |||||
377 | msgid "Gallery mode" |
|
382 | msgid "Gallery mode" | |
378 | msgstr "Режим галереи" |
|
383 | msgstr "Режим галереи" | |
379 |
|
384 | |||
380 |
#: templates/boards/thread_gallery.html: |
|
385 | #: templates/boards/thread_gallery.html:41 | |
381 | msgid "No images." |
|
386 | msgid "No images." | |
382 | msgstr "Нет изображений." |
|
387 | msgstr "Нет изображений." | |
383 |
|
388 | |||
@@ -401,8 +406,3 b' msgstr "\xd0\x9e\xd0\xb1\xd0\xbd\xd0\xbe\xd0\xb2\xd0\xb8\xd1\x82\xd1\x8c"' | |||||
401 | msgid "Ok" |
|
406 | msgid "Ok" | |
402 | msgstr "Ок" |
|
407 | msgstr "Ок" | |
403 |
|
408 | |||
404 | #~ msgid "Wait %s seconds after last posting" |
|
|||
405 | #~ msgstr "Подождите %s секунд после последнего постинга" |
|
|||
406 |
|
||||
407 | #~ msgid "tag1 several_words_tag" |
|
|||
408 | #~ msgstr "метка1 метка_из_нескольких_слов" |
|
@@ -7,3 +7,4 b' from boards.models.thread import Thread' | |||||
7 | from boards.models.post import Post |
|
7 | from boards.models.post import Post | |
8 | from boards.models.tag import Tag |
|
8 | from boards.models.tag import Tag | |
9 | from boards.models.user import Ban |
|
9 | from boards.models.user import Ban | |
|
10 | from boards.models.banner import Banner |
1 | NO CONTENT: file renamed from boards/models/post.py to boards/models/post/__init__.py |
|
NO CONTENT: file renamed from boards/models/post.py to boards/models/post/__init__.py |
@@ -56,9 +56,13 b' class Tag(models.Model, Viewable):' | |||||
56 | def get_thread_count(self) -> int: |
|
56 | def get_thread_count(self) -> int: | |
57 | return self.get_threads().count() |
|
57 | return self.get_threads().count() | |
58 |
|
58 | |||
|
59 | # TODO Remove this and use get_absolute_url | |||
59 | def get_url(self): |
|
60 | def get_url(self): | |
60 | return reverse('tag', kwargs={'tag_name': self.name}) |
|
61 | return reverse('tag', kwargs={'tag_name': self.name}) | |
61 |
|
62 | |||
|
63 | def get_absolute_url(self): | |||
|
64 | return self.get_url() | |||
|
65 | ||||
62 | def get_threads(self): |
|
66 | def get_threads(self): | |
63 | return self.thread_set.order_by('-bump_time') |
|
67 | return self.thread_set.order_by('-bump_time') | |
64 |
|
68 | |||
@@ -67,7 +71,7 b' class Tag(models.Model, Viewable):' | |||||
67 |
|
71 | |||
68 | def get_view(self): |
|
72 | def get_view(self): | |
69 | link = '<a class="tag" href="{}">{}</a>'.format( |
|
73 | link = '<a class="tag" href="{}">{}</a>'.format( | |
70 | self.get_url(), self.name) |
|
74 | self.get_absolute_url(), self.name) | |
71 | if self.is_required(): |
|
75 | if self.is_required(): | |
72 | link = '<b>{}</b>'.format(link) |
|
76 | link = '<b>{}</b>'.format(link) | |
73 | return link |
|
77 | return link |
@@ -34,12 +34,13 b' class ThreadManager(models.Manager):' | |||||
34 | threads = Thread.objects.filter(archived=False).order_by('-bump_time') |
|
34 | threads = Thread.objects.filter(archived=False).order_by('-bump_time') | |
35 | thread_count = threads.count() |
|
35 | thread_count = threads.count() | |
36 |
|
36 | |||
37 | if thread_count > settings.MAX_THREAD_COUNT: |
|
37 | max_thread_count = settings.get_int('Messages', 'MaxThreadCount') | |
38 | num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT |
|
38 | if thread_count > max_thread_count: | |
|
39 | num_threads_to_delete = thread_count - max_thread_count | |||
39 | old_threads = threads[thread_count - num_threads_to_delete:] |
|
40 | old_threads = threads[thread_count - num_threads_to_delete:] | |
40 |
|
41 | |||
41 | for thread in old_threads: |
|
42 | for thread in old_threads: | |
42 | if settings.ARCHIVE_THREADS: |
|
43 | if settings.get_bool('Storage', 'ArchiveThreads'): | |
43 | self._archive_thread(thread) |
|
44 | self._archive_thread(thread) | |
44 | else: |
|
45 | else: | |
45 | thread.delete() |
|
46 | thread.delete() | |
@@ -55,7 +56,7 b' class ThreadManager(models.Manager):' | |||||
55 |
|
56 | |||
56 |
|
57 | |||
57 | def get_thread_max_posts(): |
|
58 | def get_thread_max_posts(): | |
58 | return settings.MAX_POSTS_PER_THREAD |
|
59 | return settings.get_int('Messages', 'MaxPostsPerThread') | |
59 |
|
60 | |||
60 |
|
61 | |||
61 | class Thread(models.Model): |
|
62 | class Thread(models.Model): | |
@@ -122,11 +123,13 b' class Thread(models.Model):' | |||||
122 | Gets several last replies, not including opening post |
|
123 | Gets several last replies, not including opening post | |
123 | """ |
|
124 | """ | |
124 |
|
125 | |||
125 | if settings.LAST_REPLIES_COUNT > 0: |
|
126 | last_replies_count = settings.get_int('View', 'LastRepliesCount') | |
|
127 | ||||
|
128 | if last_replies_count > 0: | |||
126 | reply_count = self.get_reply_count() |
|
129 | reply_count = self.get_reply_count() | |
127 |
|
130 | |||
128 | if reply_count > 0: |
|
131 | if reply_count > 0: | |
129 |
reply_count_to_show = min( |
|
132 | reply_count_to_show = min(last_replies_count, | |
130 | reply_count - 1) |
|
133 | reply_count - 1) | |
131 | replies = self.get_replies() |
|
134 | replies = self.get_replies() | |
132 | last_replies = replies[reply_count - reply_count_to_show:] |
|
135 | last_replies = replies[reply_count - reply_count_to_show:] | |
@@ -138,7 +141,7 b' class Thread(models.Model):' | |||||
138 | Gets number of posts between opening post and last replies. |
|
141 | Gets number of posts between opening post and last replies. | |
139 | """ |
|
142 | """ | |
140 | reply_count = self.get_reply_count() |
|
143 | reply_count = self.get_reply_count() | |
141 |
last_replies_count = min(settings. |
|
144 | last_replies_count = min(settings.get_int('View', 'LastRepliesCount'), | |
142 | reply_count - 1) |
|
145 | reply_count - 1) | |
143 | return reply_count - last_replies_count - 1 |
|
146 | return reply_count - last_replies_count - 1 | |
144 |
|
147 | |||
@@ -222,7 +225,7 b' class Thread(models.Model):' | |||||
222 | post.threads.update(last_edit_time=self.last_edit_time) |
|
225 | post.threads.update(last_edit_time=self.last_edit_time) | |
223 |
|
226 | |||
224 | def notify_clients(self): |
|
227 | def notify_clients(self): | |
225 | if not settings.WEBSOCKETS_ENABLED: |
|
228 | if not settings.get_bool('External', 'WebsocketsEnabled'): | |
226 | return |
|
229 | return | |
227 |
|
230 | |||
228 | client = Client() |
|
231 | client = Client() | |
@@ -232,3 +235,6 b' class Thread(models.Model):' | |||||
232 | WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST, |
|
235 | WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST, | |
233 | }) |
|
236 | }) | |
234 | client.send() |
|
237 | client.send() | |
|
238 | ||||
|
239 | def get_absolute_url(self): | |||
|
240 | return self.get_opening_post().get_absolute_url() |
@@ -10,7 +10,7 b' from boards import settings' | |||||
10 | # TODO Make tests for all of these |
|
10 | # TODO Make tests for all of these | |
11 | class AllThreadsFeed(Feed): |
|
11 | class AllThreadsFeed(Feed): | |
12 |
|
12 | |||
13 |
title = settings. |
|
13 | title = settings.get('Version', 'SiteName') + ' - All threads' | |
14 | link = '/' |
|
14 | link = '/' | |
15 | description_template = 'boards/rss/post.html' |
|
15 | description_template = 'boards/rss/post.html' | |
16 |
|
16 |
@@ -1,2 +1,18 b'' | |||||
1 | from boards.default_settings import * |
|
1 | import configparser | |
|
2 | ||||
|
3 | ||||
|
4 | config = configparser.ConfigParser() | |||
|
5 | config.read('boards/config/default_settings.ini') | |||
|
6 | config.read('boards/config/settings.ini') | |||
|
7 | ||||
2 |
|
8 | |||
|
9 | def get(section, name): | |||
|
10 | return config[section][name] | |||
|
11 | ||||
|
12 | ||||
|
13 | def get_int(section, name): | |||
|
14 | return int(get(section, name)) | |||
|
15 | ||||
|
16 | ||||
|
17 | def get_bool(section, name): | |||
|
18 | return get(section, name) == 'true' |
@@ -28,6 +28,8 b" var CLASS_POST = '.post'" | |||||
28 | var POST_ADDED = 0; |
|
28 | var POST_ADDED = 0; | |
29 | var POST_UPDATED = 1; |
|
29 | var POST_UPDATED = 1; | |
30 |
|
30 | |||
|
31 | var JS_AUTOUPDATE_PERIOD = 20000; | |||
|
32 | ||||
31 | var wsUser = ''; |
|
33 | var wsUser = ''; | |
32 |
|
34 | |||
33 | var unreadPosts = 0; |
|
35 | var unreadPosts = 0; | |
@@ -48,7 +50,7 b' function connectWebsocket() {' | |||||
48 | var wsHost = metapanel.getAttribute('data-ws-host'); |
|
50 | var wsHost = metapanel.getAttribute('data-ws-host'); | |
49 | var wsPort = metapanel.getAttribute('data-ws-port'); |
|
51 | var wsPort = metapanel.getAttribute('data-ws-port'); | |
50 |
|
52 | |||
51 | if (wsHost.length > 0 && wsPort.length > 0) |
|
53 | if (wsHost.length > 0 && wsPort.length > 0) { | |
52 | var centrifuge = new Centrifuge({ |
|
54 | var centrifuge = new Centrifuge({ | |
53 | "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket", |
|
55 | "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket", | |
54 | "project": metapanel.getAttribute('data-ws-project'), |
|
56 | "project": metapanel.getAttribute('data-ws-project'), | |
@@ -78,6 +80,9 b' function connectWebsocket() {' | |||||
78 | centrifuge.connect(); |
|
80 | centrifuge.connect(); | |
79 |
|
81 | |||
80 | return true; |
|
82 | return true; | |
|
83 | } else { | |||
|
84 | return false; | |||
|
85 | } | |||
81 | } |
|
86 | } | |
82 |
|
87 | |||
83 | /** |
|
88 | /** | |
@@ -193,8 +198,21 b' function isPageBottom() {' | |||||
193 | return scroll == 1 |
|
198 | return scroll == 1 | |
194 | } |
|
199 | } | |
195 |
|
200 | |||
|
201 | function enableJsUpdate() { | |||
|
202 | setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD); | |||
|
203 | return true; | |||
|
204 | } | |||
|
205 | ||||
196 | function initAutoupdate() { |
|
206 | function initAutoupdate() { | |
197 | return connectWebsocket(); |
|
207 | if (location.protocol === 'https:') { | |
|
208 | return enableJsUpdate(); | |||
|
209 | } else { | |||
|
210 | if (connectWebsocket()) { | |||
|
211 | return true; | |||
|
212 | } else { | |||
|
213 | return enableJsUpdate(); | |||
|
214 | } | |||
|
215 | } | |||
198 | } |
|
216 | } | |
199 |
|
217 | |||
200 | function getReplyCount() { |
|
218 | function getReplyCount() { |
@@ -28,6 +28,14 b'' | |||||
28 | {% get_current_language as LANGUAGE_CODE %} |
|
28 | {% get_current_language as LANGUAGE_CODE %} | |
29 | {% get_current_timezone as TIME_ZONE %} |
|
29 | {% get_current_timezone as TIME_ZONE %} | |
30 |
|
30 | |||
|
31 | {% for banner in banners %} | |||
|
32 | <div class="post"> | |||
|
33 | <div class="title">{{ banner.title }}</div> | |||
|
34 | <div>{{ banner.text }}</div> | |||
|
35 | <div>{% trans 'Related message' %}: <a href="{{ banner.post.get_url }}">>>{{ banner.post.id }}</a></div> | |||
|
36 | </div> | |||
|
37 | {% endfor %} | |||
|
38 | ||||
31 | {% if tag %} |
|
39 | {% if tag %} | |
32 | <div class="tag_info"> |
|
40 | <div class="tag_info"> | |
33 | <h2> |
|
41 | <h2> |
@@ -1,7 +1,6 b'' | |||||
1 | {% extends "boards/base.html" %} |
|
1 | {% extends "boards/base.html" %} | |
2 |
|
2 | |||
3 | {% load i18n %} |
|
3 | {% load i18n %} | |
4 | {% load humanize %} |
|
|||
5 | {% load tz %} |
|
4 | {% load tz %} | |
6 |
|
5 | |||
7 | {% block head %} |
|
6 | {% block head %} |
@@ -91,26 +91,26 b' class PostTests(TestCase):' | |||||
91 | def test_thread_max_count(self): |
|
91 | def test_thread_max_count(self): | |
92 | """Test deletion of old posts when the max thread count is reached""" |
|
92 | """Test deletion of old posts when the max thread count is reached""" | |
93 |
|
93 | |||
94 |
for i in range(settings. |
|
94 | for i in range(settings.get_int('Messages', 'MaxThreadCount') + 1): | |
95 | self._create_post() |
|
95 | self._create_post() | |
96 |
|
96 | |||
97 |
self.assertEqual(settings. |
|
97 | self.assertEqual(settings.get_int('Messages', 'MaxThreadCount'), | |
98 | len(Thread.objects.filter(archived=False))) |
|
98 | len(Thread.objects.filter(archived=False))) | |
99 |
|
99 | |||
100 | def test_pages(self): |
|
100 | def test_pages(self): | |
101 | """Test that the thread list is properly split into pages""" |
|
101 | """Test that the thread list is properly split into pages""" | |
102 |
|
102 | |||
103 |
for i in range(settings. |
|
103 | for i in range(settings.get_int('Messages', 'MaxThreadCount')): | |
104 | self._create_post() |
|
104 | self._create_post() | |
105 |
|
105 | |||
106 | all_threads = Thread.objects.filter(archived=False) |
|
106 | all_threads = Thread.objects.filter(archived=False) | |
107 |
|
107 | |||
108 | paginator = Paginator(Thread.objects.filter(archived=False), |
|
108 | paginator = Paginator(Thread.objects.filter(archived=False), | |
109 |
settings. |
|
109 | settings.get_int('View', 'ThreadsPerPage')) | |
110 | posts_in_second_page = paginator.page(2).object_list |
|
110 | posts_in_second_page = paginator.page(2).object_list | |
111 | first_post = posts_in_second_page[0] |
|
111 | first_post = posts_in_second_page[0] | |
112 |
|
112 | |||
113 |
self.assertEqual(all_threads[settings. |
|
113 | self.assertEqual(all_threads[settings.get_int('View', 'ThreadsPerPage')].id, | |
114 | first_post.id) |
|
114 | first_post.id) | |
115 |
|
115 | |||
116 | def test_reflinks(self): |
|
116 | def test_reflinks(self): |
@@ -11,7 +11,7 b' from boards import utils, settings' | |||||
11 | from boards.abstracts.paginator import get_paginator |
|
11 | from boards.abstracts.paginator import get_paginator | |
12 | from boards.abstracts.settingsmanager import get_settings_manager |
|
12 | from boards.abstracts.settingsmanager import get_settings_manager | |
13 | from boards.forms import ThreadForm, PlainErrorList |
|
13 | from boards.forms import ThreadForm, PlainErrorList | |
14 | from boards.models import Post, Thread, Ban, Tag, PostImage |
|
14 | from boards.models import Post, Thread, Ban, Tag, PostImage, Banner | |
15 | from boards.views.banned import BannedView |
|
15 | from boards.views.banned import BannedView | |
16 | from boards.views.base import BaseBoardView, CONTEXT_FORM |
|
16 | from boards.views.base import BaseBoardView, CONTEXT_FORM | |
17 | from boards.views.posting_mixin import PostMixin |
|
17 | from boards.views.posting_mixin import PostMixin | |
@@ -28,6 +28,7 b" TAG_DELIMITER = ' '" | |||||
28 | PARAMETER_CURRENT_PAGE = 'current_page' |
|
28 | 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 |
|
32 | |||
32 | PARAMETER_PREV_LINK = 'prev_page_link' |
|
33 | PARAMETER_PREV_LINK = 'prev_page_link' | |
33 | PARAMETER_NEXT_LINK = 'next_page_link' |
|
34 | PARAMETER_NEXT_LINK = 'next_page_link' | |
@@ -50,7 +51,7 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
50 |
|
51 | |||
51 | self.settings_manager = get_settings_manager(request) |
|
52 | self.settings_manager = get_settings_manager(request) | |
52 | paginator = get_paginator(self.get_threads(), |
|
53 | paginator = get_paginator(self.get_threads(), | |
53 |
settings. |
|
54 | settings.get_int('View', 'ThreadsPerPage')) | |
54 | paginator.current_page = int(page) |
|
55 | paginator.current_page = int(page) | |
55 |
|
56 | |||
56 | try: |
|
57 | try: | |
@@ -60,6 +61,7 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
60 |
|
61 | |||
61 | params[PARAMETER_THREADS] = threads |
|
62 | params[PARAMETER_THREADS] = threads | |
62 | params[CONTEXT_FORM] = form |
|
63 | params[CONTEXT_FORM] = form | |
|
64 | params[PARAMETER_BANNERS] = Banner.objects.order_by('-id').all() | |||
63 |
|
65 | |||
64 | self.get_page_context(paginator, params, page) |
|
66 | self.get_page_context(paginator, params, page) | |
65 |
|
67 |
@@ -32,7 +32,8 b' class SettingsView(BaseBoardView):' | |||||
32 | initial={ |
|
32 | initial={ | |
33 | FORM_THEME: selected_theme, |
|
33 | FORM_THEME: selected_theme, | |
34 | FORM_IMAGE_VIEWER: settings_manager.get_setting( |
|
34 | FORM_IMAGE_VIEWER: settings_manager.get_setting( | |
35 |
SETTING_IMAGE_VIEWER, |
|
35 | SETTING_IMAGE_VIEWER, | |
|
36 | default=settings.get('View', 'DefaultImageViewer')), | |||
36 | FORM_USERNAME: settings_manager.get_setting(SETTING_USERNAME), |
|
37 | FORM_USERNAME: settings_manager.get_setting(SETTING_USERNAME), | |
37 | FORM_TIMEZONE: request.session.get( |
|
38 | FORM_TIMEZONE: request.session.get( | |
38 | SESSION_TIMEZONE, timezone.get_current_timezone()), |
|
39 | SESSION_TIMEZONE, timezone.get_current_timezone()), |
@@ -51,7 +51,7 b' class ThreadView(BaseBoardView, PostMixi' | |||||
51 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) |
|
51 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) | |
52 | params[CONTEXT_THREAD] = thread_to_show |
|
52 | params[CONTEXT_THREAD] = thread_to_show | |
53 |
|
53 | |||
54 | if settings.WEBSOCKETS_ENABLED: |
|
54 | if settings.get_bool('External', 'WebsocketsEnabled'): | |
55 | token_time = format(timezone.now(), u'U') |
|
55 | token_time = format(timezone.now(), u'U') | |
56 |
|
56 | |||
57 | params[CONTEXT_WS_TIME] = token_time |
|
57 | params[CONTEXT_WS_TIME] = token_time |
@@ -144,7 +144,7 b' INSTALLED_APPS = (' | |||||
144 | 'django.contrib.admin', |
|
144 | 'django.contrib.admin', | |
145 | # Uncomment the next line to enable admin documentation: |
|
145 | # Uncomment the next line to enable admin documentation: | |
146 | # 'django.contrib.admindocs', |
|
146 | # 'django.contrib.admindocs', | |
147 | 'django.contrib.humanize', |
|
147 | #'django.contrib.humanize', | |
148 |
|
148 | |||
149 | 'debug_toolbar', |
|
149 | 'debug_toolbar', | |
150 |
|
150 |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now