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.
 """
 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_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.utils.helpers import time_utcnow
 
 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"))
 
     def save(self, user, topic):
         post = Post(content=self.content.data)
+        current_app.pluggy.hook.flaskbb_form_post_save(form=self)
         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):
-        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:
             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):
-        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:
             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):
-    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"))
 
@@ -91,9 +163,9 @@ class ReportForm(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"))
 
@@ -103,12 +175,20 @@ class UserSearchForm(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"))
 
@@ -116,10 +196,10 @@ class SearchPageForm(FlaskForm):
         # Because the DB is not yet initialized when this form is loaded,
         # the query objects cannot be instantiated in the class itself
         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

+ 44 - 30
flaskbb/forum/models.py

@@ -8,8 +8,8 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
-from datetime import timedelta
 import logging
+from datetime import timedelta
 
 from flask import abort, current_app, url_for
 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)
 from flaskbb.utils.settings import flaskbb_config
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -193,6 +192,10 @@ class Post(HideableCRUDMixin, db.Model):
         """
         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):
         """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
@@ -433,7 +436,6 @@ class Topic(HideableCRUDMixin, db.Model):
                             cascade="all, delete-orphan",
                             post_update=True)
 
-    # Properties
     @property
     def second_last_post(self):
         """Returns the second last post or None."""
@@ -452,31 +454,7 @@ class Topic(HideableCRUDMixin, db.Model):
         """Returns the slugified url for the topic."""
         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.
 
         :param title: The title of the topic.
@@ -492,6 +470,9 @@ class Topic(HideableCRUDMixin, db.Model):
             self.user_id = user.id
             self.username = user.username
 
+        if content:
+            self._post = Post(content=content)
+
         self.date_created = self.last_updated = time_utcnow()
 
     def __repr__(self):
@@ -500,6 +481,36 @@ class Topic(HideableCRUDMixin, db.Model):
         """
         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
     def get_topic(cls, topic_id, user):
         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.commit()
 
+        if post is not None:
+            self._post = post
+
         # Create the topic post
-        post.save(user, self)
+        self._post.save(user, self)
 
         # 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
         forum.topic_count += 1

+ 79 - 17
flaskbb/forum/views.py

@@ -22,23 +22,24 @@ from pluggy import HookimplMarker
 from sqlalchemy import asc, desc
 
 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,
                                   TopicsRead)
+from flaskbb.markup import make_renderer
 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)
 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
 
 impl = HookimplMarker("flaskbb")
@@ -263,7 +264,10 @@ class NewTopic(MethodView):
     def get(self, forum_id, slug=None):
         forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
         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):
@@ -274,7 +278,10 @@ class NewTopic(MethodView):
             return redirect(url_for("forum.view_topic", topic_id=topic.id))
 
         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):
@@ -282,6 +289,49 @@ class NewTopic(MethodView):
         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):
     decorators = [
         login_required,
@@ -475,6 +525,7 @@ class NewPost(MethodView):
     def get(self, topic_id, slug=None, post_id=None):
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         form = self.form()
+        form.track_topic.data = current_user.is_tracking_topic(topic)
 
         if post_id is not None:
             post = Post.query.filter_by(id=post_id).first_or_404()
@@ -518,7 +569,12 @@ class EditPost(MethodView):
 
     def get(self, post_id):
         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.track_topic.data = current_user.is_tracking_topic(post.topic)
 
         return render_template(
             "forum/new_post.html", topic=post.topic, form=form, edit_mode=True
@@ -530,9 +586,7 @@ class EditPost(MethodView):
 
         if form.validate_on_submit():
             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 render_template(
@@ -1094,6 +1148,14 @@ def flaskbb_load_blueprints(app):
     )
     register_view(
         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"],
         view_func=MemberList.as_view("memberlist")
     )

+ 2 - 2
flaskbb/plugins/spec.py

@@ -502,7 +502,7 @@ def flaskbb_form_new_post(form):
 
 
 @spec
-def flaskbb_form_new_post_save(form):
+def flaskbb_form_post_save(form):
     """Hook for modifying the :class:`~flaskbb.forum.forms.ReplyForm`.
 
     This hook is called while populating the post object with
@@ -523,7 +523,7 @@ def flaskbb_form_new_topic(form):
 
 
 @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`.
 
     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 %}
 
 {% extends theme("layout.html") %}
-{% from theme("macros.html") import horizontal_field, render_boolean_field %}
+{% from theme("macros.html") import horizontal_field %}
 {% block content %}
 
 <div class="panel page-panel">

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

@@ -10,7 +10,7 @@
 {% extends theme("layout.html") %}
 
 {% 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">
     <ol class="breadcrumb flaskbb-breadcrumb">
@@ -32,10 +32,6 @@
             <div class="panel-body page-body">
                 <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="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'}) }}
                                 </div>
 
+                                {{ render_boolean_field(form.track_topic) }}
+
                                 {{ run_hook("flaskbb_tpl_form_new_post_after", form=form) }}
 
                                 <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 %}
 
 {% extends theme("layout.html") %}
 
 {% 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">
     <ol class="breadcrumb flaskbb-breadcrumb">
         <li><a href="{{ url_for('forum.index') }}">{% trans %}Forum{% endtrans %}</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>
 
 
@@ -18,11 +24,10 @@
         {{ form.hidden_tag() }}
         <div class="panel page-panel">
             <div class="panel-heading page-head">
-                {% trans %}New Topic{% endtrans %}
+                {{ title }}
             </div>
 
             <div class="panel-body page-body">
-                {{ form.hidden_tag() }}
                 <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'}) }}
                                 </div>
 
+                                {{ render_boolean_field(form.track_topic) }}
+
                                 {{ run_hook("flaskbb_tpl_form_new_topic_after", form=form) }}
 
                                 <div class="editor-submit">

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

@@ -114,16 +114,22 @@
                             {{ run_hook("flaskbb_tpl_post_menu_before", post=post) }}
 
                             {% 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>
-                            <!-- 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>
                             {% endif %}
 
                             {% 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 %}
+
                             {% if topic.first_post_id == post.id %}
                                 {% 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) }}">