Browse Source

Added slugs for topics, forums and categories. Fixes #4

I also replaced the ``url_for`` with a url property for the User, Post,
Topic, Forum and Category model if you just want to view a single
user/post/…
sh4nks 11 years ago
parent
commit
2494f9eecf

+ 43 - 1
flaskbb/forum/models.py

@@ -10,10 +10,11 @@
 """
 """
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 
 
-from flask import current_app
+from flask import current_app, url_for
 
 
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.utils.query import TopicQuery
 from flaskbb.utils.query import TopicQuery
+from flaskbb.utils.helpers import slugify
 
 
 
 
 moderators = db.Table(
 moderators = db.Table(
@@ -148,6 +149,12 @@ class Post(db.Model):
     date_modified = db.Column(db.DateTime)
     date_modified = db.Column(db.DateTime)
     modified_by = db.Column(db.String(15))
     modified_by = db.Column(db.String(15))
 
 
+    # Properties
+    @property
+    def url(self):
+        """Returns the url for the post"""
+        return url_for("forum.view_post", post_id=self.id)
+
     # Methods
     # Methods
     def __repr__(self):
     def __repr__(self):
         """
         """
@@ -269,6 +276,16 @@ class Topic(db.Model):
         """Returns the second last post."""
         """Returns the second last post."""
         return self.posts[-2].id
         return self.posts[-2].id
 
 
+    @property
+    def slug(self):
+        """Returns a slugified version from the topic title"""
+        return slugify(self.title)
+
+    @property
+    def url(self):
+        """Returns the url for the topic"""
+        return url_for("forum.view_topic", topic_id=self.id, slug=self.slug)
+
     # Methods
     # Methods
     def __init__(self, title=None):
     def __init__(self, title=None):
         if title:
         if title:
@@ -507,12 +524,24 @@ class Forum(db.Model):
     topics = db.relationship("Topic", backref="forum", lazy="joined",
     topics = db.relationship("Topic", backref="forum", lazy="joined",
                              cascade="all, delete-orphan")
                              cascade="all, delete-orphan")
 
 
+    # Many-to-many
     moderators = \
     moderators = \
         db.relationship("User", secondary=moderators,
         db.relationship("User", secondary=moderators,
                         primaryjoin=(moderators.c.forum_id == id),
                         primaryjoin=(moderators.c.forum_id == id),
                         backref=db.backref("forummoderator", lazy="dynamic"),
                         backref=db.backref("forummoderator", lazy="dynamic"),
                         lazy="joined")
                         lazy="joined")
 
 
+    # Properties
+    @property
+    def slug(self):
+        """Returns a slugified version from the forum title"""
+        return slugify(self.title)
+
+    @property
+    def url(self):
+        """Returns the url for the forum"""
+        return url_for("forum.view_forum", forum_id=self.id, slug=self.slug)
+
     # Methods
     # Methods
     def __repr__(self):
     def __repr__(self):
         """Set to a unique key specific to the object in the database.
         """Set to a unique key specific to the object in the database.
@@ -598,6 +627,19 @@ class Category(db.Model):
                              order_by='asc(Forum.position)',
                              order_by='asc(Forum.position)',
                              cascade="all, delete-orphan")
                              cascade="all, delete-orphan")
 
 
+    # Properties
+    @property
+    def slug(self):
+        """Returns a slugified version from the category title"""
+        return slugify(self.title)
+
+    @property
+    def url(self):
+        """Returns the url for the category"""
+        return url_for("forum.view_category", category_id=self.id,
+                       slug=self.slug)
+
+    # Methods
     def save(self):
     def save(self):
         """Saves a category"""
         """Saves a category"""
 
 

+ 47 - 39
flaskbb/forum/views.py

@@ -88,7 +88,8 @@ def index():
 
 
 
 
 @forum.route("/category/<int:category_id>")
 @forum.route("/category/<int:category_id>")
-def view_category(category_id):
+@forum.route("/category/<int:category_id>-<slug>")
+def view_category(category_id, slug=None):
     if current_user.is_authenticated():
     if current_user.is_authenticated():
         forum_query = Category.query.\
         forum_query = Category.query.\
             filter(Category.id == category_id).\
             filter(Category.id == category_id).\
@@ -118,7 +119,8 @@ def view_category(category_id):
 
 
 
 
 @forum.route("/forum/<int:forum_id>")
 @forum.route("/forum/<int:forum_id>")
-def view_forum(forum_id):
+@forum.route("/forum/<int:forum_id>-<slug>")
+def view_forum(forum_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
     if current_user.is_authenticated():
     if current_user.is_authenticated():
@@ -152,7 +154,8 @@ def view_forum(forum_id):
 
 
 
 
 @forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
 @forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
-def view_topic(topic_id):
+@forum.route("/topic/<int:topic_id>-<slug>", methods=["POST", "GET"])
+def view_topic(topic_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
     topic = Topic.query.filter_by(id=topic_id).first()
     topic = Topic.query.filter_by(id=topic_id).first()
@@ -198,24 +201,24 @@ def view_post(post_id):
     else:
     else:
         page = 1
         page = 1
 
 
-    return redirect(url_for("forum.view_topic", topic_id=post.topic.id) +
-                    "?page=%d#pid%s" % (page, post.id))
+    return redirect(post.topic.url + "?page=%d#pid%s" % (page, post.id))
 
 
 
 
 @forum.route("/<int:forum_id>/topic/new", methods=["POST", "GET"])
 @forum.route("/<int:forum_id>/topic/new", methods=["POST", "GET"])
+@forum.route("/<int:forum_id>-<slug>/topic/new", methods=["POST", "GET"])
 @login_required
 @login_required
-def new_topic(forum_id):
+def new_topic(forum_id, slug=None):
     forum = Forum.query.filter_by(id=forum_id).first_or_404()
     forum = Forum.query.filter_by(id=forum_id).first_or_404()
 
 
     if forum.locked:
     if forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
         flash("This forum is locked; you cannot submit new topics or posts.",
               "danger")
               "danger")
-        return redirect(url_for('forum.view_forum', forum_id=forum.id))
+        return redirect(forum.url)
 
 
     if not can_post_topic(user=current_user, forum=forum):
     if not can_post_topic(user=current_user, forum=forum):
         flash("You do not have the permissions to create a new topic.",
         flash("You do not have the permissions to create a new topic.",
               "danger")
               "danger")
-        return redirect(url_for('forum.view_forum', forum_id=forum.id))
+        return redirect(forum.url)
 
 
     form = NewTopicForm()
     form = NewTopicForm()
     if form.validate_on_submit():
     if form.validate_on_submit():
@@ -227,15 +230,16 @@ def new_topic(forum_id):
 
 
 
 
 @forum.route("/topic/<int:topic_id>/delete")
 @forum.route("/topic/<int:topic_id>/delete")
+@forum.route("/topic/<int:topic_id>-<slug>/delete")
 @login_required
 @login_required
-def delete_topic(topic_id):
+def delete_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if not can_delete_topic(user=current_user, forum=topic.forum,
     if not can_delete_topic(user=current_user, forum=topic.forum,
                             post_user_id=topic.first_post.user_id):
                             post_user_id=topic.first_post.user_id):
 
 
         flash("You do not have the permissions to delete the topic", "danger")
         flash("You do not have the permissions to delete the topic", "danger")
-        return redirect(url_for("forum.view_forum", forum_id=topic.forum_id))
+        return redirect(topic.forum.url)
 
 
     involved_users = User.query.filter(Post.topic_id == topic.id,
     involved_users = User.query.filter(Post.topic_id == topic.id,
                                        User.id == Post.user_id).all()
                                        User.id == Post.user_id).all()
@@ -244,65 +248,69 @@ def delete_topic(topic_id):
 
 
 
 
 @forum.route("/topic/<int:topic_id>/lock")
 @forum.route("/topic/<int:topic_id>/lock")
+@forum.route("/topic/<int:topic_id>-<slug>/lock")
 @login_required
 @login_required
-def lock_topic(topic_id):
+def lock_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if not can_lock_topic(user=current_user, forum=topic.forum):
     if not can_lock_topic(user=current_user, forum=topic.forum):
         flash("Yo do not have the permissions to lock this topic", "danger")
         flash("Yo do not have the permissions to lock this topic", "danger")
-        return redirect(url_for("forum.view_topic", topic_id=topic.id))
+        return redirect(topic.url)
 
 
     topic.locked = True
     topic.locked = True
     topic.save()
     topic.save()
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)
 
 
 
 
 @forum.route("/topic/<int:topic_id>/unlock")
 @forum.route("/topic/<int:topic_id>/unlock")
+@forum.route("/topic/<int:topic_id>-<slug>/unlock")
 @login_required
 @login_required
-def unlock_topic(topic_id):
+def unlock_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     # Unlock is basically the same as lock
     # Unlock is basically the same as lock
     if not can_lock_topic(user=current_user, forum=topic.forum):
     if not can_lock_topic(user=current_user, forum=topic.forum):
         flash("Yo do not have the permissions to unlock this topic", "danger")
         flash("Yo do not have the permissions to unlock this topic", "danger")
-        return redirect(url_for("forum.view_topic", topic_id=topic.id))
+        return redirect(topic.url)
 
 
     topic.locked = False
     topic.locked = False
     topic.save()
     topic.save()
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)
 
 
 
 
 @forum.route("/topic/<int:topic_id>/move/<int:forum_id>")
 @forum.route("/topic/<int:topic_id>/move/<int:forum_id>")
+@forum.route("/topic/<int:topic_id>-<topic_slug>/move/<int:forum_id>-<forum_slug>")
 @login_required
 @login_required
-def move_topic(topic_id, forum_id):
+def move_topic(topic_id, forum_id, topic_slug=None, forum_slug=None):
     forum = Forum.query.filter_by(id=forum_id).first_or_404()
     forum = Forum.query.filter_by(id=forum_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if not topic.move(forum):
     if not topic.move(forum):
         flash("Could not move the topic to forum %s" % forum.title, "danger")
         flash("Could not move the topic to forum %s" % forum.title, "danger")
-        return redirect(url_for("forum.view_topic", topic_id=topic.id))
+        return redirect(topic.url)
 
 
     flash("Topic was moved to forum %s" % forum.title, "success")
     flash("Topic was moved to forum %s" % forum.title, "success")
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)
 
 
 
 
 @forum.route("/topic/<int:topic_id>/post/new", methods=["POST", "GET"])
 @forum.route("/topic/<int:topic_id>/post/new", methods=["POST", "GET"])
+@forum.route("/topic/<int:topic_id>-<slug>/post/new", methods=["POST", "GET"])
 @login_required
 @login_required
-def new_post(topic_id):
+def new_post(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if topic.forum.locked:
     if topic.forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
         flash("This forum is locked; you cannot submit new topics or posts.",
               "danger")
               "danger")
-        return redirect(url_for('forum.view_forum', forum_id=topic.forum.id))
+        return redirect(topic.forum.url)
 
 
     if topic.locked:
     if topic.locked:
         flash("The topic is locked.", "danger")
         flash("The topic is locked.", "danger")
-        return redirect(url_for("forum.view_forum", forum_id=topic.forum_id))
+        return redirect(topic.forum.url)
 
 
     if not can_post_reply(user=current_user, forum=topic.forum):
     if not can_post_reply(user=current_user, forum=topic.forum):
         flash("You do not have the permissions to delete the topic", "danger")
         flash("You do not have the permissions to delete the topic", "danger")
-        return redirect(url_for("forum.view_forum", forum_id=topic.forum_id))
+        return redirect(topic.forum.url)
 
 
     form = ReplyForm()
     form = ReplyForm()
     if form.validate_on_submit():
     if form.validate_on_submit():
@@ -320,18 +328,16 @@ def edit_post(post_id):
     if post.topic.forum.locked:
     if post.topic.forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
         flash("This forum is locked; you cannot submit new topics or posts.",
               "danger")
               "danger")
-        return redirect(url_for("forum.view_forum",
-                                forum_id=post.topic.forum.id))
+        return redirect(post.topic.forum.url)
 
 
     if post.topic.locked:
     if post.topic.locked:
         flash("The topic is locked.", "danger")
         flash("The topic is locked.", "danger")
-        return redirect(url_for("forum.view_forum",
-                                forum_id=post.topic.forum_id))
+        return redirect(post.topic.forum.url)
 
 
     if not can_edit_post(user=current_user, forum=post.topic.forum,
     if not can_edit_post(user=current_user, forum=post.topic.forum,
                          post_user_id=post.user_id):
                          post_user_id=post.user_id):
         flash("You do not have the permissions to edit this post", "danger")
         flash("You do not have the permissions to edit this post", "danger")
-        return redirect(url_for('forum.view_topic', topic_id=post.topic_id))
+        return redirect(post.topic.url)
 
 
     form = ReplyForm()
     form = ReplyForm()
     if form.validate_on_submit():
     if form.validate_on_submit():
@@ -339,7 +345,7 @@ def edit_post(post_id):
         post.date_modified = datetime.datetime.utcnow()
         post.date_modified = datetime.datetime.utcnow()
         post.modified_by = current_user.username
         post.modified_by = current_user.username
         post.save()
         post.save()
-        return redirect(url_for("forum.view_topic", topic_id=post.topic.id))
+        return redirect(post.topic.url)
     else:
     else:
         form.content.data = post.content
         form.content.data = post.content
 
 
@@ -348,13 +354,13 @@ def edit_post(post_id):
 
 
 @forum.route("/post/<int:post_id>/delete")
 @forum.route("/post/<int:post_id>/delete")
 @login_required
 @login_required
-def delete_post(post_id):
+def delete_post(post_id, slug=None):
     post = Post.query.filter_by(id=post_id).first_or_404()
     post = Post.query.filter_by(id=post_id).first_or_404()
 
 
     if not can_delete_post(user=current_user, forum=post.topic.forum,
     if not can_delete_post(user=current_user, forum=post.topic.forum,
                            post_user_id=post.user_id):
                            post_user_id=post.user_id):
         flash("You do not have the permissions to edit this post", "danger")
         flash("You do not have the permissions to edit this post", "danger")
-        return redirect(url_for('forum.view_topic', topic_id=post.topic_id))
+        return redirect(post.topic.url)
 
 
     topic_id = post.topic_id
     topic_id = post.topic_id
 
 
@@ -362,8 +368,7 @@ def delete_post(post_id):
 
 
     # If the post was the first post in the topic, redirect to the forums
     # If the post was the first post in the topic, redirect to the forums
     if post.first_post:
     if post.first_post:
-        return redirect(url_for('forum.view_forum',
-                                forum_id=post.topic.forum_id))
+        return redirect(post.topic.url)
     return redirect(url_for('forum.view_topic', topic_id=topic_id))
     return redirect(url_for('forum.view_topic', topic_id=topic_id))
 
 
 
 
@@ -382,7 +387,8 @@ def report_post(post_id):
 
 
 @forum.route("/markread")
 @forum.route("/markread")
 @forum.route("/<int:forum_id>/markread")
 @forum.route("/<int:forum_id>/markread")
-def markread(forum_id=None):
+@forum.route("/<int:forum_id>-<slug>/markread")
+def markread(forum_id=None, slug=None):
 
 
     if not current_user.is_authenticated():
     if not current_user.is_authenticated():
         flash("You need to be logged in for that feature.", "danger")
         flash("You need to be logged in for that feature.", "danger")
@@ -407,7 +413,7 @@ def markread(forum_id=None):
         db.session.add(forumsread)
         db.session.add(forumsread)
         db.session.commit()
         db.session.commit()
 
 
-        return redirect(url_for("forum.view_forum", forum_id=forum.id))
+        return redirect(forum.url)
 
 
     # Mark all forums as read
     # Mark all forums as read
     ForumsRead.query.filter_by(user_id=current_user.id).delete()
     ForumsRead.query.filter_by(user_id=current_user.id).delete()
@@ -465,16 +471,18 @@ def topictracker():
 
 
 
 
 @forum.route("/topictracker/<topic_id>/add")
 @forum.route("/topictracker/<topic_id>/add")
-def track_topic(topic_id):
+@forum.route("/topictracker/<topic_id>-<slug>/add")
+def track_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     current_user.track_topic(topic)
     current_user.track_topic(topic)
     current_user.save()
     current_user.save()
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)
 
 
 
 
 @forum.route("/topictracker/<topic_id>/delete")
 @forum.route("/topictracker/<topic_id>/delete")
-def untrack_topic(topic_id):
+@forum.route("/topictracker/<topic_id>-<slug>/delete")
+def untrack_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     current_user.untrack_topic(topic)
     current_user.untrack_topic(topic)
     current_user.save()
     current_user.save()
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)

+ 3 - 3
flaskbb/templates/forum/category_layout.html

@@ -3,7 +3,7 @@
     <thead class="categoryhead">
     <thead class="categoryhead">
         <tr>
         <tr>
             <td colspan="5">
             <td colspan="5">
-                <div><strong><a href="{{ url_for('forum.view_category', category_id=category[0].id) }}">{{ category[0].title }}</a></strong></div>
+                <div><strong><a href="{{ category[0].url }}">{{ category[0].title }}</a></strong></div>
             </td>
             </td>
         </tr>
         </tr>
     </thead>
     </thead>
@@ -50,7 +50,7 @@
             </td>
             </td>
 
 
             <td valign="top">
             <td valign="top">
-                <strong><a href="{{ url_for('forum.view_forum', forum_id=forum[0].id) }}">{{ forum[0].title }}</a></strong>
+                <strong><a href="{{ forum[0].url }}">{{ forum[0].title }}</a></strong>
 
 
                 <div class="forum-description">
                 <div class="forum-description">
                     {% autoescape false %}
                     {% autoescape false %}
@@ -72,7 +72,7 @@
 
 
             <td valign="top" align="right" style="white-space: nowrap">
             <td valign="top" align="right" style="white-space: nowrap">
                 {% if forum[0].last_post_id %}
                 {% if forum[0].last_post_id %}
-                <a href="{{ url_for('forum.view_post', post_id=forum[0].last_post_id) }}" title="{{ forum[0].last_post.topic.title }}">
+                <a href="{{ forum[0].last_post.url }}" title="{{ forum[0].last_post.topic.title }}">
                     <strong>{{ forum[0].last_post.topic.title|crop_title }}</strong>
                     <strong>{{ forum[0].last_post.topic.title|crop_title }}</strong>
                 </a>
                 </a>
                 <br />
                 <br />

+ 8 - 8
flaskbb/templates/forum/forum.html

@@ -7,18 +7,18 @@
 
 
 <ol class="breadcrumb">
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li><a href="{{ url_for('forum.view_category', category_id=forum[0].category.id) }}">{{ forum[0].category.title }}</a></li>
+    <li><a href="{{ forum[0].category.url }}">{{ forum[0].category.title }}</a></li>
     <li class="active">{{ forum[0].title }}</li>
     <li class="active">{{ forum[0].title }}</li>
 </ol>
 </ol>
 
 
 <div class="pull-left" style="padding-bottom: 10px">
 <div class="pull-left" style="padding-bottom: 10px">
-    {{ render_pagination(topics, url_for('forum.view_forum', forum_id=forum[0].id)) }}
+    {{ render_pagination(topics, forum[0].url) }}
 </div> <!-- end span pagination -->
 </div> <!-- end span pagination -->
 
 
 {% if current_user|post_topic(forum[0]) %}
 {% if current_user|post_topic(forum[0]) %}
 <div class="pull-right" style="padding-bottom: 10px">
 <div class="pull-right" style="padding-bottom: 10px">
     <div class="btn-group">
     <div class="btn-group">
-        <a href="{{ url_for('forum.markread', forum_id=forum[0].id) }}" class="btn btn-default">
+        <a href="{{ url_for('forum.markread', forum_id=forum[0].id, slug=forum[0].slug) }}" class="btn btn-default">
             <span class="fa fa-check"></span> Mark as Read
             <span class="fa fa-check"></span> Mark as Read
         </a>
         </a>
 
 
@@ -27,7 +27,7 @@
             <span class="fa fa-lock"></span> Locked
             <span class="fa fa-lock"></span> Locked
         </span>
         </span>
         {% else %}
         {% else %}
-        <a href="{{ url_for('forum.new_topic', forum_id=forum[0].id) }}" class="btn btn-primary">
+        <a href="{{ url_for('forum.new_topic', forum_id=forum[0].id, slug=forum[0].slug) }}" class="btn btn-primary">
             <span class="fa fa-pencil"></span> New Topic
             <span class="fa fa-pencil"></span> New Topic
         </a>
         </a>
         {% endif %}
         {% endif %}
@@ -72,12 +72,12 @@
             </td>
             </td>
             <td>
             <td>
                 <div>
                 <div>
-                    <a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}">{{ topic.title }}</a>
+                    <a href="{{ topic.url }}">{{ topic.title }}</a>
                     <!-- Topic Pagination -->
                     <!-- Topic Pagination -->
                     {{ topic_pages(topic, config["POSTS_PER_PAGE"]) }}
                     {{ topic_pages(topic, config["POSTS_PER_PAGE"]) }}
                     <br />
                     <br />
                     {% if topic.user_id %}
                     {% if topic.user_id %}
-                    <small>by <a href="{{ url_for('user.profile', username=topic.user.username) }}">{{ topic.user.username }}</a></small>
+                    <small>by <a href="{{ topic.user.url }}">{{ topic.user.username }}</a></small>
                     {% else %}
                     {% else %}
                     <small>by {{ topic.username }}</small>
                     <small>by {{ topic.username }}</small>
                     {% endif %}
                     {% endif %}
@@ -90,10 +90,10 @@
                 {{ topic.views }}
                 {{ topic.views }}
             </td>
             </td>
             <td>
             <td>
-                <a href="{{ url_for('forum.view_post', post_id=topic.last_post.id) }}">{{ topic.last_post.date_created|time_since }}</a><br />
+                <a href="{{ topic.last_post.url }}">{{ topic.last_post.date_created|time_since }}</a><br />
 
 
                 {% if topic.last_post.user_id %}
                 {% if topic.last_post.user_id %}
-                <small>by <a href="{{ url_for('user.profile', username=topic.last_post.user.username) }}">{{ topic.last_post.user.username }}</a></small>
+                <small>by <a href="{{ topic.last_post.user.url }}">{{ topic.last_post.user.username }}</a></small>
                 {% else %}
                 {% else %}
                 <small>{{ topic.last_post.username }}</small>
                 <small>{{ topic.last_post.username }}</small>
                 {% endif %}
                 {% endif %}

+ 1 - 1
flaskbb/templates/forum/index.html

@@ -25,7 +25,7 @@
                 Total number of posts: <strong>{{ post_count }}</strong> <br />
                 Total number of posts: <strong>{{ post_count }}</strong> <br />
             </td>
             </td>
             <td>
             <td>
-                Newest registered user: {% if newest_user %}<a href="{{ url_for('user.profile', username=newest_user.username) }}">{{ newest_user.username }}</a>{% else %}No users{% endif %}<br />
+                Newest registered user: {% if newest_user %}<a href="{{ newest_user.url }}">{{ newest_user.username }}</a>{% else %}No users{% endif %}<br />
                 Registered users online: <strong>{{ online_users }}</strong> <br />
                 Registered users online: <strong>{{ online_users }}</strong> <br />
                 {% if config["REDIS_ENABLED"] %}
                 {% if config["REDIS_ENABLED"] %}
                 Guests online: <strong>{{ online_guests }}</strong> <br />
                 Guests online: <strong>{{ online_guests }}</strong> <br />

+ 1 - 1
flaskbb/templates/forum/memberlist.html

@@ -35,7 +35,7 @@
         {% for user in users.items %}
         {% for user in users.items %}
         <tr>
         <tr>
             <td>{{ user.id }}</td>
             <td>{{ user.id }}</td>
-            <td><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></td>
+            <td><a href="{{ user.url }}">{{ user.username }}</a></td>
             <td>{{ user.post_count }}</td>
             <td>{{ user.post_count }}</td>
             <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
             <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
             <td>{{ user.primary_group.name }}</td>
             <td>{{ user.primary_group.name }}</td>

+ 2 - 2
flaskbb/templates/forum/new_post.html

@@ -7,8 +7,8 @@
 
 
 <ul class="breadcrumb">
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li><a href="{{ url_for('forum.view_forum', forum_id=topic.forum.id) }}">{{ topic.forum.title }}</a></li>
-    <li><a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}">{{ topic.title }} </a></li>
+    <li><a href="{{ topic.forum.url }}">{{ topic.forum.title }}</a></li>
+    <li><a href="{{ topic.url }}">{{ topic.title }} </a></li>
     <li class="active">New Post</li>
     <li class="active">New Post</li>
 </ul>
 </ul>
 
 

+ 1 - 1
flaskbb/templates/forum/new_topic.html

@@ -7,7 +7,7 @@
 
 
 <ul class="breadcrumb">
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li><a href="{{ url_for('forum.view_forum', forum_id=forum.id) }}">{{ forum.title }}</a></li>
+    <li><a href="{{ forum.url }}">{{ forum.title }}</a></li>
     <li class="active">New Topic</li>
     <li class="active">New Topic</li>
 </ul>
 </ul>
 
 

+ 14 - 14
flaskbb/templates/forum/topic.html

@@ -7,29 +7,29 @@
 
 
 <ol class="breadcrumb">
 <ol class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li><a href="{{ url_for('forum.view_category', category_id=topic.forum.category.id) }}">{{ topic.forum.category.title }}</a></li>
-    <li><a href="{{ url_for('forum.view_forum', forum_id=topic.forum.id) }}">{{ topic.forum.title }}</a></li>
+    <li><a href="{{ topic.forum.category.url }}">{{ topic.forum.category.title }}</a></li>
+    <li><a href="{{ topic.forum.url }}">{{ topic.forum.title }}</a></li>
     <li class="active">{{ topic.title }}</li>
     <li class="active">{{ topic.title }}</li>
 </ol>
 </ol>
 
 
 <div class="pull-left" style="padding-bottom: 10px">
 <div class="pull-left" style="padding-bottom: 10px">
-    {{ render_pagination(posts, url_for('forum.view_topic', topic_id=topic.id)) }}
+    {{ render_pagination(posts, topic.url) }}
 </div> <!-- end span pagination -->
 </div> <!-- end span pagination -->
 
 
 <div class="pull-right" style="padding-bottom: 10px">
 <div class="pull-right" style="padding-bottom: 10px">
     <div class="btn btn-group">
     <div class="btn btn-group">
     {% if current_user|delete_topic(topic.first_post.user_id, topic.forum) %}
     {% if current_user|delete_topic(topic.first_post.user_id, topic.forum) %}
-        <a href="{{ url_for('forum.delete_topic', topic_id=topic.id) }}" class="btn btn-danger">
+        <a href="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-danger">
             <span class="fa fa-trash-o"></span> Delete Topic
             <span class="fa fa-trash-o"></span> Delete Topic
         </a>
         </a>
     {% endif %}
     {% endif %}
     {% if current_user|lock_topic(topic.forum) %}
     {% if current_user|lock_topic(topic.forum) %}
         {% if not topic.locked %}
         {% if not topic.locked %}
-            <a href="{{ url_for('forum.lock_topic', topic_id=topic.id) }}" class="btn btn-warning">
+            <a href="{{ url_for('forum.lock_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-warning">
                 <span class="fa fa-lock"></span> Lock Topic
                 <span class="fa fa-lock"></span> Lock Topic
             </a>
             </a>
         {% else %}
         {% else %}
-            <a href="{{ url_for('forum.unlock_topic', topic_id=topic.id) }}" class="btn btn-warning">
+            <a href="{{ url_for('forum.unlock_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-warning">
                 <span class="fa fa-unlock"></span> Unlock Topic
                 <span class="fa fa-unlock"></span> Unlock Topic
             </a>
             </a>
         {% endif %}
         {% endif %}
@@ -39,17 +39,17 @@
     {% if current_user.is_authenticated() %}
     {% if current_user.is_authenticated() %}
     <div class="btn btn-group">
     <div class="btn btn-group">
         {% if current_user.is_tracking_topic(topic) %}
         {% if current_user.is_tracking_topic(topic) %}
-        <a href="{{ url_for('forum.untrack_topic', topic_id=topic.id) }}" class="btn btn-default"><span class="fa fa-star">
+        <a href="{{ url_for('forum.untrack_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-default"><span class="fa fa-star">
             </span> Untrack Topic
             </span> Untrack Topic
         </a>
         </a>
         {% else %}
         {% else %}
-        <a href="{{ url_for('forum.track_topic', topic_id=topic.id) }}" class="btn btn-default">
+        <a href="{{ url_for('forum.track_topic', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-default">
             <span class="fa fa-star"></span> Track Topic
             <span class="fa fa-star"></span> Track Topic
         </a>
         </a>
         {% endif %}
         {% endif %}
 
 
         {% if current_user|post_reply(topic.forum) and not (topic.locked or topic.forum.locked) %}
         {% if current_user|post_reply(topic.forum) and not (topic.locked or topic.forum.locked) %}
-        <a href="{{ url_for('forum.new_post', topic_id=topic.id) }}" class="btn btn-primary">
+        <a href="{{ url_for('forum.new_post', topic_id=topic.id, slug=topic.slug) }}" class="btn btn-primary">
             <span class="fa fa-pencil"></span> Reply
             <span class="fa fa-pencil"></span> Reply
         </a>
         </a>
         {% endif %}
         {% endif %}
@@ -68,15 +68,15 @@
                 <span class="pull-left">
                 <span class="pull-left">
                     <a href="
                     <a href="
                     {%- if posts.page > 1 -%}
                     {%- if posts.page > 1 -%}
-                        {{ url_for('forum.view_topic', topic_id=topic.id) }}?page={{ posts.page }}#pid{{ post.id }}
+                        {{ topic.url }}?page={{ posts.page }}#pid{{ post.id }}
                     {%- else -%}
                     {%- else -%}
-                        {{ url_for('forum.view_topic', topic_id=topic.id) }}#pid{{ post.id }}
+                        {{ topic.url }}#pid{{ post.id }}
                     {%- endif -%}
                     {%- endif -%}
                         ">{{ post.date_created|format_date('%d %B %Y') }}</a>
                         ">{{ post.date_created|format_date('%d %B %Y') }}</a>
                     {% if post.user_id and post.date_modified %}
                     {% if post.user_id and post.date_modified %}
                     <small>
                     <small>
                         (Last modified: {{ post.date_modified|format_date }} by
                         (Last modified: {{ post.date_modified|format_date }} by
-                        <a href="{{ url_for('user.profile', username=post.user.username) }}">
+                        <a href="{{ url_for('user.profile', username=post.modified_by) }}">
                             {{ post.modified_by }}
                             {{ post.modified_by }}
                         </a>.)
                         </a>.)
                     </small>
                     </small>
@@ -96,7 +96,7 @@
                     </td>
                     </td>
                     {% endif %}
                     {% endif %}
                     <td>
                     <td>
-                        <a href="{{ url_for('user.profile', username=post.user.username) }}">
+                        <a href="{{ post.user.url }}">
                             <span style="font-weight:bold">{{ post.user.username }}</span> <!-- TODO: Implement userstyles -->
                             <span style="font-weight:bold">{{ post.user.username }}</span> <!-- TODO: Implement userstyles -->
                         </a>
                         </a>
                             {%- if post.user|is_online %}
                             {%- if post.user|is_online %}
@@ -160,7 +160,7 @@
                     {% endif %}
                     {% endif %}
                     {% if topic.first_post_id == post.id %}
                     {% if topic.first_post_id == post.id %}
                         {% if current_user|delete_topic(topic.first_post.user_id, topic.forum) %}
                         {% if current_user|delete_topic(topic.first_post.user_id, topic.forum) %}
-                        <a href="{{ url_for('forum.delete_topic', topic_id=topic.id) }}">Delete</a> |
+                        <a href="{{ url_for('forum.delete_topic', topic_id=topic.id, slug=topic.slug) }}">Delete</a> |
                         {% endif %}
                         {% endif %}
                     {% else %}
                     {% else %}
                         {% if current_user|delete_post(post.user_id, topic.forum) %}
                         {% if current_user|delete_post(post.user_id, topic.forum) %}

+ 4 - 4
flaskbb/templates/forum/topictracker.html

@@ -47,9 +47,9 @@
             </td>
             </td>
             <td>
             <td>
                 <div>
                 <div>
-                    <a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}">{{ topic.title }}</a> <br />
+                    <a href="{{ topic.url }}">{{ topic.title }}</a> <br />
                     {% if topic.user_id %}
                     {% if topic.user_id %}
-                    <small>by <a href="{{ url_for('user.profile', username=topic.user.username) }}">{{ topic.user.username }}</a></small>
+                    <small>by <a href="{{ topic.user.url }}">{{ topic.user.username }}</a></small>
                     {% else %}
                     {% else %}
                     <small>by {{ topic.username }}</small>
                     <small>by {{ topic.username }}</small>
                     {% endif %}
                     {% endif %}
@@ -62,9 +62,9 @@
                 {{ topic.views }}
                 {{ topic.views }}
             </td>
             </td>
             <td>
             <td>
-                <a href="{{ url_for('forum.view_post', post_id=topic.last_post.id) }}">{{ topic.last_post.date_created|time_since }}</a><br />
+                <a href="{{ topic.last_post.url }}">{{ topic.last_post.date_created|time_since }}</a><br />
                 {% if topic.last_post.user_id %}
                 {% if topic.last_post.user_id %}
-                <small>by <a href="{{ url_for('user.profile', username=topic.last_post.user.username) }}">{{ topic.last_post.user.username }}</a></small>
+                <small>by <a href="{{ topic.last_post.user.url }}">{{ topic.last_post.user.username }}</a></small>
                 {% else %}
                 {% else %}
                 {{ topic.last_post.username }}
                 {{ topic.last_post.username }}
                 {% endif %}
                 {% endif %}

+ 3 - 3
flaskbb/templates/user/all_posts.html

@@ -4,7 +4,7 @@
 {% block content %}
 {% block content %}
 <ul class="breadcrumb">
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li ><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></li>
+    <li ><a href="{{ user.url }}">{{ user.username }}</a></li>
     <li class="active">All Posts</li>
     <li class="active">All Posts</li>
 </ul>
 </ul>
 
 
@@ -16,8 +16,8 @@
     {% for post in posts.items %}
     {% for post in posts.items %}
         <tr>
         <tr>
             <td>
             <td>
-                <strong><a href="{{ url_for('forum.view_topic', topic_id=post.topic.id)}}">{{ post.topic.title }}</a></strong>
-                in <a href="{{ url_for('forum.view_forum', forum_id=post.topic.forum.id) }}">{{ post.topic.forum.title }}</a>
+                <strong><a href="{{ post.topic.url }}">{{ post.topic.title }}</a></strong>
+                in <a href="{{ post.topic.forum.url }}">{{ post.topic.forum.title }}</a>
                 <span class="divider"> - </span>
                 <span class="divider"> - </span>
                 <small>{{ post.date_created|time_since }}</small>
                 <small>{{ post.date_created|time_since }}</small>
             </td>
             </td>

+ 4 - 4
flaskbb/templates/user/all_topics.html

@@ -37,8 +37,8 @@
             <td width="4%"></td>
             <td width="4%"></td>
             <td>
             <td>
                 <div>
                 <div>
-                    <a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}">{{ topic.title }}</a> <br />
-                    <small>by <a href="{{ url_for('user.profile', username=topic.user.username) }}">{{ topic.user.username }}</a></small>
+                    <a href="{{ topic.url }}">{{ topic.title }}</a> <br />
+                    <small>by <a href="{{ topic.user.url }}">{{ topic.user.username }}</a></small>
                 </div>
                 </div>
             </td>
             </td>
             <td>
             <td>
@@ -48,8 +48,8 @@
                 {{ topic.views }}
                 {{ topic.views }}
             </td>
             </td>
             <td>
             <td>
-                <a href="{{ url_for('forum.view_topic', topic_id=topic.id) }}#pid{{topic.last_post.id}}">{{ topic.last_post.date_created|time_since }}</a><br />
-                <small>by <a href="{{ url_for('user.profile', username=topic.last_post.user.username) }}">{{ topic.last_post.user.username }}</a></small>
+                <a href="{{ topic.last_post.url }}">{{ topic.last_post.date_created|time_since }}</a><br />
+                <small>by <a href="{{ topic.last_post.user.username }}">{{ topic.last_post.user.username }}</a></small>
             </td>
             </td>
         </tr>
         </tr>
         {% else %}
         {% else %}

+ 2 - 2
flaskbb/templates/user/profile.html

@@ -7,7 +7,7 @@
 
 
 <table class="table table-bordered">
 <table class="table table-bordered">
     <thead>
     <thead>
-      <th><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></th>
+      <th><a href="{{ user.url }}">{{ user.username }}</a></th>
       <th>Info</th>
       <th>Info</th>
       <th>User Stats</th>
       <th>User Stats</th>
     </thead>
     </thead>
@@ -63,7 +63,7 @@
                 <tr>
                 <tr>
                   <td align="right">Last post:</td>
                   <td align="right">Last post:</td>
                   <td>{%- if user.last_post -%}
                   <td>{%- if user.last_post -%}
-                      <a href="{{ url_for('forum.view_post', post_id=user.last_post.id) }}">{{ user.last_post.date_created|time_since }}</a>
+                      <a href="{{ user.last_post.url }}">{{ user.last_post.date_created|time_since }}</a>
                       {%- else -%}
                       {%- else -%}
                         Never
                         Never
                       {%- endif -%}
                       {%- endif -%}

+ 1 - 1
flaskbb/templates/user/settings_layout.html

@@ -4,7 +4,7 @@
 
 
 <ul class="breadcrumb">
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
-    <li><a href="{{ url_for('user.profile', username=current_user.username) }}">{{ current_user.username }}</a></li>
+    <li><a href="{{ current_user.url }}">{{ current_user.username }}</a></li>
     <li class="active">Settings</li>
     <li class="active">Settings</li>
 </ul>
 </ul>
 
 

+ 6 - 1
flaskbb/user/models.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 from itsdangerous import SignatureExpired
 from itsdangerous import SignatureExpired
 from werkzeug import generate_password_hash, check_password_hash
 from werkzeug import generate_password_hash, check_password_hash
-from flask import current_app
+from flask import current_app, url_for
 from flask.ext.login import UserMixin, AnonymousUserMixin
 from flask.ext.login import UserMixin, AnonymousUserMixin
 from flaskbb.extensions import db, cache
 from flaskbb.extensions import db, cache
 from flaskbb.forum.models import (Post, Topic, topictracker, TopicsRead,
 from flaskbb.forum.models import (Post, Topic, topictracker, TopicsRead,
@@ -121,6 +121,11 @@ class User(db.Model, UserMixin):
         return Post.query.filter(Post.user_id == self.id).\
         return Post.query.filter(Post.user_id == self.id).\
             order_by(Post.date_created.desc()).first()
             order_by(Post.date_created.desc()).first()
 
 
+    @property
+    def url(self):
+        """Returns the url for the user"""
+        return url_for("user.profile", username=self.username)
+
     # Methods
     # Methods
     def __repr__(self):
     def __repr__(self):
         """Set to a unique key specific to the object in the database.
         """Set to a unique key specific to the object in the database.

+ 19 - 0
flaskbb/utils/helpers.py

@@ -8,7 +8,9 @@
     :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.
 """
 """
+import re
 import time
 import time
+from unicodedata import normalize
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 from collections import OrderedDict
 from collections import OrderedDict
 
 
@@ -20,6 +22,23 @@ from postmarkup import render_bbcode
 
 
 from flaskbb.extensions import redis
 from flaskbb.extensions import redis
 
 
+_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
+
+
+def slugify(text, delim=u'-'):
+    """Generates an slightly worse ASCII-only slug.
+    Taken from the Flask Snippets page.
+
+   :param text: The text which should be slugified
+   :param delim: Default "-". The delimeter for whitespace
+    """
+    result = []
+    for word in _punct_re.split(text.lower()):
+        word = normalize('NFKD', word).encode('ascii', 'ignore')
+        if word:
+            result.append(word)
+    return unicode(delim.join(result))
+
 
 
 def render_template(template, **context):
 def render_template(template, **context):
     """A helper function that uses the `render_theme_template` function
     """A helper function that uses the `render_theme_template` function