Browse Source

Merge pull request #502 from flaskbb/update-topic-title

Make topic title editable
Peter Justin 6 years ago
parent
commit
1da6ccc3d5

+ 127 - 47
flaskbb/forum/forms.py

@@ -9,79 +9,151 @@
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 import logging
 import logging
-from flask_wtf import FlaskForm
-from wtforms import (TextAreaField, StringField, SelectMultipleField,
-                     BooleanField, SubmitField)
-from wtforms.validators import DataRequired, Optional, Length
 
 
 from flask import current_app
 from flask import current_app
 from flask_babelplus import lazy_gettext as _
 from flask_babelplus import lazy_gettext as _
+from flask_wtf import FlaskForm
+from wtforms import (BooleanField, SelectMultipleField, StringField,
+                     SubmitField, TextAreaField)
+from wtforms.validators import DataRequired, Length, Optional
 
 
-from flaskbb.forum.models import Topic, Post, Report, Forum
+from flaskbb.forum.models import Forum, Post, Report, Topic
 from flaskbb.user.models import User
 from flaskbb.user.models import User
-
+from flaskbb.utils.helpers import time_utcnow
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-class QuickreplyForm(FlaskForm):
-    content = TextAreaField(_("Quick reply"), validators=[
-        DataRequired(message=_("You cannot post a reply without content."))])
+class PostForm(FlaskForm):
+    content = TextAreaField(
+        _("Content"),
+        validators=[
+            DataRequired(message=_("You cannot post a reply without content."))
+        ],
+    )
 
 
     submit = SubmitField(_("Reply"))
     submit = SubmitField(_("Reply"))
 
 
     def save(self, user, topic):
     def save(self, user, topic):
         post = Post(content=self.content.data)
         post = Post(content=self.content.data)
+        current_app.pluggy.hook.flaskbb_form_post_save(form=self)
         return post.save(user=user, topic=topic)
         return post.save(user=user, topic=topic)
 
 
 
 
-class ReplyForm(FlaskForm):
-    content = TextAreaField(_("Content"), validators=[
-        DataRequired(message=_("You cannot post a reply without content."))])
+class QuickreplyForm(PostForm):
+    pass
 
 
-    track_topic = BooleanField(_("Track this topic"), default=False,
-                               validators=[Optional()])
 
 
-    submit = SubmitField(_("Reply"))
+class ReplyForm(PostForm):
+    track_topic = BooleanField(
+        _("Track this topic"), default=False, validators=[Optional()]
+    )
+
+    def __init__(self, *args, **kwargs):
+        self.post = kwargs.get("obj", None)
+        PostForm.__init__(self, *args, **kwargs)
 
 
     def save(self, user, topic):
     def save(self, user, topic):
-        post = Post(content=self.content.data)
+        # new post
+        if self.post is None:
+            self.post = Post(content=self.content.data)
+        else:
+            self.post.date_modified = time_utcnow()
+            self.post.modified_by = user.username
 
 
         if self.track_topic.data:
         if self.track_topic.data:
             user.track_topic(topic)
             user.track_topic(topic)
+        else:
+            user.untrack_topic(topic)
 
 
-        current_app.pluggy.hook.flaskbb_form_new_post_save(form=self)
-        return post.save(user=user, topic=topic)
+        current_app.pluggy.hook.flaskbb_form_post_save(form=self)
+        return self.post.save(user=user, topic=topic)
 
 
 
 
-class NewTopicForm(ReplyForm):
-    title = StringField(_("Topic title"), validators=[
-        DataRequired(message=_("Please choose a title for your topic."))])
+class TopicForm(FlaskForm):
+    title = StringField(
+        _("Topic title"),
+        validators=[
+            DataRequired(message=_("Please choose a title for your topic."))
+        ],
+    )
 
 
-    content = TextAreaField(_("Content"), validators=[
-        DataRequired(message=_("You cannot post a reply without content."))])
+    content = TextAreaField(
+        _("Content"),
+        validators=[
+            DataRequired(message=_("You cannot post a reply without content."))
+        ],
+    )
 
 
-    track_topic = BooleanField(_("Track this topic"), default=False,
-                               validators=[Optional()])
+    track_topic = BooleanField(
+        _("Track this topic"), default=False, validators=[Optional()]
+    )
 
 
-    submit = SubmitField(_("Post Topic"))
+    submit = SubmitField(_("Post topic"))
 
 
     def save(self, user, forum):
     def save(self, user, forum):
-        topic = Topic(title=self.title.data)
-        post = Post(content=self.content.data)
+        topic = Topic(title=self.title.data, content=self.content.data)
 
 
         if self.track_topic.data:
         if self.track_topic.data:
             user.track_topic(topic)
             user.track_topic(topic)
+        else:
+            user.untrack_topic(topic)
+
+        current_app.pluggy.hook.flaskbb_form_topic_save(form=self, topic=topic)
+        return topic.save(user=user, forum=forum)
+
+
+class NewTopicForm(TopicForm):
+    pass
 
 
-        current_app.pluggy.hook.flaskbb_form_new_topic_save(form=self,
-                                                            topic=topic)
-        return topic.save(user=user, forum=forum, post=post)
+
+class EditTopicForm(TopicForm):
+
+    submit = SubmitField(_("Save topic"))
+
+    def __init__(self, *args, **kwargs):
+        self.topic = kwargs.get("obj").topic
+        TopicForm.__init__(self, *args, **kwargs)
+
+    def populate_obj(self, *objs):
+        """
+        Populates the attributes of the passed `obj`s with data from the
+        form's fields. This is especially useful to populate the topic and
+        post objects at the same time.
+        """
+        for obj in objs:
+            super(EditTopicForm, self).populate_obj(obj)
+
+    def save(self, user, forum):
+        if self.track_topic.data:
+            user.track_topic(self.topic)
+        else:
+            user.untrack_topic(self.topic)
+
+        if (
+            self.topic.last_post_id == forum.last_post_id
+            and self.title.data != forum.last_post_title
+        ):
+            forum.last_post_title = self.title.data
+
+        self.topic.first_post.date_modified = time_utcnow()
+        self.topic.first_post.modified_by = user.username
+
+        current_app.pluggy.hook.flaskbb_form_topic_save(
+            form=self, topic=self.topic
+        )
+        return self.topic.save(user=user, forum=forum)
 
 
 
 
 class ReportForm(FlaskForm):
 class ReportForm(FlaskForm):
-    reason = TextAreaField(_("Reason"), validators=[
-        DataRequired(message=_("What is the reason for reporting this post?"))
-    ])
+    reason = TextAreaField(
+        _("Reason"),
+        validators=[
+            DataRequired(
+                message=_("What is the reason for reporting this post?")
+            )
+        ],
+    )
 
 
     submit = SubmitField(_("Report post"))
     submit = SubmitField(_("Report post"))
 
 
@@ -91,9 +163,9 @@ class ReportForm(FlaskForm):
 
 
 
 
 class UserSearchForm(FlaskForm):
 class UserSearchForm(FlaskForm):
-    search_query = StringField(_("Search"), validators=[
-        DataRequired(), Length(min=3, max=50)
-    ])
+    search_query = StringField(
+        _("Search"), validators=[DataRequired(), Length(min=3, max=50)]
+    )
 
 
     submit = SubmitField(_("Search"))
     submit = SubmitField(_("Search"))
 
 
@@ -103,12 +175,20 @@ class UserSearchForm(FlaskForm):
 
 
 
 
 class SearchPageForm(FlaskForm):
 class SearchPageForm(FlaskForm):
-    search_query = StringField(_("Criteria"), validators=[
-        DataRequired(), Length(min=3, max=50)])
-
-    search_types = SelectMultipleField(_("Content"), validators=[
-        DataRequired()], choices=[('post', _('Post')), ('topic', _('Topic')),
-                                  ('forum', _('Forum')), ('user', _('Users'))])
+    search_query = StringField(
+        _("Criteria"), validators=[DataRequired(), Length(min=3, max=50)]
+    )
+
+    search_types = SelectMultipleField(
+        _("Content"),
+        validators=[DataRequired()],
+        choices=[
+            ("post", _("Post")),
+            ("topic", _("Topic")),
+            ("forum", _("Forum")),
+            ("user", _("Users")),
+        ],
+    )
 
 
     submit = SubmitField(_("Search"))
     submit = SubmitField(_("Search"))
 
 
@@ -116,10 +196,10 @@ class SearchPageForm(FlaskForm):
         # Because the DB is not yet initialized when this form is loaded,
         # Because the DB is not yet initialized when this form is loaded,
         # the query objects cannot be instantiated in the class itself
         # the query objects cannot be instantiated in the class itself
         search_actions = {
         search_actions = {
-            'post': Post.query.whooshee_search,
-            'topic': Topic.query.whooshee_search,
-            'forum': Forum.query.whooshee_search,
-            'user': User.query.whooshee_search
+            "post": Post.query.whooshee_search,
+            "topic": Topic.query.whooshee_search,
+            "forum": Forum.query.whooshee_search,
+            "user": User.query.whooshee_search,
         }
         }
 
 
         query = self.search_query.data
         query = self.search_query.data

+ 44 - 30
flaskbb/forum/models.py

@@ -8,8 +8,8 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
-from datetime import timedelta
 import logging
 import logging
+from datetime import timedelta
 
 
 from flask import abort, current_app, url_for
 from flask import abort, current_app, url_for
 from sqlalchemy.orm import aliased
 from sqlalchemy.orm import aliased
@@ -21,7 +21,6 @@ from flaskbb.utils.helpers import (get_categories_and_forums, get_forums,
                                    slugify, time_utcnow, topic_is_unread)
                                    slugify, time_utcnow, topic_is_unread)
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
 
 
-
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
@@ -193,6 +192,10 @@ class Post(HideableCRUDMixin, db.Model):
         """
         """
         return "<{} {}>".format(self.__class__.__name__, self.id)
         return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
+    def is_first_post(self):
+        """Checks whether this post is the first post in the topic or not."""
+        return self.topic.is_first_post(self)
+
     def save(self, user=None, topic=None):
     def save(self, user=None, topic=None):
         """Saves a new post. If no parameters are passed we assume that
         """Saves a new post. If no parameters are passed we assume that
         you will just update an existing post. It returns the object after the
         you will just update an existing post. It returns the object after the
@@ -433,7 +436,6 @@ class Topic(HideableCRUDMixin, db.Model):
                             cascade="all, delete-orphan",
                             cascade="all, delete-orphan",
                             post_update=True)
                             post_update=True)
 
 
-    # Properties
     @property
     @property
     def second_last_post(self):
     def second_last_post(self):
         """Returns the second last post or None."""
         """Returns the second last post or None."""
@@ -452,31 +454,7 @@ class Topic(HideableCRUDMixin, db.Model):
         """Returns the slugified url for the topic."""
         """Returns the slugified url for the topic."""
         return url_for("forum.view_topic", topic_id=self.id, slug=self.slug)
         return url_for("forum.view_topic", topic_id=self.id, slug=self.slug)
 
 
-    def first_unread(self, topicsread, user, forumsread=None):
-        """Returns the url to the first unread post. If no unread posts exist
-        it will return the url to the topic.
-
-        :param topicsread: The topicsread object for the topic
-        :param user: The user who should be checked if he has read the
-                     last post in the topic
-        :param forumsread: The forumsread object in which the topic is. If you
-                        also want to check if the user has marked the forum as
-                        read, than you will also need to pass an forumsread
-                        object.
-        """
-        # If the topic is unread try to get the first unread post
-        if topic_is_unread(self, topicsread, user, forumsread):
-            query = Post.query.filter(Post.topic_id == self.id)
-            if topicsread is not None:
-                query = query.filter(Post.date_created > topicsread.last_read)
-            post = query.order_by(Post.id.asc()).first()
-            if post is not None:
-                return post.url
-
-        return self.url
-
-    # Methods
-    def __init__(self, title=None, user=None):
+    def __init__(self, title=None, user=None, content=None):
         """Creates a topic object with some initial values.
         """Creates a topic object with some initial values.
 
 
         :param title: The title of the topic.
         :param title: The title of the topic.
@@ -492,6 +470,9 @@ class Topic(HideableCRUDMixin, db.Model):
             self.user_id = user.id
             self.user_id = user.id
             self.username = user.username
             self.username = user.username
 
 
+        if content:
+            self._post = Post(content=content)
+
         self.date_created = self.last_updated = time_utcnow()
         self.date_created = self.last_updated = time_utcnow()
 
 
     def __repr__(self):
     def __repr__(self):
@@ -500,6 +481,36 @@ class Topic(HideableCRUDMixin, db.Model):
         """
         """
         return "<{} {}>".format(self.__class__.__name__, self.id)
         return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
+    def is_first_post(self, post):
+        """Checks if the post is the first post in the topic.
+
+        :param post: The post object.
+        """
+        return self.first_post_id == post.id
+
+    def first_unread(self, topicsread, user, forumsread=None):
+        """Returns the url to the first unread post. If no unread posts exist
+        it will return the url to the topic.
+
+        :param topicsread: The topicsread object for the topic
+        :param user: The user who should be checked if he has read the
+                     last post in the topic
+        :param forumsread: The forumsread object in which the topic is. If you
+                        also want to check if the user has marked the forum as
+                        read, than you will also need to pass an forumsread
+                        object.
+        """
+        # If the topic is unread try to get the first unread post
+        if topic_is_unread(self, topicsread, user, forumsread):
+            query = Post.query.filter(Post.topic_id == self.id)
+            if topicsread is not None:
+                query = query.filter(Post.date_created > topicsread.last_read)
+            post = query.order_by(Post.id.asc()).first()
+            if post is not None:
+                return post.url
+
+        return self.url
+
     @classmethod
     @classmethod
     def get_topic(cls, topic_id, user):
     def get_topic(cls, topic_id, user):
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
@@ -668,11 +679,14 @@ class Topic(HideableCRUDMixin, db.Model):
         db.session.add(self)
         db.session.add(self)
         db.session.commit()
         db.session.commit()
 
 
+        if post is not None:
+            self._post = post
+
         # Create the topic post
         # Create the topic post
-        post.save(user, self)
+        self._post.save(user, self)
 
 
         # Update the first and last post id
         # Update the first and last post id
-        self.last_post = self.first_post = post
+        self.last_post = self.first_post = self._post
 
 
         # Update the topic count
         # Update the topic count
         forum.topic_count += 1
         forum.topic_count += 1

+ 79 - 17
flaskbb/forum/views.py

@@ -22,23 +22,24 @@ from pluggy import HookimplMarker
 from sqlalchemy import asc, desc
 from sqlalchemy import asc, desc
 
 
 from flaskbb.extensions import allows, db
 from flaskbb.extensions import allows, db
-from flaskbb.markup import make_renderer
-from flaskbb.forum.forms import (NewTopicForm, QuickreplyForm, ReplyForm,
-                                 ReportForm, SearchPageForm, UserSearchForm)
+from flaskbb.forum.forms import (EditTopicForm, NewTopicForm, QuickreplyForm,
+                                 ReplyForm, ReportForm, SearchPageForm,
+                                 UserSearchForm)
 from flaskbb.forum.models import (Category, Forum, ForumsRead, Post, Topic,
 from flaskbb.forum.models import (Category, Forum, ForumsRead, Post, Topic,
                                   TopicsRead)
                                   TopicsRead)
+from flaskbb.markup import make_renderer
 from flaskbb.user.models import User
 from flaskbb.user.models import User
-from flaskbb.utils.helpers import (do_topic_action, format_quote,
-                                   get_online_users, real, register_view,
-                                   render_template, time_diff, time_utcnow,
-                                   FlashAndRedirect)
-from flaskbb.utils.requirements import (CanAccessForum,
-                                        CanDeletePost, CanDeleteTopic,
-                                        CanEditPost, CanPostReply,
-                                        CanPostTopic, Has,
+from flaskbb.utils.helpers import (FlashAndRedirect, do_topic_action,
+                                   format_quote, get_online_users, real,
+                                   register_view, render_template, time_diff,
+                                   time_utcnow)
+from flaskbb.utils.requirements import (CanAccessForum, CanDeletePost,
+                                        CanDeleteTopic, CanEditPost,
+                                        CanPostReply, CanPostTopic, Has,
                                         IsAtleastModeratorInForum)
                                         IsAtleastModeratorInForum)
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
-from .locals import current_topic, current_forum, current_category
+
+from .locals import current_category, current_forum, current_topic
 from .utils import force_login_if_needed
 from .utils import force_login_if_needed
 
 
 impl = HookimplMarker("flaskbb")
 impl = HookimplMarker("flaskbb")
@@ -263,7 +264,10 @@ class NewTopic(MethodView):
     def get(self, forum_id, slug=None):
     def get(self, forum_id, slug=None):
         forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
         forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
         return render_template(
         return render_template(
-            "forum/new_topic.html", forum=forum_instance, form=self.form()
+            "forum/new_topic.html",
+            forum=forum_instance,
+            form=self.form(),
+            edit_mode=False,
         )
         )
 
 
     def post(self, forum_id, slug=None):
     def post(self, forum_id, slug=None):
@@ -274,7 +278,10 @@ class NewTopic(MethodView):
             return redirect(url_for("forum.view_topic", topic_id=topic.id))
             return redirect(url_for("forum.view_topic", topic_id=topic.id))
 
 
         return render_template(
         return render_template(
-            "forum/new_topic.html", forum=forum_instance, form=form
+            "forum/new_topic.html",
+            forum=forum_instance,
+            form=form,
+            edit_mode=False,
         )
         )
 
 
     def form(self):
     def form(self):
@@ -282,6 +289,49 @@ class NewTopic(MethodView):
         return NewTopicForm()
         return NewTopicForm()
 
 
 
 
+class EditTopic(MethodView):
+    decorators = [
+        login_required,
+        allows.requires(
+            CanPostTopic,
+            CanEditPost,
+            on_fail=FlashAndRedirect(
+                message=_("You are not allowed to edit that topic"),
+                level="warning",
+                endpoint=lambda *a, **k: current_forum.url,
+            ),
+        ),
+    ]
+
+    def get(self, topic_id, slug=None):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        form = self.form(obj=topic.first_post, title=topic.title)
+        form.track_topic.data = current_user.is_tracking_topic(topic)
+
+        return render_template(
+            "forum/new_topic.html", forum=topic.forum, form=form, edit_mode=True
+        )
+
+    def post(self, topic_id, slug=None):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        post = topic.first_post
+        form = self.form(obj=post, title=topic.title)
+
+        if form.validate_on_submit():
+            form.populate_obj(topic, post)
+            topic = form.save(real(current_user), topic.forum)
+
+            return redirect(url_for("forum.view_topic", topic_id=topic.id))
+
+        return render_template(
+            "forum/new_topic.html", forum=topic.forum, form=form, edit_mode=True
+        )
+
+    def form(self, **kwargs):
+        current_app.pluggy.hook.flaskbb_form_new_topic(form=NewTopicForm)
+        return EditTopicForm(**kwargs)
+
+
 class ManageForum(MethodView):
 class ManageForum(MethodView):
     decorators = [
     decorators = [
         login_required,
         login_required,
@@ -475,6 +525,7 @@ class NewPost(MethodView):
     def get(self, topic_id, slug=None, post_id=None):
     def get(self, topic_id, slug=None, post_id=None):
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         form = self.form()
         form = self.form()
+        form.track_topic.data = current_user.is_tracking_topic(topic)
 
 
         if post_id is not None:
         if post_id is not None:
             post = Post.query.filter_by(id=post_id).first_or_404()
             post = Post.query.filter_by(id=post_id).first_or_404()
@@ -518,7 +569,12 @@ class EditPost(MethodView):
 
 
     def get(self, post_id):
     def get(self, post_id):
         post = Post.query.filter_by(id=post_id).first_or_404()
         post = Post.query.filter_by(id=post_id).first_or_404()
+
+        if post.is_first_post():
+            return redirect(url_for("forum.edit_topic", topic_id=post.topic_id))
+
         form = self.form(obj=post)
         form = self.form(obj=post)
+        form.track_topic.data = current_user.is_tracking_topic(post.topic)
 
 
         return render_template(
         return render_template(
             "forum/new_post.html", topic=post.topic, form=form, edit_mode=True
             "forum/new_post.html", topic=post.topic, form=form, edit_mode=True
@@ -530,9 +586,7 @@ class EditPost(MethodView):
 
 
         if form.validate_on_submit():
         if form.validate_on_submit():
             form.populate_obj(post)
             form.populate_obj(post)
-            post.date_modified = time_utcnow()
-            post.modified_by = real(current_user).username
-            post.save()
+            post = form.save(real(current_user), post.topic)
             return redirect(url_for("forum.view_post", post_id=post.id))
             return redirect(url_for("forum.view_post", post_id=post.id))
 
 
         return render_template(
         return render_template(
@@ -1094,6 +1148,14 @@ def flaskbb_load_blueprints(app):
     )
     )
     register_view(
     register_view(
         forum,
         forum,
+        routes=[
+            "/topic/<int:topic_id>/edit",
+            "/topic/<int:topic_id>-<slug>/edit",
+        ],
+        view_func=EditTopic.as_view("edit_topic"),
+    )
+    register_view(
+        forum,
         routes=["/memberlist"],
         routes=["/memberlist"],
         view_func=MemberList.as_view("memberlist")
         view_func=MemberList.as_view("memberlist")
     )
     )

+ 2 - 2
flaskbb/plugins/spec.py

@@ -502,7 +502,7 @@ def flaskbb_form_new_post(form):
 
 
 
 
 @spec
 @spec
-def flaskbb_form_new_post_save(form):
+def flaskbb_form_post_save(form):
     """Hook for modifying the :class:`~flaskbb.forum.forms.ReplyForm`.
     """Hook for modifying the :class:`~flaskbb.forum.forms.ReplyForm`.
 
 
     This hook is called while populating the post object with
     This hook is called while populating the post object with
@@ -523,7 +523,7 @@ def flaskbb_form_new_topic(form):
 
 
 
 
 @spec
 @spec
-def flaskbb_form_new_topic_save(form, topic):
+def flaskbb_form_topic_save(form, topic):
     """Hook for modifying the :class:`~flaskbb.forum.forms.NewTopicForm`.
     """Hook for modifying the :class:`~flaskbb.forum.forms.NewTopicForm`.
 
 
     This hook is called while populating the topic object with
     This hook is called while populating the topic object with

+ 1 - 1
flaskbb/templates/auth/login.html

@@ -2,7 +2,7 @@
 {% set active_forum_nav=True %}
 {% set active_forum_nav=True %}
 
 
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
-{% from theme("macros.html") import horizontal_field, render_boolean_field %}
+{% from theme("macros.html") import horizontal_field %}
 {% block content %}
 {% block content %}
 
 
 <div class="panel page-panel">
 <div class="panel page-panel">

+ 3 - 5
flaskbb/templates/forum/new_post.html

@@ -10,7 +10,7 @@
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
 
 
 {% block content %}
 {% block content %}
-{% from theme("macros.html") import render_quickreply, render_submit_field, render_field %}
+{% from theme("macros.html") import render_field, render_submit_field, render_quickreply, render_boolean_field %}
 
 
 <div class="page-view">
 <div class="page-view">
     <ol class="breadcrumb flaskbb-breadcrumb">
     <ol class="breadcrumb flaskbb-breadcrumb">
@@ -32,10 +32,6 @@
             <div class="panel-body page-body">
             <div class="panel-body page-body">
                 <div class="col-md-12 col-sm-12 col-xs-12">
                 <div class="col-md-12 col-sm-12 col-xs-12">
 
 
-                    {% if form.title %}
-                        {{ render_field(form.title, div_class="col-md-12 col-sm-12 col-xs-12") }}
-                    {% endif %}
-
                     <div class="form-group">
                     <div class="form-group">
                         <div class="col-md-12 col-sm-12 col-xs-12">
                         <div class="col-md-12 col-sm-12 col-xs-12">
 
 
@@ -47,6 +43,8 @@
                                     {{ render_quickreply(form.content, div_class="new-message", rows=7, cols=75, placeholder="", **{'data-provide': 'markdown', 'data-autofocus': 'false', 'class': 'flaskbb-editor'}) }}
                                     {{ render_quickreply(form.content, div_class="new-message", rows=7, cols=75, placeholder="", **{'data-provide': 'markdown', 'data-autofocus': 'false', 'class': 'flaskbb-editor'}) }}
                                 </div>
                                 </div>
 
 
+                                {{ render_boolean_field(form.track_topic) }}
+
                                 {{ run_hook("flaskbb_tpl_form_new_post_after", form=form) }}
                                 {{ run_hook("flaskbb_tpl_form_new_post_after", form=form) }}
 
 
                                 <div class="editor-submit">
                                 <div class="editor-submit">

+ 12 - 5
flaskbb/templates/forum/new_topic.html

@@ -1,16 +1,22 @@
-{% set page_title = _("New Topic") %}
+{% if edit_mode %}
+    {% set title = _("Edit Topic") %}
+{% else %}
+    {% set title = _("New Topic") %}
+{% endif %}
+
+{% set page_title = title %}
 {% set active_forum_nav=True %}
 {% set active_forum_nav=True %}
 
 
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
 
 
 {% block content %}
 {% block content %}
-{% from theme("macros.html") import render_field, render_submit_field, render_quickreply %}
+{% from theme("macros.html") import render_field, render_submit_field, render_quickreply, render_boolean_field %}
 
 
 <div class="page-view">
 <div class="page-view">
     <ol class="breadcrumb flaskbb-breadcrumb">
     <ol class="breadcrumb flaskbb-breadcrumb">
         <li><a href="{{ url_for('forum.index') }}">{% trans %}Forum{% endtrans %}</a></li>
         <li><a href="{{ url_for('forum.index') }}">{% trans %}Forum{% endtrans %}</a></li>
         <li><a href="{{ forum.url }}">{{ forum.title }}</a></li>
         <li><a href="{{ forum.url }}">{{ forum.title }}</a></li>
-        <li class="active">{% trans %}New Topic{% endtrans %}</li>
+        <li class="active">{{ title }}</li>
     </ol>
     </ol>
 
 
 
 
@@ -18,11 +24,10 @@
         {{ form.hidden_tag() }}
         {{ form.hidden_tag() }}
         <div class="panel page-panel">
         <div class="panel page-panel">
             <div class="panel-heading page-head">
             <div class="panel-heading page-head">
-                {% trans %}New Topic{% endtrans %}
+                {{ title }}
             </div>
             </div>
 
 
             <div class="panel-body page-body">
             <div class="panel-body page-body">
-                {{ form.hidden_tag() }}
                 <div class="col-md-12 col-sm-12 col-xs-12">
                 <div class="col-md-12 col-sm-12 col-xs-12">
 
 
                     {{ render_field(form.title, div_class="col-md-12 col-sm-12 col-xs-12") }}
                     {{ render_field(form.title, div_class="col-md-12 col-sm-12 col-xs-12") }}
@@ -37,6 +42,8 @@
                                     {{ render_quickreply(form.content, div_class="new-message", rows=7, cols=75, placeholder="", **{'data-provide': 'markdown', 'data-autofocus': 'false', 'class': 'flaskbb-editor'}) }}
                                     {{ render_quickreply(form.content, div_class="new-message", rows=7, cols=75, placeholder="", **{'data-provide': 'markdown', 'data-autofocus': 'false', 'class': 'flaskbb-editor'}) }}
                                 </div>
                                 </div>
 
 
+                                {{ render_boolean_field(form.track_topic) }}
+
                                 {{ run_hook("flaskbb_tpl_form_new_topic_after", form=form) }}
                                 {{ run_hook("flaskbb_tpl_form_new_topic_after", form=form) }}
 
 
                                 <div class="editor-submit">
                                 <div class="editor-submit">

+ 10 - 4
flaskbb/templates/forum/topic.html

@@ -114,16 +114,22 @@
                             {{ run_hook("flaskbb_tpl_post_menu_before", post=post) }}
                             {{ run_hook("flaskbb_tpl_post_menu_before", post=post) }}
 
 
                             {% if current_user|post_reply(topic) %}
                             {% if current_user|post_reply(topic) %}
-                            <!-- Quick quote -->
+                                <!-- Quick quote -->
                                 <a href="#" class="btn btn-icon icon-reply quote-btn" data-post-id="{{ post.id }}" data-toggle="tooltip" data-placement="top" title="{% trans %}Quote this post{% endtrans %}"></a>
                                 <a href="#" class="btn btn-icon icon-reply quote-btn" data-post-id="{{ post.id }}" data-toggle="tooltip" data-placement="top" title="{% trans %}Quote this post{% endtrans %}"></a>
-                            <!-- Full quote/reply -->
+                                <!-- Full quote/reply -->
                                 <a href="{{ url_for('forum.reply_post', topic_id=topic.id, post_id=post.id) }}" class="btn btn-icon icon-replyall" data-toggle="tooltip" data-placement="top" title="{% trans %}Full Reply{% endtrans %}"></a>
                                 <a href="{{ url_for('forum.reply_post', topic_id=topic.id, post_id=post.id) }}" class="btn btn-icon icon-replyall" data-toggle="tooltip" data-placement="top" title="{% trans %}Full Reply{% endtrans %}"></a>
                             {% endif %}
                             {% endif %}
 
 
                             {% if current_user|edit_post(post) %}
                             {% if current_user|edit_post(post) %}
-                            <!-- Edit Post -->
-                            <a href="{{ url_for('forum.edit_post', post_id=post.id) }}" class="btn btn-icon icon-edit" data-toggle="tooltip" data-placement="top" title="{% trans %}Edit this post{% endtrans %}"></a>
+                                {% if topic.first_post_id == post.id %}
+                                <!-- Edit Topic -->
+                                <a href="{{ url_for('forum.edit_topic', topic_id=topic.id) }}" class="btn btn-icon icon-edit" data-toggle="tooltip" data-placement="top" title="{% trans %}Edit this topic{% endtrans %}"></a>
+                                {% else %}
+                                <!-- Edit Post -->
+                                <a href="{{ url_for('forum.edit_post', post_id=post.id) }}" class="btn btn-icon icon-edit" data-toggle="tooltip" data-placement="top" title="{% trans %}Edit this post{% endtrans %}"></a>
+                                {% endif %}
                             {% endif %}
                             {% endif %}
+
                             {% if topic.first_post_id == post.id %}
                             {% if topic.first_post_id == post.id %}
                                 {% if current_user|delete_topic(topic) %}
                                 {% if current_user|delete_topic(topic) %}
                                 <form class="inline-form" method="post" action="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}">
                                 <form class="inline-form" method="post" action="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}">