Просмотр исходного кода

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 лет назад
Родитель
Сommit
2494f9eecf

+ 43 - 1
flaskbb/forum/models.py

@@ -10,10 +10,11 @@
 """
 from datetime import datetime, timedelta
 
-from flask import current_app
+from flask import current_app, url_for
 
 from flaskbb.extensions import db
 from flaskbb.utils.query import TopicQuery
+from flaskbb.utils.helpers import slugify
 
 
 moderators = db.Table(
@@ -148,6 +149,12 @@ class Post(db.Model):
     date_modified = db.Column(db.DateTime)
     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
     def __repr__(self):
         """
@@ -269,6 +276,16 @@ class Topic(db.Model):
         """Returns the second last post."""
         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
     def __init__(self, title=None):
         if title:
@@ -507,12 +524,24 @@ class Forum(db.Model):
     topics = db.relationship("Topic", backref="forum", lazy="joined",
                              cascade="all, delete-orphan")
 
+    # Many-to-many
     moderators = \
         db.relationship("User", secondary=moderators,
                         primaryjoin=(moderators.c.forum_id == id),
                         backref=db.backref("forummoderator", lazy="dynamic"),
                         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
     def __repr__(self):
         """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)',
                              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):
         """Saves a category"""
 

+ 47 - 39
flaskbb/forum/views.py

@@ -88,7 +88,8 @@ def index():
 
 
 @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():
         forum_query = Category.query.\
             filter(Category.id == category_id).\
@@ -118,7 +119,8 @@ def view_category(category_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)
 
     if current_user.is_authenticated():
@@ -152,7 +154,8 @@ def view_forum(forum_id):
 
 
 @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)
 
     topic = Topic.query.filter_by(id=topic_id).first()
@@ -198,24 +201,24 @@ def view_post(post_id):
     else:
         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>-<slug>/topic/new", methods=["POST", "GET"])
 @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()
 
     if forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
               "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):
         flash("You do not have the permissions to create a new topic.",
               "danger")
-        return redirect(url_for('forum.view_forum', forum_id=forum.id))
+        return redirect(forum.url)
 
     form = NewTopicForm()
     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>-<slug>/delete")
 @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()
 
     if not can_delete_topic(user=current_user, forum=topic.forum,
                             post_user_id=topic.first_post.user_id):
 
         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,
                                        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>-<slug>/lock")
 @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()
 
     if not can_lock_topic(user=current_user, forum=topic.forum):
         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.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>-<slug>/unlock")
 @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()
 
     # Unlock is basically the same as lock
     if not can_lock_topic(user=current_user, forum=topic.forum):
         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.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>-<topic_slug>/move/<int:forum_id>-<forum_slug>")
 @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()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
     if not topic.move(forum):
         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")
-    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>-<slug>/post/new", methods=["POST", "GET"])
 @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()
 
     if topic.forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
               "danger")
-        return redirect(url_for('forum.view_forum', forum_id=topic.forum.id))
+        return redirect(topic.forum.url)
 
     if topic.locked:
         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):
         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()
     if form.validate_on_submit():
@@ -320,18 +328,16 @@ def edit_post(post_id):
     if post.topic.forum.locked:
         flash("This forum is locked; you cannot submit new topics or posts.",
               "danger")
-        return redirect(url_for("forum.view_forum",
-                                forum_id=post.topic.forum.id))
+        return redirect(post.topic.forum.url)
 
     if post.topic.locked:
         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,
                          post_user_id=post.user_id):
         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()
     if form.validate_on_submit():
@@ -339,7 +345,7 @@ def edit_post(post_id):
         post.date_modified = datetime.datetime.utcnow()
         post.modified_by = current_user.username
         post.save()
-        return redirect(url_for("forum.view_topic", topic_id=post.topic.id))
+        return redirect(post.topic.url)
     else:
         form.content.data = post.content
 
@@ -348,13 +354,13 @@ def edit_post(post_id):
 
 @forum.route("/post/<int:post_id>/delete")
 @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()
 
     if not can_delete_post(user=current_user, forum=post.topic.forum,
                            post_user_id=post.user_id):
         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
 
@@ -362,8 +368,7 @@ def delete_post(post_id):
 
     # If the post was the first post in the topic, redirect to the forums
     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))
 
 
@@ -382,7 +387,8 @@ def report_post(post_id):
 
 @forum.route("/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():
         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.commit()
 
-        return redirect(url_for("forum.view_forum", forum_id=forum.id))
+        return redirect(forum.url)
 
     # Mark all forums as read
     ForumsRead.query.filter_by(user_id=current_user.id).delete()
@@ -465,16 +471,18 @@ def topictracker():
 
 
 @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()
     current_user.track_topic(topic)
     current_user.save()
-    return redirect(url_for("forum.view_topic", topic_id=topic.id))
+    return redirect(topic.url)
 
 
 @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()
     current_user.untrack_topic(topic)
     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">
         <tr>
             <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>
         </tr>
     </thead>
@@ -50,7 +50,7 @@
             </td>
 
             <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">
                     {% autoescape false %}
@@ -72,7 +72,7 @@
 
             <td valign="top" align="right" style="white-space: nowrap">
                 {% 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>
                 </a>
                 <br />

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

@@ -7,18 +7,18 @@
 
 <ol class="breadcrumb">
     <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>
 </ol>
 
 <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 -->
 
 {% if current_user|post_topic(forum[0]) %}
 <div class="pull-right" style="padding-bottom: 10px">
     <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
         </a>
 
@@ -27,7 +27,7 @@
             <span class="fa fa-lock"></span> Locked
         </span>
         {% 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
         </a>
         {% endif %}
@@ -72,12 +72,12 @@
             </td>
             <td>
                 <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_pages(topic, config["POSTS_PER_PAGE"]) }}
                     <br />
                     {% 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 %}
                     <small>by {{ topic.username }}</small>
                     {% endif %}
@@ -90,10 +90,10 @@
                 {{ topic.views }}
             </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 %}
-                <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 %}
                 <small>{{ topic.last_post.username }}</small>
                 {% endif %}

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

@@ -25,7 +25,7 @@
                 Total number of posts: <strong>{{ post_count }}</strong> <br />
             </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 />
                 {% if config["REDIS_ENABLED"] %}
                 Guests online: <strong>{{ online_guests }}</strong> <br />

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

@@ -35,7 +35,7 @@
         {% for user in users.items %}
         <tr>
             <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.date_joined|format_date('%b %d %Y') }}</td>
             <td>{{ user.primary_group.name }}</td>

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

@@ -7,8 +7,8 @@
 
 <ul class="breadcrumb">
     <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>
 </ul>
 

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

@@ -7,7 +7,7 @@
 
 <ul class="breadcrumb">
     <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>
 </ul>
 

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

@@ -7,29 +7,29 @@
 
 <ol class="breadcrumb">
     <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>
 </ol>
 
 <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 class="pull-right" style="padding-bottom: 10px">
     <div class="btn btn-group">
     {% 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
         </a>
     {% endif %}
     {% if current_user|lock_topic(topic.forum) %}
         {% 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
             </a>
         {% 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
             </a>
         {% endif %}
@@ -39,17 +39,17 @@
     {% if current_user.is_authenticated() %}
     <div class="btn btn-group">
         {% 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
         </a>
         {% 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
         </a>
         {% endif %}
 
         {% 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
         </a>
         {% endif %}
@@ -68,15 +68,15 @@
                 <span class="pull-left">
                     <a href="
                     {%- 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 -%}
-                        {{ url_for('forum.view_topic', topic_id=topic.id) }}#pid{{ post.id }}
+                        {{ topic.url }}#pid{{ post.id }}
                     {%- endif -%}
                         ">{{ post.date_created|format_date('%d %B %Y') }}</a>
                     {% if post.user_id and post.date_modified %}
                     <small>
                         (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 }}
                         </a>.)
                     </small>
@@ -96,7 +96,7 @@
                     </td>
                     {% endif %}
                     <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 -->
                         </a>
                             {%- if post.user|is_online %}
@@ -160,7 +160,7 @@
                     {% endif %}
                     {% if topic.first_post_id == post.id %}
                         {% 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 %}
                     {% else %}
                         {% if current_user|delete_post(post.user_id, topic.forum) %}

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

@@ -47,9 +47,9 @@
             </td>
             <td>
                 <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 %}
-                    <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 %}
                     <small>by {{ topic.username }}</small>
                     {% endif %}
@@ -62,9 +62,9 @@
                 {{ topic.views }}
             </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 %}
-                <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 %}
                 {{ topic.last_post.username }}
                 {% endif %}

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

@@ -4,7 +4,7 @@
 {% block content %}
 <ul class="breadcrumb">
     <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>
 </ul>
 
@@ -16,8 +16,8 @@
     {% for post in posts.items %}
         <tr>
             <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>
                 <small>{{ post.date_created|time_since }}</small>
             </td>

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

@@ -37,8 +37,8 @@
             <td width="4%"></td>
             <td>
                 <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>
             </td>
             <td>
@@ -48,8 +48,8 @@
                 {{ topic.views }}
             </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>
         </tr>
         {% else %}

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

@@ -7,7 +7,7 @@
 
 <table class="table table-bordered">
     <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>User Stats</th>
     </thead>
@@ -63,7 +63,7 @@
                 <tr>
                   <td align="right">Last post:</td>
                   <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 -%}
                         Never
                       {%- endif -%}

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

@@ -4,7 +4,7 @@
 
 <ul class="breadcrumb">
     <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>
 </ul>
 

+ 6 - 1
flaskbb/user/models.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 from itsdangerous import SignatureExpired
 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 flaskbb.extensions import db, cache
 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).\
             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
     def __repr__(self):
         """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.
     :license: BSD, see LICENSE for more details.
 """
+import re
 import time
+from unicodedata import normalize
 from datetime import datetime, timedelta
 from collections import OrderedDict
 
@@ -20,6 +22,23 @@ from postmarkup import render_bbcode
 
 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):
     """A helper function that uses the `render_theme_template` function