Coderaemon
Coderaemon

Reputation: 3867

Passing Pk or Slug to Generic DetailView in Django?

I am new to Django Class based views. I am trying to make a simple view to get details of a post. My views.py:

from django.views.generic import ListView, View, DetailView 
class GenreDetail(DetailView):
            model = Post
            template_name = "post.html"

My urls.py:

urlpatterns = [
        url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
        url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
    ] 

Error that I get:

AttributeError at /2/memoirs-of-a-geisha-by-arthur-golden
Generic detail view GenreDetail must be called with either an object pk or a slug.

So the pk or slug is not passed to the Generic Detailview. How do I pass that ? I assume from url it can pick up but it's not.

Upvotes: 14

Views: 34096

Answers (4)

Ivan Ogai
Ivan Ogai

Reputation: 1486

Using path:

from django.urls import path
from . import views

urlpatterns = [
    path('<pk>/', views.GenreDetail.as_view(), name="post")]

For slug:

path('<slug:slug>/', views.GenreDetail.as_view(), name="post")

Upvotes: 0

Ozgur Vatansever
Ozgur Vatansever

Reputation: 52153

The problem is that you have to tell DetailView that it should use post_id keyword in the URL instead of default ones pk or slug in order to get the object that will be displayed.

This can be done by setting pk_url_kwarg attribute:

(Your url definition is also wrong, always end your url definitions with $. Below is the corrected version)

url(r'(?P<post_id>\d+)$', GenreDetail.as_view(), name = 'post'),
url(r'(?P<post_id>\d+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),

The following urls will match given the url patterns above:

  • /2
  • /2/memoirs-of-a-geisha-by-arthur-golden

from django.views.generic import DetailView 

class GenreDetail(DetailView):
    model = Post
    template_name = "post.html"
    pk_url_kwarg = "post_id"

Alternatively, you can just change post_id to pk in your url so you don't have to touch anything in your view:

url(r'(?P<pk>\d+)$', GenreDetail.as_view(), name = 'post'),
url(r'(?P<pk>\d+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),

Upvotes: 4

Rajesh Kaushik
Rajesh Kaushik

Reputation: 1511

If you want to fetch details using either post_id or slug then your urls should be like this

url(r'post/(?P<post_id>\d+)/$', GenreDetail.as_view(), name = 'post_detail'),
url(r'post/(?P<slug>[-\w]+)/$', GenreDetail.as_view(), name = 'post_detail_slug'),

And your view should be like this

from django.views.generic import DetailView 

class GenreDetail(DetailView):
    model = Post
    template_name = "post.html"
    pk_url_kwarg = "post_id"
    slug_url_kwarg = 'slug'
    query_pk_and_slug = True

For more details please read the docs.

Upvotes: 11

Anentropic
Anentropic

Reputation: 33833

url patterns are checked in the order you define them

so here:

urlpatterns = [
        url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
        url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
    ] 

...the first pattern is getting matched (because it does not end with $ so the extra segment is just ignored)

...and that pattern only passes a single keyword arg

Generally it is a bad idea to have multiple url patterns pointing to the same view. If possible you should try and make a single regex (eg using optional groups) which handles the various cases of the url for a particular view. It's more explicit that way.

On the other hand, simply reversing the order of your patterns to put the more explicit one first would also work and be correct (this is the Django rule of urlpatterns!)

urlpatterns = [
        url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
        url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
    ] 

As @ozgur mentions you also need to tell the view to use post_id instead of pk by setting pk_url_kwarg

Upvotes: 14

Related Questions