diff --git a/boards/locale/ru/LC_MESSAGES/django.mo b/boards/locale/ru/LC_MESSAGES/django.mo index c9a6a9e4fec0e37d95b19e39e725a8cf294b2a7b..594ba59ed7c322b3d23fbb2c3171a25684ef5976 GIT binary patch literal 7576 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\n" "Language-Team: LANGUAGE \n" @@ -189,7 +189,7 @@ msgid "Create new thread" msgstr "Создать новую тему" #: templates/boards/all_threads.html:118 templates/boards/preview.html:16 -#: templates/boards/thread_normal.html:43 +#: templates/boards/thread_normal.html:38 msgid "Post" msgstr "Отправить" @@ -199,7 +199,7 @@ msgstr "" "Метки должны быть разделены пробелами. Текст или изображение обязательны." #: templates/boards/all_threads.html:126 -#: templates/boards/thread_normal.html:48 +#: templates/boards/thread_normal.html:43 msgid "Text syntax" msgstr "Синтаксис текста" @@ -244,7 +244,6 @@ msgid "tags" msgstr "метки" #: templates/boards/base.html:40 -#| msgid "Search" msgid "search" msgstr "поиск" @@ -298,15 +297,15 @@ msgstr "Изменить" msgid "Edit thread" msgstr "Изменить тему" -#: templates/boards/post.html:77 +#: templates/boards/post.html:84 msgid "Replies" msgstr "Ответы" -#: templates/boards/post.html:89 templates/boards/thread.html:26 +#: templates/boards/post.html:97 templates/boards/thread.html:37 msgid "messages" msgstr "сообщений" -#: templates/boards/post.html:90 templates/boards/thread.html:27 +#: templates/boards/post.html:98 templates/boards/thread.html:38 msgid "images" msgstr "изображений" @@ -388,37 +387,40 @@ msgstr "Метки не найдены." msgid "All tags" msgstr "Все метки" -#: templates/boards/thread.html:28 -msgid "Last update: " -msgstr "Последнее обновление: " - -#: templates/boards/thread_gallery.html:19 -#: templates/boards/thread_normal.html:13 +#: templates/boards/thread.html:15 msgid "Normal mode" msgstr "Нормальный режим" -#: templates/boards/thread_gallery.html:20 -#: templates/boards/thread_normal.html:14 +#: templates/boards/thread.html:16 msgid "Gallery mode" msgstr "Режим галереи" -#: templates/boards/thread_gallery.html:41 +#: templates/boards/thread.html:17 +#| msgid "Normal mode" +msgid "Tree mode" +msgstr "Древовидный режим" + +#: templates/boards/thread.html:39 +msgid "Last update: " +msgstr "Последнее обновление: " + +#: templates/boards/thread_gallery.html:36 msgid "No images." msgstr "Нет изображений." -#: templates/boards/thread_normal.html:22 +#: templates/boards/thread_normal.html:17 msgid "posts to bumplimit" msgstr "сообщений до бамплимита" -#: templates/boards/thread_normal.html:36 +#: templates/boards/thread_normal.html:31 msgid "Reply to thread" msgstr "Ответить в тему" -#: templates/boards/thread_normal.html:49 +#: templates/boards/thread_normal.html:44 msgid "Close form" msgstr "Закрыть форму" -#: templates/boards/thread_normal.html:63 +#: templates/boards/thread_normal.html:58 templates/boards/thread_tree.html:22 msgid "Update" msgstr "Обновить" diff --git a/boards/migrations/0019_auto_20150519_1323.py b/boards/migrations/0019_auto_20150519_1323.py new file mode 100644 --- /dev/null +++ b/boards/migrations/0019_auto_20150519_1323.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('boards', '0018_banner'), + ] + + operations = [ + migrations.AlterField( + model_name='post', + name='referenced_posts', + field=models.ManyToManyField(db_index=True, to='boards.Post', null=True, related_name='refposts', blank=True), + ), + ] diff --git a/boards/models/post/__init__.py b/boards/models/post/__init__.py --- a/boards/models/post/__init__.py +++ b/boards/models/post/__init__.py @@ -51,6 +51,15 @@ PARAMETER_NEED_OPEN_LINK = 'need_open_li PARAMETER_REPLY_LINK = 'reply_link' PARAMETER_NEED_OP_DATA = 'need_op_data' +POST_VIEW_PARAMS = ( + 'need_op_data', + 'reply_link', + 'moderator', + 'need_open_link', + 'truncated', + 'mode_tree', +) + REFMAP_STR = '>>{}' @@ -171,7 +180,7 @@ class Post(models.Model, Viewable): referenced_posts = models.ManyToManyField('Post', symmetrical=False, null=True, - blank=True, related_name='rfp+', + blank=True, related_name='refposts', db_index=True) refmap = models.TextField(null=True, blank=True) threads = models.ManyToManyField('Thread', db_index=True) @@ -183,6 +192,9 @@ class Post(models.Model, Viewable): def __str__(self): return 'P#{}/{}'.format(self.id, self.title) + def get_referenced_posts(self): + return self.referenced_posts.order_by('pub_time') + def get_title(self) -> str: """ Gets original post title or part of its text. @@ -228,8 +240,7 @@ class Post(models.Model, Viewable): return self.threads - def get_view(self, moderator=False, need_open_link=False, - truncated=False, reply_link=False, *args, **kwargs) -> str: + def get_view(self, *args, **kwargs) -> str: """ Renders post's HTML view. Some of the post params can be passed over kwargs for the means of caching (if we view the thread, some params @@ -250,19 +261,21 @@ class Post(models.Model, Viewable): elif not thread.can_bump(): css_class += ' dead_post' - return render_to_string('boards/post.html', { + params = dict() + for param in POST_VIEW_PARAMS: + if param in kwargs: + params[param] = kwargs[param] + + params.update({ PARAMETER_POST: self, - PARAMETER_MODERATOR: moderator, PARAMETER_IS_OPENING: is_opening, PARAMETER_THREAD: thread, PARAMETER_CSS_CLASS: css_class, - PARAMETER_NEED_OPEN_LINK: need_open_link, - PARAMETER_TRUNCATED: truncated, PARAMETER_OP_ID: opening_post_id, - PARAMETER_REPLY_LINK: reply_link, - PARAMETER_NEED_OP_DATA: kwargs.get(PARAMETER_NEED_OP_DATA) }) + return render_to_string('boards/post.html', params) + def get_search_view(self, *args, **kwargs): return self.get_view(need_op_data=True, *args, **kwargs) diff --git a/boards/models/thread.py b/boards/models/thread.py --- a/boards/models/thread.py +++ b/boards/models/thread.py @@ -156,6 +156,9 @@ class Thread(models.Model): query = query.defer('poster_ip') return query.all() + def get_top_level_replies(self): + return self.get_replies().exclude(refposts__threads__in=[self]) + def get_replies_with_images(self, view_fields_only=False) -> list: """ Gets replies that have at least one image attached diff --git a/boards/static/css/md/base_page.css b/boards/static/css/md/base_page.css --- a/boards/static/css/md/base_page.css +++ b/boards/static/css/md/base_page.css @@ -524,3 +524,10 @@ ul { .post-button-form > button:hover { text-decoration: underline; } + +.tree_reply > .post { + margin-left: 1ex; + margin-top: 1ex; + border-left: solid 1px #777; + border-right: solid 1px #777; +} diff --git a/boards/templates/boards/post.html b/boards/templates/boards/post.html --- a/boards/templates/boards/post.html +++ b/boards/templates/boards/post.html @@ -69,11 +69,19 @@ {% endif %} {% endautoescape %} {% if post.is_referenced %} -
- {% autoescape off %} - {% trans "Replies" %}: {{ post.refmap }} - {% endautoescape %} -
+ {% if mode_tree %} +
+ {% for refpost in post.get_referenced_posts %} + {% post_view refpost mode_tree=True %} + {% endfor %} +
+ {% else %} +
+ {% autoescape off %} + {% trans "Replies" %}: {{ post.refmap }} + {% endautoescape %} +
+ {% endif %} {% endif %} {% comment %} diff --git a/boards/templates/boards/thread.html b/boards/templates/boards/thread.html --- a/boards/templates/boards/thread.html +++ b/boards/templates/boards/thread.html @@ -10,6 +10,17 @@ - {{ site_name }} {% endblock %} +{% block content %} + + + {% block thread_content %} + {% endblock %} +{% endblock %} + {% block metapanel %} {% endblock %} -{% block content %} +{% block thread_content %} {% get_current_language as LANGUAGE_CODE %} {% get_current_timezone as TIME_ZONE %} - -
{% if posts %} {% for post in posts %} diff --git a/boards/templates/boards/thread_normal.html b/boards/templates/boards/thread_normal.html --- a/boards/templates/boards/thread_normal.html +++ b/boards/templates/boards/thread_normal.html @@ -5,15 +5,10 @@ {% load board %} {% load tz %} -{% block content %} +{% block thread_content %} {% get_current_language as LANGUAGE_CODE %} {% get_current_timezone as TIME_ZONE %} - - {% if bumpable and thread.has_post_limit %}
diff --git a/boards/templates/boards/thread_tree.html b/boards/templates/boards/thread_tree.html new file mode 100644 --- /dev/null +++ b/boards/templates/boards/thread_tree.html @@ -0,0 +1,19 @@ +{% extends "boards/thread.html" %} + +{% load i18n %} +{% load static from staticfiles %} +{% load board %} +{% load tz %} + +{% block thread_content %} + {% get_current_language as LANGUAGE_CODE %} + {% get_current_timezone as TIME_ZONE %} + +
+ {% for post in thread.get_top_level_replies %} + {% post_view post moderator=moderator reply_link=True mode_tree=True %} + {% endfor %} +
+ + +{% endblock %} diff --git a/boards/urls.py b/boards/urls.py --- a/boards/urls.py +++ b/boards/urls.py @@ -31,10 +31,12 @@ urlpatterns = patterns('', tag_threads.TagView.as_view(), name='tag'), # /boards/thread/ - url(r'^thread/(?P\d+)/$', views.thread.normal.NormalThreadView.as_view(), + url(r'^thread/(?P\d+)/$', views.thread.NormalThreadView.as_view(), name='thread'), - url(r'^thread/(?P\d+)/mode/gallery/$', views.thread.gallery.GalleryThreadView.as_view(), + url(r'^thread/(?P\d+)/mode/gallery/$', views.thread.GalleryThreadView.as_view(), name='thread_gallery'), + url(r'^thread/(?P\d+)/mode/tree/$', views.thread.TreeThreadView.as_view(), + name='thread_tree'), # /feed/ url(r'^feed/$', views.feed.FeedView.as_view(), name='feed'), url(r'^feed/page/(?P\w+)/$', views.feed.FeedView.as_view(), diff --git a/boards/views/thread/__init__.py b/boards/views/thread/__init__.py --- a/boards/views/thread/__init__.py +++ b/boards/views/thread/__init__.py @@ -1,3 +1,4 @@ from boards.views.thread.thread import ThreadView from boards.views.thread.normal import NormalThreadView from boards.views.thread.gallery import GalleryThreadView +from boards.views.thread.tree import TreeThreadView diff --git a/boards/views/thread/gallery.py b/boards/views/thread/gallery.py --- a/boards/views/thread/gallery.py +++ b/boards/views/thread/gallery.py @@ -3,6 +3,7 @@ from boards.views.thread import ThreadVi TEMPLATE_GALLERY = 'boards/thread_gallery.html' CONTEXT_POSTS = 'posts' +CONTEXT_OP = 'opening_post' class GalleryThreadView(ThreadView): @@ -15,5 +16,9 @@ class GalleryThreadView(ThreadView): params[CONTEXT_POSTS] = thread.get_replies_with_images( view_fields_only=True) + params[CONTEXT_OP] = thread.get_opening_post() return params + + def get_mode(self): + return 'gallery' diff --git a/boards/views/thread/normal.py b/boards/views/thread/normal.py --- a/boards/views/thread/normal.py +++ b/boards/views/thread/normal.py @@ -14,6 +14,9 @@ class NormalThreadView(ThreadView): def get_template(self): return TEMPLATE_NORMAL + def get_mode(self): + return 'normal' + def get_data(self, thread): params = dict() diff --git a/boards/views/thread/thread.py b/boards/views/thread/thread.py --- a/boards/views/thread/thread.py +++ b/boards/views/thread/thread.py @@ -21,6 +21,7 @@ CONTEXT_WS_PROJECT = 'ws_project' CONTEXT_WS_HOST = 'ws_host' CONTEXT_WS_PORT = 'ws_port' CONTEXT_WS_TIME = 'ws_token_time' +CONTEXT_MODE = 'mode' FORM_TITLE = 'title' FORM_TEXT = 'text' @@ -51,6 +52,7 @@ class ThreadView(BaseBoardView, PostMixi params[CONTEXT_FORM] = form params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) params[CONTEXT_THREAD] = thread_to_show + params[CONTEXT_MODE] = self.get_mode() if settings.get_bool('External', 'WebsocketsEnabled'): token_time = format(timezone.now(), u'U') @@ -129,3 +131,6 @@ class ThreadView(BaseBoardView, PostMixi """ pass + + def get_mode(self): + pass diff --git a/boards/views/thread/tree.py b/boards/views/thread/tree.py new file mode 100644 --- /dev/null +++ b/boards/views/thread/tree.py @@ -0,0 +1,27 @@ +from boards import settings +from boards.views.thread import ThreadView + +TEMPLATE_TREE = 'boards/thread_tree.html' + +CONTEXT_OP = 'opening_post' +CONTEXT_BUMPABLE = 'bumpable' + + +class TreeThreadView(ThreadView): + + def get_template(self): + return TEMPLATE_TREE + + def get_data(self, thread): + params = dict() + + bumpable = thread.can_bump() + params[CONTEXT_BUMPABLE] = bumpable + max_posts = thread.max_posts + + params[CONTEXT_OP] = thread.get_opening_post() + + return params + + def get_mode(self): + return 'tree'