pretty and seo friendly urls in django
Asked Answered
D

5

14

I am currently writing a web-blog, learning django. I need a view to display a single blog-post and my first try was to create a url for it like the following:

myblog.com/blog/view/1

This uses the blog-id to identify the specified blog-post.

Now if you look at many blogs/website, you see that they use the title of a blog-post in the url, this is because this is more search-engine friendly, so it can be easier found. This might look like this.

myblog.com/blog/view/a-python-backup-script

How do I implement this in django?

Bonus Question: A lot of sites also include the month and the year of a post. I guess this has also to do with SEO, but how exactly is that useful?

Disinterested answered 9/8, 2012 at 8:25 Comment(0)
T
27

Add a slug field to your Blog model.

from django.template.defaultfilters import slugify

Class Blog(models.Model):
    title = models.CharField(max_length=40)
    slug = models.SlugField(_('slug'), max_length=60, blank=True)

    #Then override models save method:
    def save(self, *args, **kwargs):
        if not self.id:
            #Only set the slug when the object is created.
            self.slug = slugify(self.title) #Or whatever you want the slug to use
        super(Blog, self).save(*args, **kwargs)

In your urls.py

(r'^blog/view/(?P<slug>[-\w]+)/$', 'app.views.blog_view'),

In views.py

def blog_view(request, slug):
    blog = Blog.objects.get(slug=slug)
    #Then do whatever you want

EDIT: I added a check in the save method since you want the slug to be created when the object is created. It shouldn't be saved everytime.

Twirl answered 9/8, 2012 at 8:38 Comment(2)
What if two blog posts have the same title?Erick
just add the ID tooSipes
S
8

Make sure your model actually has a slug field:

class BlogPost(models.Model):
    slug = models.SlugField(unique=True)

and that you have a view:

from django.shortcuts import get_object_or_404
def blog_detail(request, slug):
    ...
    post = get_object_or_404(BlogPost, slug=slug)
    ...
    render(request, "blog/blog_post.detail.html", { 'blog_post' : post })

and then in your urls.py, you can specify a slug:

url(r'^(?P<slug>[-w]+)/$', 'blog.views.blog_detail', {}, name="blog_detail"),

the first argument is a regular expression, that when matched, will run the view blog_detail view and pass the matched slug group from the regular expression to thew view (which will in turn render and return a template)

Regarding your last point: I find that as well as potentially being positive in terms of SEO, having the dates in the url makes it much easier for me to see if the blog post is new at a glance. Also, in Django, it is very easy to use this approach along with date-based generic views which will cut down on the amount of boiler plate view code you need to write. This would be an example:

url(r'(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/(?P<slug>[-w]+)/$', 
        'django.views.generic.date_based.object_detail', 
        { template_name = "blog/detail.html", ... }, 
        name="blog_detail"),
Shuttering answered 9/8, 2012 at 8:35 Comment(0)
P
2

Or, if you're using Class-based views, the most basic thing you could do is:

from django.views.generic import DetailView
from models import Blog

class BlogView(DetailView):
    model = Blog
    template_name = "blog/blog_detail.html"

Then, the url looks something like this:

from views import BlogView

url(r'^(?P<slug>[-w]+)/$', BlogView._as_view(), name="blog_detail"),

Note that Django's generic DetailView expects either a pk or a slug. So using a slug is no different from using a pk in this case.

Particia answered 9/8, 2012 at 8:57 Comment(0)
S
2

This similar method is backwards compatible with urls that us a numerical id filed.

Add a slug field and a save definition in models.py:

from django.template.defaultfilters import slugify

slug = models.SlugField(default='no-slug', max_length=60, blank=True)

def save(self, *args, **kwargs):
    #save a slug if there is no slug or when it's 'no-slug' (the default slug)
    if not self.slug or self.slug == 'no-slug':
        self.slug = slugify(self.name)
    super(Project, self).save(*args, **kwargs)

Add a second url pattern in urls.py:

#original:
url(r'^(?P<id>\d+)/$', 'project.views.view', name='view_url'),
#new pattern added under original:
url(r'^(?P<id>\d+)-(?P<slug>[-\w\d]+)/$', 'project.views.view', name='view_url'),

In views.py let slug pass through:

def view(request, mid=None, slug=None):

Then all you need to do to use this URL pattern is edit models.py:

def get_absolute_url(self):
    return reverse('view_url', args=[self.id, self.slug])
Staysail answered 9/3, 2015 at 19:4 Comment(0)
T
0

django-autoslug works nicely for this purpose and has lots of useful options.

Took answered 15/10, 2014 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.