Show More
@@ -0,0 +1,19 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', '0018_banner'), | |||
|
11 | ] | |||
|
12 | ||||
|
13 | operations = [ | |||
|
14 | migrations.AlterField( | |||
|
15 | model_name='post', | |||
|
16 | name='referenced_posts', | |||
|
17 | field=models.ManyToManyField(db_index=True, to='boards.Post', null=True, related_name='refposts', blank=True), | |||
|
18 | ), | |||
|
19 | ] |
@@ -0,0 +1,15 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', '0019_merge'), | |||
|
11 | ('boards', '0019_auto_20150519_1323'), | |||
|
12 | ] | |||
|
13 | ||||
|
14 | operations = [ | |||
|
15 | ] |
@@ -0,0 +1,19 b'' | |||||
|
1 | {% extends "boards/thread.html" %} | |||
|
2 | ||||
|
3 | {% load i18n %} | |||
|
4 | {% load static from staticfiles %} | |||
|
5 | {% load board %} | |||
|
6 | {% load tz %} | |||
|
7 | ||||
|
8 | {% block thread_content %} | |||
|
9 | {% get_current_language as LANGUAGE_CODE %} | |||
|
10 | {% get_current_timezone as TIME_ZONE %} | |||
|
11 | ||||
|
12 | <div class="thread"> | |||
|
13 | {% for post in thread.get_top_level_replies %} | |||
|
14 | {% post_view post moderator=moderator mode_tree=True %} | |||
|
15 | {% endfor %} | |||
|
16 | </div> | |||
|
17 | ||||
|
18 | <script src="{% static 'js/thread.js' %}"></script> | |||
|
19 | {% endblock %} |
@@ -0,0 +1,12 b'' | |||||
|
1 | from boards.views.thread import ThreadView | |||
|
2 | ||||
|
3 | TEMPLATE_TREE = 'boards/thread_tree.html' | |||
|
4 | ||||
|
5 | ||||
|
6 | class TreeThreadView(ThreadView): | |||
|
7 | ||||
|
8 | def get_template(self): | |||
|
9 | return TEMPLATE_TREE | |||
|
10 | ||||
|
11 | def get_mode(self): | |||
|
12 | return 'tree' |
@@ -29,3 +29,7 b' d528d76d3242cced614fa11bb63f3d342e4e1d09' | |||||
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 |
|
31 | d53fc814a424d7fd90f23025c87b87baa164450e 2.7.0 | |
|
32 | 836d8bb9fcd930b952b9a02029442c71c2441983 2.8.0 | |||
|
33 | dfb6c481b1a2c33705de9a9b5304bc924c46b202 2.8.1 | |||
|
34 | 4a5bec08ccfb47a27f9e98698f12dd5b7246623b 2.8.2 | |||
|
35 | 604935b98f5b5e4a5e903594f048046e1fbb3519 2.8.3 |
@@ -2,7 +2,7 b'' | |||||
2 |
|
2 | |||
3 | from django.core.paginator import Paginator |
|
3 | from django.core.paginator import Paginator | |
4 |
|
4 | |||
5 |
PAGINATOR_LOOKAROUND_SIZE = |
|
5 | PAGINATOR_LOOKAROUND_SIZE = 2 | |
6 |
|
6 | |||
7 |
|
7 | |||
8 | def get_paginator(*args, **kwargs): |
|
8 | def get_paginator(*args, **kwargs): | |
@@ -14,9 +14,48 b' class DividedPaginator(Paginator):' | |||||
14 | lookaround_size = PAGINATOR_LOOKAROUND_SIZE |
|
14 | lookaround_size = PAGINATOR_LOOKAROUND_SIZE | |
15 | current_page = 0 |
|
15 | current_page = 0 | |
16 |
|
16 | |||
17 |
def |
|
17 | def _left_range(self): | |
|
18 | return self.page_range[:self.lookaround_size] | |||
|
19 | ||||
|
20 | def _right_range(self): | |||
|
21 | pages = self.num_pages-self.lookaround_size | |||
|
22 | if pages <= 0: | |||
|
23 | return [] | |||
|
24 | else: | |||
|
25 | return self.page_range[pages:] | |||
|
26 | ||||
|
27 | def _center_range(self): | |||
18 | index = self.page_range.index(self.current_page) |
|
28 | index = self.page_range.index(self.current_page) | |
19 |
|
29 | |||
20 |
start = max( |
|
30 | start = max(self.lookaround_size, index - self.lookaround_size) | |
21 |
end = min( |
|
31 | end = min(self.num_pages - self.lookaround_size, index + self.lookaround_size + 1) | |
22 | return self.page_range[start:end] No newline at end of file |
|
32 | return self.page_range[start:end] | |
|
33 | ||||
|
34 | def get_divided_range(self): | |||
|
35 | dr = list() | |||
|
36 | ||||
|
37 | dr += self._left_range() | |||
|
38 | dr += self._center_range() | |||
|
39 | dr += self._right_range() | |||
|
40 | ||||
|
41 | # Remove duplicates | |||
|
42 | dr = list(set(dr)) | |||
|
43 | dr.sort() | |||
|
44 | ||||
|
45 | return dr | |||
|
46 | ||||
|
47 | def get_dividers(self): | |||
|
48 | dividers = [] | |||
|
49 | ||||
|
50 | prev_page = 1 | |||
|
51 | for page in self.get_divided_range(): | |||
|
52 | if page - prev_page > 1: | |||
|
53 | dividers.append(page) | |||
|
54 | ||||
|
55 | # There can be no more than 2 dividers, so don't bother going | |||
|
56 | # further | |||
|
57 | if len(dividers) > 2: | |||
|
58 | break | |||
|
59 | prev_page = page | |||
|
60 | ||||
|
61 | return dividers |
@@ -71,7 +71,7 b' class SettingsManager:' | |||||
71 | tag_names = self.get_setting(SETTING_FAVORITE_TAGS) |
|
71 | tag_names = self.get_setting(SETTING_FAVORITE_TAGS) | |
72 | tags = [] |
|
72 | tags = [] | |
73 | if tag_names: |
|
73 | if tag_names: | |
74 | tags = Tag.objects.filter(name__in=tag_names) |
|
74 | tags = list(Tag.objects.filter(name__in=tag_names)) | |
75 | return tags |
|
75 | return tags | |
76 |
|
76 | |||
77 | def add_fav_tag(self, tag): |
|
77 | def add_fav_tag(self, tag): | |
@@ -95,9 +95,7 b' class SettingsManager:' | |||||
95 | tag_names = self.get_setting(SETTING_HIDDEN_TAGS) |
|
95 | tag_names = self.get_setting(SETTING_HIDDEN_TAGS) | |
96 | tags = [] |
|
96 | tags = [] | |
97 | if tag_names: |
|
97 | if tag_names: | |
98 | for tag_name in tag_names: |
|
98 | tags = list(Tag.objects.filter(name__in=tag_names)) | |
99 | tag = get_object_or_404(Tag, name=tag_name) |
|
|||
100 | tags.append(tag) |
|
|||
101 |
|
99 | |||
102 | return tags |
|
100 | return tags | |
103 |
|
101 |
@@ -1,6 +1,6 b'' | |||||
1 | [Version] |
|
1 | [Version] | |
2 |
Version = 2. |
|
2 | Version = 2.8.3 Charlie | |
3 | SiteName = Neboard |
|
3 | SiteName = Neboard DEV | |
4 |
|
4 | |||
5 | [Cache] |
|
5 | [Cache] | |
6 | # Timeout for caching, if cache is used |
|
6 | # Timeout for caching, if cache is used |
@@ -15,9 +15,12 b' from boards.models import Tag, Post' | |||||
15 | from neboard import settings |
|
15 | from neboard import settings | |
16 | import boards.settings as board_settings |
|
16 | import boards.settings as board_settings | |
17 |
|
17 | |||
|
18 | HEADER_CONTENT_LENGTH = 'content-length' | |||
|
19 | HEADER_CONTENT_TYPE = 'content-type' | |||
18 |
|
20 | |||
19 | CONTENT_TYPE_IMAGE = ( |
|
21 | CONTENT_TYPE_IMAGE = ( | |
20 | 'image/jpeg', |
|
22 | 'image/jpeg', | |
|
23 | 'image/jpg', | |||
21 | 'image/png', |
|
24 | 'image/png', | |
22 | 'image/gif', |
|
25 | 'image/gif', | |
23 | 'image/bmp', |
|
26 | 'image/bmp', | |
@@ -298,9 +301,9 b' class PostForm(NeboardForm):' | |||||
298 | try: |
|
301 | try: | |
299 | # Verify content headers |
|
302 | # Verify content headers | |
300 | response_head = requests.head(url, verify=False) |
|
303 | response_head = requests.head(url, verify=False) | |
301 |
content_type = response_head.headers[ |
|
304 | content_type = response_head.headers[HEADER_CONTENT_TYPE].split(';')[0] | |
302 | if content_type in CONTENT_TYPE_IMAGE: |
|
305 | if content_type in CONTENT_TYPE_IMAGE: | |
303 |
length_header = response_head.headers.get( |
|
306 | length_header = response_head.headers.get(HEADER_CONTENT_LENGTH) | |
304 | if length_header: |
|
307 | if length_header: | |
305 | length = int(length_header) |
|
308 | length = int(length_header) | |
306 | self.validate_image_size(length) |
|
309 | self.validate_image_size(length) |
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-05-1 |
|
10 | "POT-Creation-Date: 2015-05-19 17:51+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" | |
@@ -189,7 +189,7 b' msgid "Create new thread"' | |||||
189 | msgstr "Создать новую тему" |
|
189 | msgstr "Создать новую тему" | |
190 |
|
190 | |||
191 | #: templates/boards/all_threads.html:118 templates/boards/preview.html:16 |
|
191 | #: templates/boards/all_threads.html:118 templates/boards/preview.html:16 | |
192 |
#: templates/boards/thread_normal.html: |
|
192 | #: templates/boards/thread_normal.html:38 | |
193 | msgid "Post" |
|
193 | msgid "Post" | |
194 | msgstr "Отправить" |
|
194 | msgstr "Отправить" | |
195 |
|
195 | |||
@@ -199,7 +199,7 b' msgstr ""' | |||||
199 | "Метки должны быть разделены пробелами. Текст или изображение обязательны." |
|
199 | "Метки должны быть разделены пробелами. Текст или изображение обязательны." | |
200 |
|
200 | |||
201 | #: templates/boards/all_threads.html:126 |
|
201 | #: templates/boards/all_threads.html:126 | |
202 |
#: templates/boards/thread_normal.html:4 |
|
202 | #: templates/boards/thread_normal.html:43 | |
203 | msgid "Text syntax" |
|
203 | msgid "Text syntax" | |
204 | msgstr "Синтаксис текста" |
|
204 | msgstr "Синтаксис текста" | |
205 |
|
205 | |||
@@ -244,7 +244,6 b' msgid "tags"' | |||||
244 | msgstr "метки" |
|
244 | msgstr "метки" | |
245 |
|
245 | |||
246 | #: templates/boards/base.html:40 |
|
246 | #: templates/boards/base.html:40 | |
247 | #| msgid "Search" |
|
|||
248 | msgid "search" |
|
247 | msgid "search" | |
249 | msgstr "поиск" |
|
248 | msgstr "поиск" | |
250 |
|
249 | |||
@@ -298,15 +297,15 b' msgstr "\xd0\x98\xd0\xb7\xd0\xbc\xd0\xb5\xd0\xbd\xd0\xb8\xd1\x82\xd1\x8c"' | |||||
298 | msgid "Edit thread" |
|
297 | msgid "Edit thread" | |
299 | msgstr "Изменить тему" |
|
298 | msgstr "Изменить тему" | |
300 |
|
299 | |||
301 |
#: templates/boards/post.html: |
|
300 | #: templates/boards/post.html:84 | |
302 | msgid "Replies" |
|
301 | msgid "Replies" | |
303 | msgstr "Ответы" |
|
302 | msgstr "Ответы" | |
304 |
|
303 | |||
305 |
#: templates/boards/post.html: |
|
304 | #: templates/boards/post.html:97 templates/boards/thread.html:37 | |
306 | msgid "messages" |
|
305 | msgid "messages" | |
307 | msgstr "сообщений" |
|
306 | msgstr "сообщений" | |
308 |
|
307 | |||
309 |
#: templates/boards/post.html:9 |
|
308 | #: templates/boards/post.html:98 templates/boards/thread.html:38 | |
310 | msgid "images" |
|
309 | msgid "images" | |
311 | msgstr "изображений" |
|
310 | msgstr "изображений" | |
312 |
|
311 | |||
@@ -388,37 +387,42 b' msgstr "\xd0\x9c\xd0\xb5\xd1\x82\xd0\xba\xd0\xb8 \xd0\xbd\xd0\xb5 \xd0\xbd\xd0\xb0\xd0\xb9\xd0\xb4\xd0\xb5\xd0\xbd\xd1\x8b."' | |||||
388 | msgid "All tags" |
|
387 | msgid "All tags" | |
389 | msgstr "Все метки" |
|
388 | msgstr "Все метки" | |
390 |
|
389 | |||
391 |
#: templates/boards/thread.html: |
|
390 | #: templates/boards/thread.html:15 | |
|
391 | #| msgid "Normal mode" | |||
|
392 | msgid "Normal" | |||
|
393 | msgstr "Нормальный" | |||
|
394 | ||||
|
395 | #: templates/boards/thread.html:16 | |||
|
396 | #| msgid "Gallery mode" | |||
|
397 | msgid "Gallery" | |||
|
398 | msgstr "Галерея" | |||
|
399 | ||||
|
400 | #: templates/boards/thread.html:17 | |||
|
401 | #| msgid "Tree mode" | |||
|
402 | msgid "Tree" | |||
|
403 | msgstr "Дерево" | |||
|
404 | ||||
|
405 | #: templates/boards/thread.html:39 | |||
392 | msgid "Last update: " |
|
406 | msgid "Last update: " | |
393 | msgstr "Последнее обновление: " |
|
407 | msgstr "Последнее обновление: " | |
394 |
|
408 | |||
395 |
#: templates/boards/thread_gallery.html: |
|
409 | #: templates/boards/thread_gallery.html:36 | |
396 | #: templates/boards/thread_normal.html:13 |
|
|||
397 | msgid "Normal mode" |
|
|||
398 | msgstr "Нормальный режим" |
|
|||
399 |
|
||||
400 | #: templates/boards/thread_gallery.html:20 |
|
|||
401 | #: templates/boards/thread_normal.html:14 |
|
|||
402 | msgid "Gallery mode" |
|
|||
403 | msgstr "Режим галереи" |
|
|||
404 |
|
||||
405 | #: templates/boards/thread_gallery.html:41 |
|
|||
406 | msgid "No images." |
|
410 | msgid "No images." | |
407 | msgstr "Нет изображений." |
|
411 | msgstr "Нет изображений." | |
408 |
|
412 | |||
409 |
#: templates/boards/thread_normal.html: |
|
413 | #: templates/boards/thread_normal.html:17 | |
410 | msgid "posts to bumplimit" |
|
414 | msgid "posts to bumplimit" | |
411 | msgstr "сообщений до бамплимита" |
|
415 | msgstr "сообщений до бамплимита" | |
412 |
|
416 | |||
413 |
#: templates/boards/thread_normal.html:3 |
|
417 | #: templates/boards/thread_normal.html:31 | |
414 | msgid "Reply to thread" |
|
418 | msgid "Reply to thread" | |
415 | msgstr "Ответить в тему" |
|
419 | msgstr "Ответить в тему" | |
416 |
|
420 | |||
417 |
#: templates/boards/thread_normal.html:4 |
|
421 | #: templates/boards/thread_normal.html:44 | |
418 | msgid "Close form" |
|
422 | msgid "Close form" | |
419 | msgstr "Закрыть форму" |
|
423 | msgstr "Закрыть форму" | |
420 |
|
424 | |||
421 |
#: templates/boards/thread_normal.html: |
|
425 | #: templates/boards/thread_normal.html:58 | |
422 | msgid "Update" |
|
426 | msgid "Update" | |
423 | msgstr "Обновить" |
|
427 | msgstr "Обновить" | |
424 |
|
428 |
@@ -17,6 +17,5 b' class Command(BaseCommand):' | |||||
17 | primary=first_key) |
|
17 | primary=first_key) | |
18 | print(key) |
|
18 | print(key) | |
19 |
|
19 | |||
20 | if first_key: |
|
20 | for post in Post.objects.filter(global_id=None): | |
21 |
|
|
21 | post.set_global_id() | |
22 | post.set_global_id() No newline at end of file |
|
@@ -160,6 +160,17 b' def render_notification(tag_name, value,' | |||||
160 | reverse('notifications', kwargs={'username': username}), username) |
|
160 | reverse('notifications', kwargs={'username': username}), username) | |
161 |
|
161 | |||
162 |
|
162 | |||
|
163 | def render_tag(tag_name, value, options, parent, context): | |||
|
164 | tag_name = value.lower() | |||
|
165 | ||||
|
166 | try: | |||
|
167 | url = boards.models.Tag.objects.get(name=tag_name).get_view() | |||
|
168 | except ObjectDoesNotExist: | |||
|
169 | url = tag_name | |||
|
170 | ||||
|
171 | return url | |||
|
172 | ||||
|
173 | ||||
163 | formatters = [ |
|
174 | formatters = [ | |
164 | QuotePattern, |
|
175 | QuotePattern, | |
165 | SpoilerPattern, |
|
176 | SpoilerPattern, | |
@@ -188,6 +199,7 b' class Parser:' | |||||
188 | self.parser.add_formatter('post', render_reflink, strip=True) |
|
199 | self.parser.add_formatter('post', render_reflink, strip=True) | |
189 | self.parser.add_formatter('quote', render_quote, strip=True) |
|
200 | self.parser.add_formatter('quote', render_quote, strip=True) | |
190 | self.parser.add_formatter('user', render_notification, strip=True) |
|
201 | self.parser.add_formatter('user', render_notification, strip=True) | |
|
202 | self.parser.add_formatter('tag', render_tag, strip=True) | |||
191 | self.parser.add_simple_formatter( |
|
203 | self.parser.add_simple_formatter( | |
192 | 'comment', '<span class="comment">//%(value)s</span>') |
|
204 | 'comment', '<span class="comment">//%(value)s</span>') | |
193 | self.parser.add_simple_formatter( |
|
205 | self.parser.add_simple_formatter( |
@@ -7,7 +7,7 b' import uuid' | |||||
7 | from django.core.exceptions import ObjectDoesNotExist |
|
7 | from django.core.exceptions import ObjectDoesNotExist | |
8 | from django.core.urlresolvers import reverse |
|
8 | from django.core.urlresolvers import reverse | |
9 | from django.db import models, transaction |
|
9 | from django.db import models, transaction | |
10 | from django.db.models import TextField |
|
10 | from django.db.models import TextField, QuerySet | |
11 | from django.template.loader import render_to_string |
|
11 | from django.template.loader import render_to_string | |
12 | from django.utils import timezone |
|
12 | from django.utils import timezone | |
13 |
|
13 | |||
@@ -44,7 +44,6 b" REGEX_GLOBAL_REPLY = re.compile(r'\\[post" | |||||
44 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') |
|
44 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') | |
45 | REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]') |
|
45 | REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]') | |
46 |
|
46 | |||
47 |
|
||||
48 | PARAMETER_TRUNCATED = 'truncated' |
|
47 | PARAMETER_TRUNCATED = 'truncated' | |
49 | PARAMETER_TAG = 'tag' |
|
48 | PARAMETER_TAG = 'tag' | |
50 | PARAMETER_OFFSET = 'offset' |
|
49 | PARAMETER_OFFSET = 'offset' | |
@@ -59,7 +58,14 b" PARAMETER_NEED_OPEN_LINK = 'need_open_li" | |||||
59 | PARAMETER_REPLY_LINK = 'reply_link' |
|
58 | PARAMETER_REPLY_LINK = 'reply_link' | |
60 | PARAMETER_NEED_OP_DATA = 'need_op_data' |
|
59 | PARAMETER_NEED_OP_DATA = 'need_op_data' | |
61 |
|
60 | |||
62 | DIFF_TYPE_HTML = 'html' |
|
61 | POST_VIEW_PARAMS = ( | |
|
62 | 'need_op_data', | |||
|
63 | 'reply_link', | |||
|
64 | 'moderator', | |||
|
65 | 'need_open_link', | |||
|
66 | 'truncated', | |||
|
67 | 'mode_tree', | |||
|
68 | ) | |||
63 |
|
69 | |||
64 | REFMAP_STR = '<a href="{}">>>{}</a>' |
|
70 | REFMAP_STR = '<a href="{}">>>{}</a>' | |
65 |
|
71 | |||
@@ -67,7 +73,7 b' REFMAP_STR = \'<a href="{}">>>{}</a' | |||||
67 | class PostManager(models.Manager): |
|
73 | class PostManager(models.Manager): | |
68 | @transaction.atomic |
|
74 | @transaction.atomic | |
69 | def create_post(self, title: str, text: str, image=None, thread=None, |
|
75 | def create_post(self, title: str, text: str, image=None, thread=None, | |
70 |
ip=NO_IP, tags: list=None, |
|
76 | ip=NO_IP, tags: list=None, opening_posts: list=None): | |
71 | """ |
|
77 | """ | |
72 | Creates new post |
|
78 | Creates new post | |
73 | """ |
|
79 | """ | |
@@ -80,13 +86,14 b' class PostManager(models.Manager):' | |||||
80 |
|
86 | |||
81 | if not tags: |
|
87 | if not tags: | |
82 | tags = [] |
|
88 | tags = [] | |
83 |
if not |
|
89 | if not opening_posts: | |
84 |
|
|
90 | opening_posts = [] | |
85 |
|
91 | |||
86 | posting_time = timezone.now() |
|
92 | posting_time = timezone.now() | |
87 | if not thread: |
|
93 | if not thread: | |
88 | thread = boards.models.thread.Thread.objects.create( |
|
94 | thread = boards.models.thread.Thread.objects.create( | |
89 | bump_time=posting_time, last_edit_time=posting_time) |
|
95 | bump_time=posting_time, last_edit_time=posting_time) | |
|
96 | list(map(thread.tags.add, tags)) | |||
90 | new_thread = True |
|
97 | new_thread = True | |
91 | else: |
|
98 | else: | |
92 | new_thread = False |
|
99 | new_thread = False | |
@@ -110,8 +117,6 b' class PostManager(models.Manager):' | |||||
110 | if image: |
|
117 | if image: | |
111 | post.images.add(PostImage.objects.create_with_hash(image)) |
|
118 | post.images.add(PostImage.objects.create_with_hash(image)) | |
112 |
|
119 | |||
113 | list(map(thread.add_tag, tags)) |
|
|||
114 |
|
||||
115 | if new_thread: |
|
120 | if new_thread: | |
116 | boards.models.thread.Thread.objects.process_oldest_threads() |
|
121 | boards.models.thread.Thread.objects.process_oldest_threads() | |
117 | else: |
|
122 | else: | |
@@ -119,8 +124,9 b' class PostManager(models.Manager):' | |||||
119 | thread.bump() |
|
124 | thread.bump() | |
120 | thread.save() |
|
125 | thread.save() | |
121 |
|
126 | |||
|
127 | post.build_url() | |||
122 | post.connect_replies() |
|
128 | post.connect_replies() | |
123 |
post.connect_threads( |
|
129 | post.connect_threads(opening_posts) | |
124 | post.connect_notifications() |
|
130 | post.connect_notifications() | |
125 |
|
131 | |||
126 | post.build_url() |
|
132 | post.build_url() | |
@@ -183,7 +189,7 b' class Post(models.Model, Viewable):' | |||||
183 |
|
189 | |||
184 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, |
|
190 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, | |
185 | null=True, |
|
191 | null=True, | |
186 |
blank=True, related_name='r |
|
192 | blank=True, related_name='refposts', | |
187 | db_index=True) |
|
193 | db_index=True) | |
188 | refmap = models.TextField(null=True, blank=True) |
|
194 | refmap = models.TextField(null=True, blank=True) | |
189 | threads = models.ManyToManyField('Thread', db_index=True) |
|
195 | threads = models.ManyToManyField('Thread', db_index=True) | |
@@ -202,6 +208,11 b' class Post(models.Model, Viewable):' | |||||
202 | def __str__(self): |
|
208 | def __str__(self): | |
203 | return 'P#{}/{}'.format(self.id, self.title) |
|
209 | return 'P#{}/{}'.format(self.id, self.title) | |
204 |
|
210 | |||
|
211 | def get_referenced_posts(self): | |||
|
212 | threads = self.get_threads().all() | |||
|
213 | return self.referenced_posts.filter(threads__in=threads)\ | |||
|
214 | .order_by('pub_time').distinct().all() | |||
|
215 | ||||
205 | def get_title(self) -> str: |
|
216 | def get_title(self) -> str: | |
206 | """ |
|
217 | """ | |
207 | Gets original post title or part of its text. |
|
218 | Gets original post title or part of its text. | |
@@ -235,20 +246,26 b' class Post(models.Model, Viewable):' | |||||
235 | return self.get_thread().get_opening_post_id() == self.id |
|
246 | return self.get_thread().get_opening_post_id() == self.id | |
236 |
|
247 | |||
237 | def get_absolute_url(self): |
|
248 | def get_absolute_url(self): | |
238 |
|
|
249 | if self.url: | |
|
250 | return self.url | |||
|
251 | else: | |||
|
252 | opening_id = self.get_thread().get_opening_post_id() | |||
|
253 | post_url = reverse('thread', kwargs={'post_id': opening_id}) | |||
|
254 | if self.id != opening_id: | |||
|
255 | post_url += '#' + str(self.id) | |||
|
256 | return post_url | |||
239 |
|
257 | |||
240 | def get_thread(self): |
|
258 | def get_thread(self): | |
241 | return self.thread |
|
259 | return self.thread | |
242 |
|
260 | |||
243 |
def get_threads(self) -> |
|
261 | def get_threads(self) -> QuerySet: | |
244 | """ |
|
262 | """ | |
245 | Gets post's thread. |
|
263 | Gets post's thread. | |
246 | """ |
|
264 | """ | |
247 |
|
265 | |||
248 | return self.threads |
|
266 | return self.threads | |
249 |
|
267 | |||
250 | def get_view(self, moderator=False, need_open_link=False, |
|
268 | def get_view(self, *args, **kwargs) -> str: | |
251 | truncated=False, reply_link=False, *args, **kwargs) -> str: |
|
|||
252 | """ |
|
269 | """ | |
253 | Renders post's HTML view. Some of the post params can be passed over |
|
270 | Renders post's HTML view. Some of the post params can be passed over | |
254 | kwargs for the means of caching (if we view the thread, some params |
|
271 | kwargs for the means of caching (if we view the thread, some params | |
@@ -269,19 +286,21 b' class Post(models.Model, Viewable):' | |||||
269 | elif not thread.can_bump(): |
|
286 | elif not thread.can_bump(): | |
270 | css_class += ' dead_post' |
|
287 | css_class += ' dead_post' | |
271 |
|
288 | |||
272 | return render_to_string('boards/post.html', { |
|
289 | params = dict() | |
|
290 | for param in POST_VIEW_PARAMS: | |||
|
291 | if param in kwargs: | |||
|
292 | params[param] = kwargs[param] | |||
|
293 | ||||
|
294 | params.update({ | |||
273 | PARAMETER_POST: self, |
|
295 | PARAMETER_POST: self, | |
274 | PARAMETER_MODERATOR: moderator, |
|
|||
275 | PARAMETER_IS_OPENING: is_opening, |
|
296 | PARAMETER_IS_OPENING: is_opening, | |
276 | PARAMETER_THREAD: thread, |
|
297 | PARAMETER_THREAD: thread, | |
277 | PARAMETER_CSS_CLASS: css_class, |
|
298 | PARAMETER_CSS_CLASS: css_class, | |
278 | PARAMETER_NEED_OPEN_LINK: need_open_link, |
|
|||
279 | PARAMETER_TRUNCATED: truncated, |
|
|||
280 | PARAMETER_OP_ID: opening_post_id, |
|
299 | PARAMETER_OP_ID: opening_post_id, | |
281 | PARAMETER_REPLY_LINK: reply_link, |
|
|||
282 | PARAMETER_NEED_OP_DATA: kwargs.get(PARAMETER_NEED_OP_DATA) |
|
|||
283 | }) |
|
300 | }) | |
284 |
|
301 | |||
|
302 | return render_to_string('boards/post.html', params) | |||
|
303 | ||||
285 | def get_search_view(self, *args, **kwargs): |
|
304 | def get_search_view(self, *args, **kwargs): | |
286 | return self.get_view(need_op_data=True, *args, **kwargs) |
|
305 | return self.get_view(need_op_data=True, *args, **kwargs) | |
287 |
|
306 | |||
@@ -399,12 +418,7 b' class Post(models.Model, Viewable):' | |||||
399 | pass |
|
418 | pass | |
400 |
|
419 | |||
401 | def build_url(self): |
|
420 | def build_url(self): | |
402 |
|
|
421 | self.url = self.get_absolute_url() | |
403 | opening_id = thread.get_opening_post_id() |
|
|||
404 | post_url = reverse('thread', kwargs={'post_id': opening_id}) |
|
|||
405 | if self.id != opening_id: |
|
|||
406 | post_url += '#' + str(self.id) |
|
|||
407 | self.url = post_url |
|
|||
408 | self.save(update_fields=['url']) |
|
422 | self.save(update_fields=['url']) | |
409 |
|
423 | |||
410 | def save(self, force_insert=False, force_update=False, using=None, |
|
424 | def save(self, force_insert=False, force_update=False, using=None, | |
@@ -417,11 +431,9 b' class Post(models.Model, Viewable):' | |||||
417 |
|
431 | |||
418 | if self.id: |
|
432 | if self.id: | |
419 | for thread in self.get_threads().all(): |
|
433 | for thread in self.get_threads().all(): | |
420 | if thread.can_bump(): |
|
|||
421 | thread.update_bump_status(exclude_posts=[self]) |
|
|||
422 | thread.last_edit_time = self.last_edit_time |
|
434 | thread.last_edit_time = self.last_edit_time | |
423 |
|
435 | |||
424 |
thread.save(update_fields=['last_edit_time' |
|
436 | thread.save(update_fields=['last_edit_time']) | |
425 |
|
437 | |||
426 | super().save(force_insert, force_update, using, update_fields) |
|
438 | super().save(force_insert, force_update, using, update_fields) | |
427 |
|
439 | |||
@@ -466,11 +478,6 b' class Post(models.Model, Viewable):' | |||||
466 | pass |
|
478 | pass | |
467 |
|
479 | |||
468 | def connect_threads(self, opening_posts): |
|
480 | def connect_threads(self, opening_posts): | |
469 | """ |
|
|||
470 | If the referenced post is an OP in another thread, |
|
|||
471 | make this post multi-thread. |
|
|||
472 | """ |
|
|||
473 |
|
||||
474 | for opening_post in opening_posts: |
|
481 | for opening_post in opening_posts: | |
475 | threads = opening_post.get_threads().all() |
|
482 | threads = opening_post.get_threads().all() | |
476 | for thread in threads: |
|
483 | for thread in threads: | |
@@ -479,5 +486,4 b' class Post(models.Model, Viewable):' | |||||
479 |
|
486 | |||
480 | thread.last_edit_time = self.last_edit_time |
|
487 | thread.last_edit_time = self.last_edit_time | |
481 | thread.save(update_fields=['last_edit_time', 'bumpable']) |
|
488 | thread.save(update_fields=['last_edit_time', 'bumpable']) | |
482 |
|
489 | self.threads.add(opening_post.get_thread()) | ||
483 | self.threads.add(thread) |
|
@@ -30,6 +30,7 b" ATTR_MIMETYPE = 'mimetype'" | |||||
30 | STATUS_SUCCESS = 'success' |
|
30 | STATUS_SUCCESS = 'success' | |
31 |
|
31 | |||
32 |
|
32 | |||
|
33 | # TODO Make this fully static | |||
33 | class SyncManager: |
|
34 | class SyncManager: | |
34 | def generate_response_get(self, model_list: list): |
|
35 | def generate_response_get(self, model_list: list): | |
35 | response = et.Element(TAG_RESPONSE) |
|
36 | response = et.Element(TAG_RESPONSE) |
@@ -1,7 +1,7 b'' | |||||
1 | import logging |
|
1 | import logging | |
2 | from adjacent import Client |
|
2 | from adjacent import Client | |
3 |
|
3 | |||
4 | from django.db.models import Count, Sum |
|
4 | from django.db.models import Count, Sum, QuerySet | |
5 | from django.utils import timezone |
|
5 | from django.utils import timezone | |
6 | from django.db import models |
|
6 | from django.db import models | |
7 |
|
7 | |||
@@ -72,7 +72,7 b' class Thread(models.Model):' | |||||
72 | bumpable = models.BooleanField(default=True) |
|
72 | bumpable = models.BooleanField(default=True) | |
73 | max_posts = models.IntegerField(default=get_thread_max_posts) |
|
73 | max_posts = models.IntegerField(default=get_thread_max_posts) | |
74 |
|
74 | |||
75 |
def get_tags(self) -> |
|
75 | def get_tags(self) -> QuerySet: | |
76 | """ |
|
76 | """ | |
77 | Gets a sorted tag list. |
|
77 | Gets a sorted tag list. | |
78 | """ |
|
78 | """ | |
@@ -118,7 +118,7 b' class Thread(models.Model):' | |||||
118 |
|
118 | |||
119 | return self.bumpable and not self.archived |
|
119 | return self.bumpable and not self.archived | |
120 |
|
120 | |||
121 |
def get_last_replies(self) -> |
|
121 | def get_last_replies(self) -> QuerySet: | |
122 | """ |
|
122 | """ | |
123 | Gets several last replies, not including opening post |
|
123 | Gets several last replies, not including opening post | |
124 | """ |
|
124 | """ | |
@@ -145,7 +145,7 b' class Thread(models.Model):' | |||||
145 | reply_count - 1) |
|
145 | reply_count - 1) | |
146 | return reply_count - last_replies_count - 1 |
|
146 | return reply_count - last_replies_count - 1 | |
147 |
|
147 | |||
148 |
def get_replies(self, view_fields_only=False) -> |
|
148 | def get_replies(self, view_fields_only=False) -> QuerySet: | |
149 | """ |
|
149 | """ | |
150 | Gets sorted thread posts |
|
150 | Gets sorted thread posts | |
151 | """ |
|
151 | """ | |
@@ -156,7 +156,10 b' class Thread(models.Model):' | |||||
156 | query = query.defer('poster_ip') |
|
156 | query = query.defer('poster_ip') | |
157 | return query.all() |
|
157 | return query.all() | |
158 |
|
158 | |||
159 | def get_replies_with_images(self, view_fields_only=False) -> list: |
|
159 | def get_top_level_replies(self) -> QuerySet: | |
|
160 | return self.get_replies().exclude(refposts__threads__in=[self]) | |||
|
161 | ||||
|
162 | def get_replies_with_images(self, view_fields_only=False) -> QuerySet: | |||
160 | """ |
|
163 | """ | |
161 | Gets replies that have at least one image attached |
|
164 | Gets replies that have at least one image attached | |
162 | """ |
|
165 | """ | |
@@ -164,14 +167,6 b' class Thread(models.Model):' | |||||
164 | return self.get_replies(view_fields_only).annotate(images_count=Count( |
|
167 | return self.get_replies(view_fields_only).annotate(images_count=Count( | |
165 | 'images')).filter(images_count__gt=0) |
|
168 | 'images')).filter(images_count__gt=0) | |
166 |
|
169 | |||
167 | # TODO Do we still need this? |
|
|||
168 | def add_tag(self, tag: Tag): |
|
|||
169 | """ |
|
|||
170 | Connects thread to a tag and tag to a thread |
|
|||
171 | """ |
|
|||
172 |
|
||||
173 | self.tags.add(tag) |
|
|||
174 |
|
||||
175 | def get_opening_post(self, only_id=False) -> Post: |
|
170 | def get_opening_post(self, only_id=False) -> Post: | |
176 | """ |
|
171 | """ | |
177 | Gets the first post of the thread |
|
172 | Gets the first post of the thread | |
@@ -199,16 +194,6 b' class Thread(models.Model):' | |||||
199 |
|
194 | |||
200 | return self.get_opening_post().pub_time |
|
195 | return self.get_opening_post().pub_time | |
201 |
|
196 | |||
202 | def delete(self, using=None): |
|
|||
203 | """ |
|
|||
204 | Deletes thread with all replies. |
|
|||
205 | """ |
|
|||
206 |
|
||||
207 | for reply in self.get_replies().all(): |
|
|||
208 | reply.delete() |
|
|||
209 |
|
||||
210 | super(Thread, self).delete(using) |
|
|||
211 |
|
||||
212 | def __str__(self): |
|
197 | def __str__(self): | |
213 | return 'T#{}/{}'.format(self.id, self.get_opening_post_id()) |
|
198 | return 'T#{}/{}'.format(self.id, self.get_opening_post_id()) | |
214 |
|
199 | |||
@@ -216,13 +201,15 b' class Thread(models.Model):' | |||||
216 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags()) |
|
201 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags()) | |
217 |
|
202 | |||
218 | def update_posts_time(self, exclude_posts=None): |
|
203 | def update_posts_time(self, exclude_posts=None): | |
|
204 | last_edit_time = self.last_edit_time | |||
|
205 | ||||
219 | for post in self.post_set.all(): |
|
206 | for post in self.post_set.all(): | |
220 |
if exclude_posts is |
|
207 | if exclude_posts is None or post not in exclude_posts: | |
221 | # Manual update is required because uids are generated on save |
|
208 | # Manual update is required because uids are generated on save | |
222 |
post.last_edit_time = |
|
209 | post.last_edit_time = last_edit_time | |
223 | post.save(update_fields=['last_edit_time']) |
|
210 | post.save(update_fields=['last_edit_time']) | |
224 |
|
211 | |||
225 |
post.threads.update(last_edit_time= |
|
212 | post.get_threads().update(last_edit_time=last_edit_time) | |
226 |
|
213 | |||
227 | def notify_clients(self): |
|
214 | def notify_clients(self): | |
228 | if not settings.get_bool('External', 'WebsocketsEnabled'): |
|
215 | if not settings.get_bool('External', 'WebsocketsEnabled'): |
@@ -99,3 +99,7 b' textarea, input {' | |||||
99 | .post-image-full { |
|
99 | .post-image-full { | |
100 | width: 100%; |
|
100 | width: 100%; | |
101 | } |
|
101 | } | |
|
102 | ||||
|
103 | #preview-text { | |||
|
104 | display: none; | |||
|
105 | } |
@@ -232,11 +232,11 b' blockquote {' | |||||
232 | } |
|
232 | } | |
233 |
|
233 | |||
234 | .dead_post { |
|
234 | .dead_post { | |
235 | background-color: #442222; |
|
235 | border-left: solid 5px #982C2C; | |
236 | } |
|
236 | } | |
237 |
|
237 | |||
238 | .archive_post { |
|
238 | .archive_post { | |
239 | background-color: #000; |
|
239 | border-left: solid 5px #B7B7B7; | |
240 | } |
|
240 | } | |
241 |
|
241 | |||
242 | .mark_btn { |
|
242 | .mark_btn { | |
@@ -534,3 +534,22 b' ul {' | |||||
534 | .post-button-form > button:hover { |
|
534 | .post-button-form > button:hover { | |
535 | text-decoration: underline; |
|
535 | text-decoration: underline; | |
536 | } |
|
536 | } | |
|
537 | ||||
|
538 | .tree_reply > .post { | |||
|
539 | margin-left: 1ex; | |||
|
540 | margin-top: 1ex; | |||
|
541 | border-left: solid 1px #777; | |||
|
542 | border-right: solid 1px #777; | |||
|
543 | } | |||
|
544 | ||||
|
545 | #preview-text { | |||
|
546 | border: solid 1px white; | |||
|
547 | margin: 1ex 0 1ex 0; | |||
|
548 | padding: 1ex; | |||
|
549 | } | |||
|
550 | ||||
|
551 | button { | |||
|
552 | border: 1px solid white; | |||
|
553 | margin-bottom: .5ex; | |||
|
554 | margin-top: .5ex; | |||
|
555 | } |
@@ -28,7 +28,7 b' html {' | |||||
28 | } |
|
28 | } | |
29 |
|
29 | |||
30 | .link, a { |
|
30 | .link, a { | |
31 | color: rgb(255, 102, 0); |
|
31 | color: #ff7000; | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
34 | .block { |
|
34 | .block { | |
@@ -187,7 +187,11 b' blockquote {' | |||||
187 | } |
|
187 | } | |
188 |
|
188 | |||
189 | .dead_post { |
|
189 | .dead_post { | |
190 | background-color: #ecc; |
|
190 | border-top: solid #d5494f; | |
|
191 | } | |||
|
192 | ||||
|
193 | .archive_post { | |||
|
194 | border-top: solid #575e9f; | |||
191 | } |
|
195 | } | |
192 |
|
196 | |||
193 | .quote { |
|
197 | .quote { | |
@@ -366,3 +370,9 b' input[type="submit"]:hover {' | |||||
366 | .highlight { |
|
370 | .highlight { | |
367 | background-color: #F9E8A5; |
|
371 | background-color: #F9E8A5; | |
368 | } |
|
372 | } | |
|
373 | ||||
|
374 | #preview-text { | |||
|
375 | border: solid 1px black; | |||
|
376 | margin: 1ex 0 1ex 0; | |||
|
377 | padding: 1ex; | |||
|
378 | } |
@@ -401,3 +401,9 b' li {' | |||||
401 | .dead_post { |
|
401 | .dead_post { | |
402 | border-right: 1ex solid #666; |
|
402 | border-right: 1ex solid #666; | |
403 | } |
|
403 | } | |
|
404 | ||||
|
405 | #preview-text { | |||
|
406 | border: solid 1px white; | |||
|
407 | margin: 1ex 0 1ex 0; | |||
|
408 | padding: 1ex; | |||
|
409 | } |
1 | NO CONTENT: modified file, binary diff hidden |
|
NO CONTENT: modified file, binary diff hidden |
@@ -24,4 +24,20 b" var form = $('#form');" | |||||
24 | if (event.which == 13 && event.ctrlKey) { |
|
24 | if (event.which == 13 && event.ctrlKey) { | |
25 | form.submit(); |
|
25 | form.submit(); | |
26 | } |
|
26 | } | |
27 | }); No newline at end of file |
|
27 | }); | |
|
28 | ||||
|
29 | $('#preview-button').click(function() { | |||
|
30 | var data = { | |||
|
31 | raw_text: $('textarea').val() | |||
|
32 | } | |||
|
33 | ||||
|
34 | var diffUrl = '/api/preview/'; | |||
|
35 | ||||
|
36 | $.post(diffUrl, | |||
|
37 | data, | |||
|
38 | function(data) { | |||
|
39 | var previewTextBlock = $('#preview-text'); | |||
|
40 | previewTextBlock.html(data); | |||
|
41 | previewTextBlock.show(); | |||
|
42 | }) | |||
|
43 | }) |
@@ -104,10 +104,11 b' function getThreadDiff() {' | |||||
104 | } |
|
104 | } | |
105 |
|
105 | |||
106 | var data = { |
|
106 | var data = { | |
107 | uids: uids |
|
107 | uids: uids, | |
|
108 | thread: threadId | |||
108 | } |
|
109 | } | |
109 |
|
110 | |||
110 |
var diffUrl = '/api/diff_thread |
|
111 | var diffUrl = '/api/diff_thread/'; | |
111 |
|
112 | |||
112 | $.post(diffUrl, |
|
113 | $.post(diffUrl, | |
113 | data, |
|
114 | data, | |
@@ -309,6 +310,7 b' function resetForm(form) {' | |||||
309 | form.find('input:radio, input:checkbox') |
|
310 | form.find('input:radio, input:checkbox') | |
310 | .removeAttr('checked').removeAttr('selected'); |
|
311 | .removeAttr('checked').removeAttr('selected'); | |
311 | $('.file_wrap').find('.file-thumb').remove(); |
|
312 | $('.file_wrap').find('.file-thumb').remove(); | |
|
313 | $('#preview-text').hide(); | |||
312 | } |
|
314 | } | |
313 |
|
315 | |||
314 | /** |
|
316 | /** | |
@@ -373,6 +375,9 b' function processNewPost(post) {' | |||||
373 | showAsErrors($('form'), gettext('Sending message...')); |
|
375 | showAsErrors($('form'), gettext('Sending message...')); | |
374 | }, |
|
376 | }, | |
375 | success: updateOnPost, |
|
377 | success: updateOnPost, | |
|
378 | error: function() { | |||
|
379 | showAsErrors($('form'), gettext('Server error!')); | |||
|
380 | }, | |||
376 | url: '/api/add_post/' + threadId + '/' |
|
381 | url: '/api/add_post/' + threadId + '/' | |
377 | }; |
|
382 | }; | |
378 |
|
383 |
@@ -122,12 +122,10 b'' | |||||
122 | <div> |
|
122 | <div> | |
123 | {% trans 'Tags must be delimited by spaces. Text or image is required.' %} |
|
123 | {% trans 'Tags must be delimited by spaces. Text or image is required.' %} | |
124 | </div> |
|
124 | </div> | |
125 | <div> |
|
125 | <div><button id="preview-button">{% trans 'Preview' %}</button></div> | |
126 | <a href="{% url "staticpage" name="help" %}">{% trans 'Text syntax' %}</a> |
|
126 | <div id="preview-text"></div> | |
127 | </div> |
|
127 | <div><a href="{% url "staticpage" name="help" %}">{% trans 'Text syntax' %}</a></div> | |
128 | <div> |
|
128 | <div><a href="{% url "tags" "required" %}">{% trans 'Tags' %}</a></div> | |
129 | <a href="{% url "tags" "required" %}">{% trans 'Tags' %}</a> |
|
|||
130 | </div> |
|
|||
131 | </div> |
|
129 | </div> | |
132 | </div> |
|
130 | </div> | |
133 |
|
131 | |||
@@ -141,36 +139,27 b'' | |||||
141 | <span class="metapanel"> |
|
139 | <span class="metapanel"> | |
142 | <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b> |
|
140 | <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b> | |
143 | {% trans "Pages:" %} |
|
141 | {% trans "Pages:" %} | |
144 | <a href=" |
|
|||
145 | {% if tag %} |
|
|||
146 | {% url "tag" tag_name=tag.name page=paginator.page_range|first %} |
|
|||
147 | {% else %} |
|
|||
148 | {% url "index" page=paginator.page_range|first %} |
|
|||
149 | {% endif %} |
|
|||
150 | "><<</a> |
|
|||
151 | [ |
|
142 | [ | |
152 | {% for page in paginator.center_range %} |
|
143 | {% with dividers=paginator.get_dividers %} | |
153 | <a |
|
144 | {% for page in paginator.get_divided_range %} | |
154 |
{% if |
|
145 | {% if page in dividers %} | |
155 |
|
|
146 | …, | |
156 |
{% endif |
|
147 | {% endif %} | |
157 |
|
|
148 | <a | |
158 | {% if tag %} |
|
149 | {% ifequal page current_page.number %} | |
159 | {% url "tag" tag_name=tag.name page=page %} |
|
150 | class="current_page" | |
160 |
{% e |
|
151 | {% endifequal %} | |
161 | {% url "index" page=page %} |
|
152 | href=" | |
162 |
{% |
|
153 | {% if tag %} | |
163 | ">{{ page }}</a> |
|
154 | {% url "tag" tag_name=tag.name %}?page={{ page }} | |
164 | {% if not forloop.last %},{% endif %} |
|
155 | {% else %} | |
165 | {% endfor %} |
|
156 | {% url "index" %}?page={{ page }} | |
|
157 | {% endif %} | |||
|
158 | ">{{ page }}</a> | |||
|
159 | {% if not forloop.last %},{% endif %} | |||
|
160 | {% endfor %} | |||
|
161 | {% endwith %} | |||
166 | ] |
|
162 | ] | |
167 | <a href=" |
|
|||
168 | {% if tag %} |
|
|||
169 | {% url "tag" tag_name=tag.name page=paginator.page_range|last %} |
|
|||
170 | {% else %} |
|
|||
171 | {% url "index" page=paginator.page_range|last %} |
|
|||
172 | {% endif %} |
|
|||
173 | ">>></a> |
|
|||
174 | [<a href="rss/">RSS</a>] |
|
163 | [<a href="rss/">RSS</a>] | |
175 | </span> |
|
164 | </span> | |
176 |
|
165 |
@@ -51,24 +51,21 b'' | |||||
51 | <span class="metapanel"> |
|
51 | <span class="metapanel"> | |
52 | <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b> |
|
52 | <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b> | |
53 | {% trans "Pages:" %} |
|
53 | {% trans "Pages:" %} | |
54 | <a href=" |
|
|||
55 | {% url "feed" page=paginator.page_range|first %} |
|
|||
56 | "><<</a> |
|
|||
57 | [ |
|
54 | [ | |
58 | {% for page in paginator.center_range %} |
|
55 | {% with dividers=paginator.get_dividers %} | |
59 | <a |
|
56 | {% for page in paginator.get_divided_range %} | |
60 |
{% if |
|
57 | {% if page in dividers %} | |
61 |
|
|
58 | …, | |
62 |
{% endif |
|
59 | {% endif %} | |
63 |
|
|
60 | <a | |
64 |
|
|
61 | {% ifequal page current_page.number %} | |
65 | ">{{ page }}</a> |
|
62 | class="current_page" | |
66 | {% if not forloop.last %},{% endif %} |
|
63 | {% endifequal %} | |
67 | {% endfor %} |
|
64 | href="{% url "feed" %}?page={{ page }}">{{ page }}</a> | |
|
65 | {% if not forloop.last %},{% endif %} | |||
|
66 | {% endfor %} | |||
|
67 | {% endwith %} | |||
68 | ] |
|
68 | ] | |
69 | <a href=" |
|
|||
70 | {% url "feed" page=paginator.page_range|last %} |
|
|||
71 | ">>></a> |
|
|||
72 | </span> |
|
69 | </span> | |
73 |
|
70 | |||
74 | {% endblock %} |
|
71 | {% endblock %} |
@@ -73,11 +73,19 b'' | |||||
73 | {% endif %} |
|
73 | {% endif %} | |
74 | {% endautoescape %} |
|
74 | {% endautoescape %} | |
75 | {% if post.is_referenced %} |
|
75 | {% if post.is_referenced %} | |
76 | <div class="refmap"> |
|
76 | {% if mode_tree %} | |
77 | {% autoescape off %} |
|
77 | <div class="tree_reply"> | |
78 | {% trans "Replies" %}: {{ post.refmap }} |
|
78 | {% for refpost in post.get_referenced_posts %} | |
79 | {% endautoescape %} |
|
79 | {% post_view refpost mode_tree=True %} | |
80 | </div> |
|
80 | {% endfor %} | |
|
81 | </div> | |||
|
82 | {% else %} | |||
|
83 | <div class="refmap"> | |||
|
84 | {% autoescape off %} | |||
|
85 | {% trans "Replies" %}: {{ post.refmap }} | |||
|
86 | {% endautoescape %} | |||
|
87 | </div> | |||
|
88 | {% endif %} | |||
81 | {% endif %} |
|
89 | {% endif %} | |
82 | </div> |
|
90 | </div> | |
83 | {% comment %} |
|
91 | {% comment %} |
@@ -16,6 +16,7 b'' | |||||
16 | <p>[comment]<span class="comment">{% trans 'Comment' %}</span>[/comment]</p> |
|
16 | <p>[comment]<span class="comment">{% trans 'Comment' %}</span>[/comment]</p> | |
17 | <p>[quote]<span class="quote">>{% trans 'Quote' %}</span>[/quote]</p> |
|
17 | <p>[quote]<span class="quote">>{% trans 'Quote' %}</span>[/quote]</p> | |
18 | <p>[quote source=src]<div class="multiquote"><div class="quote-header">src</div><div class="quote-text">{% trans 'Quote' %}</div></div><br />[/quote]</p> |
|
18 | <p>[quote source=src]<div class="multiquote"><div class="quote-header">src</div><div class="quote-text">{% trans 'Quote' %}</div></div><br />[/quote]</p> | |
|
19 | <p>[tag]<a class="tag">tag</a>[/tag]</p> | |||
19 | <br/> |
|
20 | <br/> | |
20 | <p>{% trans 'You can try pasting the text and previewing the result here:' %} <a href="{% url 'preview' %}">{% trans 'Preview' %}</a></p> |
|
21 | <p>{% trans 'You can try pasting the text and previewing the result here:' %} <a href="{% url 'preview' %}">{% trans 'Preview' %}</a></p> | |
21 | {% endblock %} |
|
22 | {% endblock %} |
@@ -10,6 +10,17 b'' | |||||
10 | - {{ site_name }}</title> |
|
10 | - {{ site_name }}</title> | |
11 | {% endblock %} |
|
11 | {% endblock %} | |
12 |
|
12 | |||
|
13 | {% block content %} | |||
|
14 | <div class="image-mode-tab"> | |||
|
15 | <a {% ifequal mode 'normal' %}class="current_mode"{% endifequal %} href="{% url 'thread' opening_post.id %}">{% trans 'Normal' %}</a>, | |||
|
16 | <a {% ifequal mode 'gallery' %}class="current_mode"{% endifequal %} href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery' %}</a>, | |||
|
17 | <a {% ifequal mode 'tree' %}class="current_mode"{% endifequal %} href="{% url 'thread_tree' opening_post.id %}">{% trans 'Tree' %}</a> | |||
|
18 | </div> | |||
|
19 | ||||
|
20 | {% block thread_content %} | |||
|
21 | {% endblock %} | |||
|
22 | {% endblock %} | |||
|
23 | ||||
13 | {% block metapanel %} |
|
24 | {% block metapanel %} | |
14 |
|
25 | |||
15 | <span class="metapanel" |
|
26 | <span class="metapanel" |
@@ -11,15 +11,10 b'' | |||||
11 | - {{ site_name }}</title> |
|
11 | - {{ site_name }}</title> | |
12 | {% endblock %} |
|
12 | {% endblock %} | |
13 |
|
13 | |||
14 | {% block content %} |
|
14 | {% block thread_content %} | |
15 | {% get_current_language as LANGUAGE_CODE %} |
|
15 | {% get_current_language as LANGUAGE_CODE %} | |
16 | {% get_current_timezone as TIME_ZONE %} |
|
16 | {% get_current_timezone as TIME_ZONE %} | |
17 |
|
17 | |||
18 | <div class="image-mode-tab"> |
|
|||
19 | <a href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>, |
|
|||
20 | <a class="current_mode" href="{% url 'thread_gallery' thread.get_opening_post.id %}">{% trans 'Gallery mode' %}</a> |
|
|||
21 | </div> |
|
|||
22 |
|
||||
23 | <div id="posts-table"> |
|
18 | <div id="posts-table"> | |
24 | {% if posts %} |
|
19 | {% if posts %} | |
25 | {% for post in posts %} |
|
20 | {% for post in posts %} |
@@ -5,15 +5,10 b'' | |||||
5 | {% load board %} |
|
5 | {% load board %} | |
6 | {% load tz %} |
|
6 | {% load tz %} | |
7 |
|
7 | |||
8 | {% block content %} |
|
8 | {% block thread_content %} | |
9 | {% get_current_language as LANGUAGE_CODE %} |
|
9 | {% get_current_language as LANGUAGE_CODE %} | |
10 | {% get_current_timezone as TIME_ZONE %} |
|
10 | {% get_current_timezone as TIME_ZONE %} | |
11 |
|
11 | |||
12 | <div class="image-mode-tab"> |
|
|||
13 | <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>, |
|
|||
14 | <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a> |
|
|||
15 | </div> |
|
|||
16 |
|
||||
17 | {% if bumpable and thread.has_post_limit %} |
|
12 | {% if bumpable and thread.has_post_limit %} | |
18 | <div class="bar-bg"> |
|
13 | <div class="bar-bg"> | |
19 | <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress"> |
|
14 | <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress"> | |
@@ -44,6 +39,8 b'' | |||||
44 | </div> |
|
39 | </div> | |
45 | </form> |
|
40 | </form> | |
46 | </div> |
|
41 | </div> | |
|
42 | <div><button id="preview-button">{% trans 'Preview' %}</button></div> | |||
|
43 | <div id="preview-text"></div> | |||
47 | <div><a href="{% url "staticpage" name="help" %}"> |
|
44 | <div><a href="{% url "staticpage" name="help" %}"> | |
48 | {% trans 'Text syntax' %}</a></div> |
|
45 | {% trans 'Text syntax' %}</a></div> | |
49 | <div><a id="form-close-button" href="#" onClick="resetFormPosition(); return false;">{% trans 'Close form' %}</a></div> |
|
46 | <div><a id="form-close-button" href="#" onClick="resetFormPosition(); return false;">{% trans 'Close form' %}</a></div> |
@@ -36,7 +36,7 b' def image_actions(*args, **kwargs):' | |||||
36 | image_link = 'http://' + args[1] + image_link # TODO https? |
|
36 | image_link = 'http://' + args[1] + image_link # TODO https? | |
37 |
|
37 | |||
38 | return ', '.join([IMG_ACTION_URL.format( |
|
38 | return ', '.join([IMG_ACTION_URL.format( | |
39 | action['link'] % image_link, action['name'])for action in actions]) |
|
39 | action['link'] % image_link, action['name']) for action in actions]) | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | @register.simple_tag(name='post_view') |
|
42 | @register.simple_tag(name='post_view') |
@@ -15,7 +15,7 b' class ApiTest(TestCase):' | |||||
15 | tags=[tag]) |
|
15 | tags=[tag]) | |
16 |
|
16 | |||
17 | req = MockRequest() |
|
17 | req = MockRequest() | |
18 |
req. |
|
18 | req.POST['thread'] = opening_post.id | |
19 | req.POST['uids'] = opening_post.uid |
|
19 | req.POST['uids'] = opening_post.uid | |
20 | # Check the exact timestamp post was added |
|
20 | # Check the exact timestamp post was added | |
21 | empty_response = api.api_get_threaddiff(req) |
|
21 | empty_response = api.api_get_threaddiff(req) | |
@@ -29,7 +29,7 b' class ApiTest(TestCase):' | |||||
29 | text='[post]%d[/post]\ntext' % opening_post.id, |
|
29 | text='[post]%d[/post]\ntext' % opening_post.id, | |
30 | thread=opening_post.get_thread()) |
|
30 | thread=opening_post.get_thread()) | |
31 | req = MockRequest() |
|
31 | req = MockRequest() | |
32 |
req. |
|
32 | req.POST['thread'] = opening_post.id | |
33 | req.POST['uids'] = ' '.join(uids) |
|
33 | req.POST['uids'] = ' '.join(uids) | |
34 | # Check the timestamp before post was added |
|
34 | # Check the timestamp before post was added | |
35 | response = api.api_get_threaddiff(req) |
|
35 | response = api.api_get_threaddiff(req) | |
@@ -40,7 +40,7 b' class ApiTest(TestCase):' | |||||
40 | # Reload post to get the new UID |
|
40 | # Reload post to get the new UID | |
41 | opening_post = Post.objects.get(id=opening_post.id) |
|
41 | opening_post = Post.objects.get(id=opening_post.id) | |
42 | req = MockRequest() |
|
42 | req = MockRequest() | |
43 |
req. |
|
43 | req.POST['thread'] = opening_post.id | |
44 | req.POST['uids'] = ' '.join([opening_post.uid, reply.uid]) |
|
44 | req.POST['uids'] = ' '.join([opening_post.uid, reply.uid]) | |
45 | empty_response = api.api_get_threaddiff(req) |
|
45 | empty_response = api.api_get_threaddiff(req) | |
46 | diff = simplejson.loads(empty_response.content) |
|
46 | diff = simplejson.loads(empty_response.content) |
@@ -3,7 +3,7 b' import logging' | |||||
3 |
|
3 | |||
4 | from django.test import TestCase |
|
4 | from django.test import TestCase | |
5 | from boards.models import KeyPair, GlobalId, Post |
|
5 | from boards.models import KeyPair, GlobalId, Post | |
6 |
|
6 | from boards.models.post.sync import SyncManager | ||
7 |
|
7 | |||
8 | logger = logging.getLogger(__name__) |
|
8 | logger = logging.getLogger(__name__) | |
9 |
|
9 | |||
@@ -56,7 +56,7 b' class KeyTest(TestCase):' | |||||
56 | text='[post]%d[/post]' % post.id, |
|
56 | text='[post]%d[/post]' % post.id, | |
57 | thread=post.get_thread()) |
|
57 | thread=post.get_thread()) | |
58 |
|
58 | |||
59 |
response = |
|
59 | response = SyncManager().generate_response_get([reply_post]) | |
60 | logger.debug(response) |
|
60 | logger.debug(response) | |
61 |
|
61 | |||
62 | key = KeyPair.objects.get(primary=True) |
|
62 | key = KeyPair.objects.get(primary=True) |
@@ -1,5 +1,6 b'' | |||||
1 | from django.core.paginator import Paginator |
|
1 | from django.core.paginator import Paginator | |
2 | from django.test import TestCase |
|
2 | from django.test import TestCase | |
|
3 | ||||
3 | from boards import settings |
|
4 | from boards import settings | |
4 | from boards.models import Tag, Post, Thread, KeyPair |
|
5 | from boards.models import Tag, Post, Thread, KeyPair | |
5 |
|
6 | |||
@@ -145,8 +146,8 b' class PostTests(TestCase):' | |||||
145 | tags=[tag]) |
|
146 | tags=[tag]) | |
146 | thread = opening_post.get_thread() |
|
147 | thread = opening_post.get_thread() | |
147 |
|
148 | |||
148 |
|
|
149 | Post.objects.create_post(title='title', text='text', thread=thread) | |
149 |
|
|
150 | Post.objects.create_post(title='title', text='text', thread=thread) | |
150 |
|
151 | |||
151 | replies = thread.get_replies() |
|
152 | replies = thread.get_replies() | |
152 | self.assertTrue(len(replies) > 0, 'No replies found for thread.') |
|
153 | self.assertTrue(len(replies) > 0, 'No replies found for thread.') | |
@@ -154,3 +155,45 b' class PostTests(TestCase):' | |||||
154 | replies = thread.get_replies(view_fields_only=True) |
|
155 | replies = thread.get_replies(view_fields_only=True) | |
155 | self.assertTrue(len(replies) > 0, |
|
156 | self.assertTrue(len(replies) > 0, | |
156 | 'No replies found for thread with view fields only.') |
|
157 | 'No replies found for thread with view fields only.') | |
|
158 | ||||
|
159 | def test_bumplimit(self): | |||
|
160 | """ | |||
|
161 | Tests that the thread bumpable status is changed and post uids and | |||
|
162 | last update times are updated across all post threads. | |||
|
163 | """ | |||
|
164 | ||||
|
165 | op1 = Post.objects.create_post(title='title', text='text') | |||
|
166 | op2 = Post.objects.create_post(title='title', text='text') | |||
|
167 | ||||
|
168 | thread1 = op1.get_thread() | |||
|
169 | thread1.max_posts = 5 | |||
|
170 | thread1.save() | |||
|
171 | ||||
|
172 | uid_1 = op1.uid | |||
|
173 | uid_2 = op2.uid | |||
|
174 | ||||
|
175 | # Create multi reply | |||
|
176 | Post.objects.create_post( | |||
|
177 | title='title', text='text', thread=thread1, | |||
|
178 | opening_posts=[op1, op2]) | |||
|
179 | thread_update_time_2 = op2.get_thread().last_edit_time | |||
|
180 | for i in range(6): | |||
|
181 | Post.objects.create_post(title='title', text='text', | |||
|
182 | thread=thread1) | |||
|
183 | ||||
|
184 | self.assertFalse(op1.get_thread().can_bump(), | |||
|
185 | 'Thread is bumpable when it should not be.') | |||
|
186 | self.assertTrue(op2.get_thread().can_bump(), | |||
|
187 | 'Thread is not bumpable when it should be.') | |||
|
188 | self.assertNotEqual( | |||
|
189 | uid_1, Post.objects.get(id=op1.id).uid, | |||
|
190 | 'UID of the first OP should be changed but it is not.') | |||
|
191 | self.assertEqual( | |||
|
192 | uid_2, Post.objects.get(id=op2.id).uid, | |||
|
193 | 'UID of the first OP should not be changed but it is.') | |||
|
194 | ||||
|
195 | self.assertNotEqual( | |||
|
196 | thread_update_time_2, | |||
|
197 | Thread.objects.get(id=op2.get_thread().id).last_edit_time, | |||
|
198 | 'Thread last update time should change when the other thread ' | |||
|
199 | 'changes status.') |
@@ -1,6 +1,6 b'' | |||||
1 | from boards.models import KeyPair, Post |
|
1 | from boards.models import KeyPair, Post | |
2 | from boards.tests.mocks import MockRequest |
|
2 | from boards.tests.mocks import MockRequest | |
3 |
from boards.views.sync import respon |
|
3 | from boards.views.sync import response_get | |
4 |
|
4 | |||
5 | __author__ = 'neko259' |
|
5 | __author__ = 'neko259' | |
6 |
|
6 | |||
@@ -18,7 +18,7 b' class SyncTest(TestCase):' | |||||
18 | post = Post.objects.create_post(title='test_title', text='test_text') |
|
18 | post = Post.objects.create_post(title='test_title', text='test_text') | |
19 |
|
19 | |||
20 | request = MockRequest() |
|
20 | request = MockRequest() | |
21 |
request. |
|
21 | request.body = ( | |
22 | '<request type="get" version="1.0">' |
|
22 | '<request type="get" version="1.0">' | |
23 | '<model name="post" version="1.0">' |
|
23 | '<model name="post" version="1.0">' | |
24 | '<id key="%s" local-id="%d" type="%s" />' |
|
24 | '<id key="%s" local-id="%d" type="%s" />' | |
@@ -44,5 +44,5 b' class SyncTest(TestCase):' | |||||
44 | post.title, |
|
44 | post.title, | |
45 | post.get_raw_text(), |
|
45 | post.get_raw_text(), | |
46 | post.get_pub_time_epoch(), |
|
46 | post.get_pub_time_epoch(), | |
47 |
) in respon |
|
47 | ) in response_get(request).content.decode(), | |
48 | 'Wrong response generated for the GET request.') |
|
48 | 'Wrong response generated for the GET request.') |
@@ -20,26 +20,20 b' js_info_dict = {' | |||||
20 | urlpatterns = patterns('', |
|
20 | urlpatterns = patterns('', | |
21 | # /boards/ |
|
21 | # /boards/ | |
22 | url(r'^$', all_threads.AllThreadsView.as_view(), name='index'), |
|
22 | url(r'^$', all_threads.AllThreadsView.as_view(), name='index'), | |
23 | # /boards/page/ |
|
|||
24 | url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(), |
|
|||
25 | name='index'), |
|
|||
26 |
|
23 | |||
27 | # /boards/tag/tag_name/ |
|
24 | # /boards/tag/tag_name/ | |
28 | url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(), |
|
25 | url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(), | |
29 | name='tag'), |
|
26 | name='tag'), | |
30 | # /boards/tag/tag_id/page/ |
|
|||
31 | url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', |
|
|||
32 | tag_threads.TagView.as_view(), name='tag'), |
|
|||
33 |
|
27 | |||
34 | # /boards/thread/ |
|
28 | # /boards/thread/ | |
35 |
url(r'^thread/(?P<post_id>\d+)/$', views.thread. |
|
29 | url(r'^thread/(?P<post_id>\d+)/$', views.thread.NormalThreadView.as_view(), | |
36 | name='thread'), |
|
30 | name='thread'), | |
37 |
url(r'^thread/(?P<post_id>\d+)/mode/gallery/$', views.thread. |
|
31 | url(r'^thread/(?P<post_id>\d+)/mode/gallery/$', views.thread.GalleryThreadView.as_view(), | |
38 | name='thread_gallery'), |
|
32 | name='thread_gallery'), | |
|
33 | url(r'^thread/(?P<post_id>\d+)/mode/tree/$', views.thread.TreeThreadView.as_view(), | |||
|
34 | name='thread_tree'), | |||
39 | # /feed/ |
|
35 | # /feed/ | |
40 | url(r'^feed/$', views.feed.FeedView.as_view(), name='feed'), |
|
36 | url(r'^feed/$', views.feed.FeedView.as_view(), name='feed'), | |
41 | url(r'^feed/page/(?P<page>\w+)/$', views.feed.FeedView.as_view(), |
|
|||
42 | name='feed'), |
|
|||
43 |
|
37 | |||
44 | url(r'^settings/$', settings.SettingsView.as_view(), name='settings'), |
|
38 | url(r'^settings/$', settings.SettingsView.as_view(), name='settings'), | |
45 | url(r'^tags/(?P<query>\w+)?/?$', all_tags.AllTagsView.as_view(), name='tags'), |
|
39 | url(r'^tags/(?P<query>\w+)?/?$', all_tags.AllTagsView.as_view(), name='tags'), | |
@@ -62,8 +56,7 b" urlpatterns = patterns(''," | |||||
62 |
|
56 | |||
63 | # API |
|
57 | # API | |
64 | url(r'^api/post/(?P<post_id>\d+)/$', api.get_post, name="get_post"), |
|
58 | url(r'^api/post/(?P<post_id>\d+)/$', api.get_post, name="get_post"), | |
65 | url(r'^api/diff_thread$', |
|
59 | url(r'^api/diff_thread/$', api.api_get_threaddiff, name="get_thread_diff"), | |
66 | api.api_get_threaddiff, name="get_thread_diff"), |
|
|||
67 | url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads, |
|
60 | url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads, | |
68 | name='get_threads'), |
|
61 | name='get_threads'), | |
69 | url(r'^api/tags/$', api.api_get_tags, name='get_tags'), |
|
62 | url(r'^api/tags/$', api.api_get_tags, name='get_tags'), | |
@@ -73,6 +66,7 b" urlpatterns = patterns(''," | |||||
73 | name='add_post'), |
|
66 | name='add_post'), | |
74 | url(r'^api/notifications/(?P<username>\w+)/$', api.api_get_notifications, |
|
67 | url(r'^api/notifications/(?P<username>\w+)/$', api.api_get_notifications, | |
75 | name='api_notifications'), |
|
68 | name='api_notifications'), | |
|
69 | url(r'^api/preview/$', api.api_get_preview, name='preview'), | |||
76 |
|
70 | |||
77 | # Sync protocol API |
|
71 | # Sync protocol API | |
78 | url(r'^api/sync/pull/$', api.sync_pull, name='api_sync_pull'), |
|
72 | url(r'^api/sync/pull/$', api.sync_pull, name='api_sync_pull'), |
@@ -43,7 +43,9 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
43 | self.settings_manager = None |
|
43 | self.settings_manager = None | |
44 | super(AllThreadsView, self).__init__() |
|
44 | super(AllThreadsView, self).__init__() | |
45 |
|
45 | |||
46 |
def get(self, request, |
|
46 | def get(self, request, form: ThreadForm=None): | |
|
47 | page = request.GET.get('page', DEFAULT_PAGE) | |||
|
48 | ||||
47 | params = self.get_context_data(request=request) |
|
49 | params = self.get_context_data(request=request) | |
48 |
|
50 | |||
49 | if not form: |
|
51 | if not form: | |
@@ -67,7 +69,7 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
67 |
|
69 | |||
68 | return render(request, TEMPLATE, params) |
|
70 | return render(request, TEMPLATE, params) | |
69 |
|
71 | |||
70 |
def post(self, request |
|
72 | def post(self, request): | |
71 | form = ThreadForm(request.POST, request.FILES, |
|
73 | form = ThreadForm(request.POST, request.FILES, | |
72 | error_class=PlainErrorList) |
|
74 | error_class=PlainErrorList) | |
73 | form.session = request.session |
|
75 | form.session = request.session | |
@@ -78,7 +80,7 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
78 | # Ban user because he is suspected to be a bot |
|
80 | # Ban user because he is suspected to be a bot | |
79 | self._ban_current_user(request) |
|
81 | self._ban_current_user(request) | |
80 |
|
82 | |||
81 |
return self.get(request, |
|
83 | return self.get(request, form) | |
82 |
|
84 | |||
83 | def get_page_context(self, paginator, params, page): |
|
85 | def get_page_context(self, paginator, params, page): | |
84 | """ |
|
86 | """ | |
@@ -95,14 +97,12 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
95 | params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page) |
|
97 | params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page) | |
96 |
|
98 | |||
97 | def get_previous_page_link(self, current_page): |
|
99 | def get_previous_page_link(self, current_page): | |
98 |
return reverse('index' |
|
100 | return reverse('index') + '?page=' \ | |
99 |
|
|
101 | + str(current_page.previous_page_number()) | |
100 | }) |
|
|||
101 |
|
102 | |||
102 | def get_next_page_link(self, current_page): |
|
103 | def get_next_page_link(self, current_page): | |
103 |
return reverse('index' |
|
104 | return reverse('index') + '?page=' \ | |
104 |
|
|
105 | + str(current_page.next_page_number()) | |
105 | }) |
|
|||
106 |
|
106 | |||
107 | @staticmethod |
|
107 | @staticmethod | |
108 | def parse_tags_string(tag_strings): |
|
108 | def parse_tags_string(tag_strings): | |
@@ -151,7 +151,7 b' class AllThreadsView(PostMixin, BaseBoar' | |||||
151 | tags = self.parse_tags_string(tag_strings) |
|
151 | tags = self.parse_tags_string(tag_strings) | |
152 |
|
152 | |||
153 | post = Post.objects.create_post(title=title, text=text, image=image, |
|
153 | post = Post.objects.create_post(title=title, text=text, image=image, | |
154 |
ip=ip, tags=tags, |
|
154 | ip=ip, tags=tags, opening_posts=threads) | |
155 |
|
155 | |||
156 | # This is required to update the threads to which posts we have replied |
|
156 | # This is required to update the threads to which posts we have replied | |
157 | # when creating this one |
|
157 | # when creating this one |
@@ -14,6 +14,7 b' from boards.models.post.sync import Sync' | |||||
14 | from boards.utils import datetime_to_epoch |
|
14 | from boards.utils import datetime_to_epoch | |
15 | from boards.views.thread import ThreadView |
|
15 | from boards.views.thread import ThreadView | |
16 | from boards.models.user import Notification |
|
16 | from boards.models.user import Notification | |
|
17 | from boards.mdx_neboard import Parser | |||
17 |
|
18 | |||
18 |
|
19 | |||
19 | __author__ = 'neko259' |
|
20 | __author__ = 'neko259' | |
@@ -43,7 +44,7 b' def api_get_threaddiff(request):' | |||||
43 | Gets posts that were changed or added since time |
|
44 | Gets posts that were changed or added since time | |
44 | """ |
|
45 | """ | |
45 |
|
46 | |||
46 |
thread_id = request. |
|
47 | thread_id = request.POST.get(PARAMETER_THREAD) | |
47 | uids_str = request.POST.get(PARAMETER_UIDS).strip() |
|
48 | uids_str = request.POST.get(PARAMETER_UIDS).strip() | |
48 | uids = uids_str.split(' ') |
|
49 | uids = uids_str.split(' ') | |
49 |
|
50 | |||
@@ -58,7 +59,8 b' def api_get_threaddiff(request):' | |||||
58 | diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) |
|
59 | diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) | |
59 |
|
60 | |||
60 | for post in posts: |
|
61 | for post in posts: | |
61 |
json_data[PARAMETER_UPDATED].append(get_post_data(post.id, diff_type, |
|
62 | json_data[PARAMETER_UPDATED].append(get_post_data(post.id, diff_type, | |
|
63 | request)) | |||
62 | json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) |
|
64 | json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) | |
63 |
|
65 | |||
64 | return HttpResponse(content=json.dumps(json_data)) |
|
66 | return HttpResponse(content=json.dumps(json_data)) | |
@@ -234,6 +236,13 b' def get_post_data(post_id, format_type=D' | |||||
234 | include_last_update=include_last_update) |
|
236 | include_last_update=include_last_update) | |
235 |
|
237 | |||
236 |
|
238 | |||
|
239 | def api_get_preview(request): | |||
|
240 | raw_text = request.POST['raw_text'] | |||
|
241 | ||||
|
242 | parser = Parser() | |||
|
243 | return HttpResponse(content=parser.parse(parser.preparse(raw_text))) | |||
|
244 | ||||
|
245 | ||||
237 | # TODO Make a separate module for sync API methods |
|
246 | # TODO Make a separate module for sync API methods | |
238 | def sync_pull(request): |
|
247 | def sync_pull(request): | |
239 | """ |
|
248 | """ |
@@ -28,7 +28,9 b' DEFAULT_PAGE = 1' | |||||
28 |
|
28 | |||
29 | class FeedView(PostMixin, BaseBoardView): |
|
29 | class FeedView(PostMixin, BaseBoardView): | |
30 |
|
30 | |||
31 |
def get(self, request |
|
31 | def get(self, request): | |
|
32 | page = request.GET.get('page', DEFAULT_PAGE) | |||
|
33 | ||||
32 | params = self.get_context_data(request=request) |
|
34 | params = self.get_context_data(request=request) | |
33 |
|
35 | |||
34 | settings_manager = get_settings_manager(request) |
|
36 | settings_manager = get_settings_manager(request) | |
@@ -61,11 +63,9 b' class FeedView(PostMixin, BaseBoardView)' | |||||
61 | params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page) |
|
63 | params[PARAMETER_NEXT_LINK] = self.get_next_page_link(current_page) | |
62 |
|
64 | |||
63 | def get_previous_page_link(self, current_page): |
|
65 | def get_previous_page_link(self, current_page): | |
64 |
return reverse('feed' |
|
66 | return reverse('feed') + '?page={}'.format( | |
65 |
|
|
67 | current_page.previous_page_number()) | |
66 | }) |
|
|||
67 |
|
68 | |||
68 | def get_next_page_link(self, current_page): |
|
69 | def get_next_page_link(self, current_page): | |
69 |
return reverse('feed' |
|
70 | return reverse('feed') + '?page={}'.format( | |
70 |
|
|
71 | current_page.next_page_number()) | |
71 | }) |
|
@@ -52,22 +52,20 b' class TagView(AllThreadsView, Dispatcher' | |||||
52 | def get_previous_page_link(self, current_page): |
|
52 | def get_previous_page_link(self, current_page): | |
53 | return reverse('tag', kwargs={ |
|
53 | return reverse('tag', kwargs={ | |
54 | 'tag_name': self.tag_name, |
|
54 | 'tag_name': self.tag_name, | |
55 |
|
|
55 | }) + '?page=' + str(current_page.previous_page_number()) | |
56 | }) |
|
|||
57 |
|
56 | |||
58 | def get_next_page_link(self, current_page): |
|
57 | def get_next_page_link(self, current_page): | |
59 | return reverse('tag', kwargs={ |
|
58 | return reverse('tag', kwargs={ | |
60 | 'tag_name': self.tag_name, |
|
59 | 'tag_name': self.tag_name, | |
61 |
|
|
60 | }) + '?page=' + str(current_page.next_page_number()) | |
62 | }) |
|
|||
63 |
|
61 | |||
64 |
def get(self, request, tag_name, |
|
62 | def get(self, request, tag_name, form=None): | |
65 | self.tag_name = tag_name |
|
63 | self.tag_name = tag_name | |
66 |
|
64 | |||
67 |
return super(TagView, self).get(request, |
|
65 | return super(TagView, self).get(request, form) | |
68 |
|
66 | |||
69 |
|
67 | |||
70 |
def post(self, request, tag_name |
|
68 | def post(self, request, tag_name): | |
71 | self.tag_name = tag_name |
|
69 | self.tag_name = tag_name | |
72 |
|
70 | |||
73 | if 'method' in request.POST: |
|
71 | if 'method' in request.POST: |
@@ -1,3 +1,4 b'' | |||||
1 | from boards.views.thread.thread import ThreadView |
|
1 | from boards.views.thread.thread import ThreadView | |
2 | from boards.views.thread.normal import NormalThreadView |
|
2 | from boards.views.thread.normal import NormalThreadView | |
3 | from boards.views.thread.gallery import GalleryThreadView |
|
3 | from boards.views.thread.gallery import GalleryThreadView | |
|
4 | from boards.views.thread.tree import TreeThreadView |
@@ -17,3 +17,6 b' class GalleryThreadView(ThreadView):' | |||||
17 | view_fields_only=True) |
|
17 | view_fields_only=True) | |
18 |
|
18 | |||
19 | return params |
|
19 | return params | |
|
20 | ||||
|
21 | def get_mode(self): | |||
|
22 | return 'gallery' |
@@ -1,9 +1,7 b'' | |||||
1 | from boards import settings |
|
|||
2 |
|
|
1 | from boards.views.thread import ThreadView | |
3 |
|
2 | |||
4 | TEMPLATE_NORMAL = 'boards/thread_normal.html' |
|
3 | TEMPLATE_NORMAL = 'boards/thread_normal.html' | |
5 |
|
4 | |||
6 | CONTEXT_OP = 'opening_post' |
|
|||
7 | CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress' |
|
5 | CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress' | |
8 | CONTEXT_POSTS_LEFT = 'posts_left' |
|
6 | CONTEXT_POSTS_LEFT = 'posts_left' | |
9 | CONTEXT_BUMPABLE = 'bumpable' |
|
7 | CONTEXT_BUMPABLE = 'bumpable' | |
@@ -14,6 +12,9 b' class NormalThreadView(ThreadView):' | |||||
14 | def get_template(self): |
|
12 | def get_template(self): | |
15 | return TEMPLATE_NORMAL |
|
13 | return TEMPLATE_NORMAL | |
16 |
|
14 | |||
|
15 | def get_mode(self): | |||
|
16 | return 'normal' | |||
|
17 | ||||
17 | def get_data(self, thread): |
|
18 | def get_data(self, thread): | |
18 | params = dict() |
|
19 | params = dict() | |
19 |
|
20 | |||
@@ -26,6 +27,4 b' class NormalThreadView(ThreadView):' | |||||
26 | params[CONTEXT_BUMPLIMIT_PRG] = str( |
|
27 | params[CONTEXT_BUMPLIMIT_PRG] = str( | |
27 | float(left_posts) / max_posts * 100) |
|
28 | float(left_posts) / max_posts * 100) | |
28 |
|
29 | |||
29 | params[CONTEXT_OP] = thread.get_opening_post() |
|
|||
30 |
|
||||
31 | return params |
|
30 | return params |
@@ -21,6 +21,8 b" CONTEXT_WS_PROJECT = 'ws_project'" | |||||
21 | CONTEXT_WS_HOST = 'ws_host' |
|
21 | CONTEXT_WS_HOST = 'ws_host' | |
22 | CONTEXT_WS_PORT = 'ws_port' |
|
22 | CONTEXT_WS_PORT = 'ws_port' | |
23 | CONTEXT_WS_TIME = 'ws_token_time' |
|
23 | CONTEXT_WS_TIME = 'ws_token_time' | |
|
24 | CONTEXT_MODE = 'mode' | |||
|
25 | CONTEXT_OP = 'opening_post' | |||
24 |
|
26 | |||
25 | FORM_TITLE = 'title' |
|
27 | FORM_TITLE = 'title' | |
26 | FORM_TEXT = 'text' |
|
28 | FORM_TEXT = 'text' | |
@@ -51,6 +53,8 b' class ThreadView(BaseBoardView, PostMixi' | |||||
51 | params[CONTEXT_FORM] = form |
|
53 | params[CONTEXT_FORM] = form | |
52 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) |
|
54 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) | |
53 | params[CONTEXT_THREAD] = thread_to_show |
|
55 | params[CONTEXT_THREAD] = thread_to_show | |
|
56 | params[CONTEXT_MODE] = self.get_mode() | |||
|
57 | params[CONTEXT_OP] = opening_post | |||
54 |
|
58 | |||
55 | if settings.get_bool('External', 'WebsocketsEnabled'): |
|
59 | if settings.get_bool('External', 'WebsocketsEnabled'): | |
56 | token_time = format(timezone.now(), u'U') |
|
60 | token_time = format(timezone.now(), u'U') | |
@@ -107,7 +111,7 b' class ThreadView(BaseBoardView, PostMixi' | |||||
107 |
|
111 | |||
108 | post = Post.objects.create_post(title=title, text=text, image=image, |
|
112 | post = Post.objects.create_post(title=title, text=text, image=image, | |
109 | thread=post_thread, ip=ip, |
|
113 | thread=post_thread, ip=ip, | |
110 |
|
|
114 | opening_posts=threads) | |
111 | post.notify_clients() |
|
115 | post.notify_clients() | |
112 |
|
116 | |||
113 | if html_response: |
|
117 | if html_response: | |
@@ -116,16 +120,19 b' class ThreadView(BaseBoardView, PostMixi' | |||||
116 | else: |
|
120 | else: | |
117 | return post |
|
121 | return post | |
118 |
|
122 | |||
119 | def get_data(self, thread): |
|
123 | def get_data(self, thread) -> dict: | |
120 | """ |
|
124 | """ | |
121 | Returns context params for the view. |
|
125 | Returns context params for the view. | |
122 | """ |
|
126 | """ | |
123 |
|
127 | |||
124 | pass |
|
128 | return dict() | |
125 |
|
129 | |||
126 | def get_template(self): |
|
130 | def get_template(self) -> str: | |
127 | """ |
|
131 | """ | |
128 | Gets template to show the thread mode on. |
|
132 | Gets template to show the thread mode on. | |
129 | """ |
|
133 | """ | |
130 |
|
134 | |||
131 | pass |
|
135 | pass | |
|
136 | ||||
|
137 | def get_mode(self) -> str: | |||
|
138 | pass |
@@ -83,7 +83,6 b' STATICFILES_DIRS = (' | |||||
83 | STATICFILES_FINDERS = ( |
|
83 | STATICFILES_FINDERS = ( | |
84 | 'django.contrib.staticfiles.finders.FileSystemFinder', |
|
84 | 'django.contrib.staticfiles.finders.FileSystemFinder', | |
85 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', |
|
85 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', | |
86 | 'compressor.finders.CompressorFinder', |
|
|||
87 | ) |
|
86 | ) | |
88 |
|
87 | |||
89 | if DEBUG: |
|
88 | if DEBUG: |
@@ -12,7 +12,7 b' Site: http://neboard.me/' | |||||
12 | 1. Install all dependencies over pip or system-wide |
|
12 | 1. Install all dependencies over pip or system-wide | |
13 | 2. Setup a database in `neboard/settings.py` |
|
13 | 2. Setup a database in `neboard/settings.py` | |
14 | 3. Run `./manage.py migrate` to apply all south migrations |
|
14 | 3. Run `./manage.py migrate` to apply all south migrations | |
15 |
4. Apply config changes to `boards/ |
|
15 | 4. Apply config changes to `boards/config/config.ini`. You can see the default settings in `boards/config/default_config.ini` | |
16 |
|
16 | |||
17 | # RUNNING # |
|
17 | # RUNNING # | |
18 |
|
18 | |||
@@ -34,4 +34,4 b' You can also just clone the mercurial pr' | |||||
34 |
|
34 | |||
35 | # CONCLUSION # |
|
35 | # CONCLUSION # | |
36 |
|
36 | |||
37 | Enjoy our software and thank you! No newline at end of file |
|
37 | Enjoy our software and thank you! |
General Comments 0
You need to be logged in to leave comments.
Login now