Add Title or Title Tag to url path

I'm hoping somebody can help me with this issue. Im having trouble adding my page/ article title to the url path. I've tried a number of ways can't seem to get it. If anyone could help that would be great.

My current Url path is "https://stackoverflow.com/article/1"

Would like it to be "https://stackoverflow.com/article/1/example-question-help", or some variation of that.

Below you can find how my views and url files are set up.

 path('article/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),'

class ArticleDetailView(DetailView):
    model = Post
    template_name = 'article_detail.html'

    def get_context_data(self, *args, **kwargs):
        cat_menu = Category.objects.all()
        stuff = get_object_or_404(Post, id=self.kwargs['pk'])
        total_likes = stuff.total_likes()
        liked = False
        if stuff.likes.filter(id=self.request.user.id).exists():
            liked = True
        context = super(ArticleDetailView, self).get_context_data(*args, **kwargs)
        context["cat_menu"] = cat_menu
        context["total_likes"] = total_likes
        context["liked"] = liked
        return context

Assuming that your model has a slug field, called slug, which it looks like it may given your request, you'd change things like this;

path('article/<slug:slug>/', ArticleDetailView.as_view(), name='article-detail'),'

Django will then do the rest because SingleObjectMixin which is used by DetailView looks at the URL first for a primary key, then for a slug.

So this will give you URLs that look like;

https://stackoverflow.com/article/example-question-help

You can define a path that includes both the primary key and the slug:

path(
    'article/<int:pk>/<slug:slug>/',
    ArticleDetailView.as_view(),
    name='article-detail',
),

This will automatically filter the item properly. You can boost efficiency by determining the number of likes and whether the object is liked all in the same queryset with an Exists subquery [Django-doc]:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Exists, OuterRef


class ArticleDetailView(LoginRequiredMixin, DetailView):
    model = Post
    template_name = 'article_detail.html'
    queryset = Post.objects.annotate(total_likes=Count('likes'))

    def get_queryset(self, *args, **kwargs):
        super().get_queryset(*args, **kwargs).annotate(
            is_liked=Exists(
                Post.likes.through.objects.filter(
                    post_id=OuterRef('pk'), user=request.user
                )
            )
        )

    def get_context_data(self, *args, **kwargs):
        return super().get_context_data(
            *args, **kwargs, cat_menu=Category.objects.all()
        )

In the modeling, you might want to work with an AutoSlugField [readthedocs.io] from the django-autoslug package [readthedocs.io] to automatically slugify. Otherwise you will have to do this yourself. It also makes not much sense that the slug field is NULLable: normally a record will always have a slug. You thus might want to refactor the model to:

from autoslug import AutoSlugField
from django.conf import settings


class Post(models.Model):
    title = models.CharField(max_length=250)
    header_image = models.ImageField(null=True, blank=True, upload_to='images/')
    title_tag = models.CharField(max_length=250, default='none')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    body = RichTextField(blank=True, null=True)
    slug = models.AutoSlugField(populate_from='title')
    post_date = models.DateField(auto_now_add=True)
    category = models.CharField(max_length=250, default='')
    snippet = models.CharField(max_length=250)
    likes = models.ManyToManyField(
        settings.AUTH_USER_MODEL, related_name='liked_posts'
    )

Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Back to Top