2

I'm developing a simple blog to learn Django. I would like to have a path for each posts like this:

  • /category-1/title-post
  • /category-2/title-post
  • etc..

Below urls.py

from django.urls import include, path
from .views import CategoryList, PostList, SingleCategory, SinglePost, SingleTag, TagList

urlpatterns = [
        path("", PostList.as_view(), name="list_post"),
        path("<slug:slug>", SinglePost.as_view(), name="single_post"),
        path("tags/", TagList.as_view(), name="list_tag"),
        path("tags/<slug:slug>", SingleTag.as_view(), name="single_tag"),
        path("categories/", CategoryList.as_view(), name="list_category"),
        path("categories/<slug:slug>", SingleCategory.as_view(), name="single_category"),
]

And views.py

from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView

from .models import Category, Post, Tag
# Create your views here.

class CategoryList(ListView):
    model = Category
    context_object_name = 'category_list'
    template_name = "list_category.html"


class SingleCategory(DetailView):
    model = Category
    template_name = "single_category.html"


class PostList(ListView):
    model = Post
    queryset = Post.objects.order_by('-id')
    context_object_name = 'post_list'
    template_name = "list_post.html"
    paginate_by = 4


class SinglePost(DetailView):
    model = Post
    template_name = "single_post.html"


class TagList(ListView):
    model = Tag
    context_object_name = 'tag_list'
    template_name = "list_tag.html"


class SingleTag(DetailView):
    model = Tag
    template_name = "single_tag.html"

Here models.py

class Category(models.Model):
    category_name = models.CharField(
                max_length=50,
                )
    slug = models.SlugField(
                unique=True,
                )

    def __str__(self):
        return self.category_name

    def get_absolute_url(self):
        return reverse("single_category", kwargs={"slug": self.slug})


class Post(models.Model):
    title = models.CharField(
                max_length=50,
                )
    slug = models.SlugField(
                unique=True,
                )
    content = models.TextField()
    tag = models.ManyToManyField(
                Tag,
                related_name="tag_set",
                )
    category = models.ForeignKey(
                    Category,
                    on_delete=models.CASCADE,
                    related_name="category_set",
                    )
    highlighted = models.BooleanField(
                    default=False,
                    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("single_post", kwargs={"slug": self.slug})

    class Meta:
        ordering = ['-id']

I don't understand how I can change the path "categories/" in "category-slug/". I would like to do a same thing for categories / , that must change in category-slug/post-slug.

How I can do this using the Class Based View?

MaxDragonheart
  • 1,117
  • 13
  • 34

2 Answers2

4

You can define as many parameters in your URL as you like. Then you'll need to override get_object to get the relevant post by slug and category.

path('<slug:category_slug>/<slug:post_slug>', SinglePostByCategory.as_view(), 'single_post_by_category')

...

class SinglePostByCategory(DetailView):
    def get_queryset(self):
        return get_object_or_404(Post,
            category__slug=self.kwargs['category_slug'],
            slug=self.kwargs['post_slug']
        )
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • I've edited my post for add models.py. If I use your solution it's shown this error: `django.urls.exceptions.NoReverseMatch: Reverse for 'single_post' with keyword arguments '{'slug': 'articolo-due'}' not found. 1 pattern(s) tried: ['(?P[-a-zA-Z0-9_]+)\\/(?P[-a-zA-Z0-9_]+)$']` But I think that the problem is a mistake given by my not strong skill with Django. Can you review your past post using the information from models.py? – MaxDragonheart Nov 16 '18 at 10:53
  • 1
    This doesn't seem to be related to my answer at all. Did you change the "single_post" URL rather than adding a separate "single_post_by_category"? – Daniel Roseman Nov 16 '18 at 11:36
  • You are right! I've correct my mistake and I've used your path with only difference in the name of the view. Now in 127.0.0.1:8000 I've this error: `django.urls.exceptions.NoReverseMatch: Reverse for 'single_post' not found. 'single_post' is not a valid view function or pattern name.` . If I use this path 127.0.0.1:8000/argomento-1/articolo-uno (I'm sure that is correct), I've this error: `AttributeError: Generic detail view SinglePost must be called with either an object pk or a slug in the URLconf.` – MaxDragonheart Nov 16 '18 at 12:09
  • @DanielRoseman Should not we override the get_object method instead of get_queryset? I have overridden get_object method like this: class SinglePostByCategory(DetailView): def get_object(self, queryset=None): return get_object_or_404(Post, category__slug=self.kwargs['category_slug'], slug=self.kwargs['post_slug'] ) - and have it working nicely - I think you need to correct the answer... – Radek Jul 03 '19 at 23:31
0

Override the get_object.

class SinglePostByCategory(DetailView):

    def get_object(self):
        obj = get_object_or_404(Post, category__slug=self.kwargs['category_slug'], slug=self.kwargs['post_slug'] )
        return obj
dsixnine
  • 101
  • 1
  • 5