Add Title or Title Tag to url path

Viewed 46

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.

<a href="{% url 'article-detail' post.pk %}"
 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
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(User, on_delete=models.CASCADE)
    body = RichTextField(blank=True, null=True)
    slug = models.SlugField(null=True)
    # body = models.TextField()
    post_date = models.DateField(auto_now_add=True)
    category = models.CharField(max_length=250, default='')
    snippet = models.CharField(max_length=250)
    likes = models.ManyToManyField(User, related_name='blog_posts')
2 Answers

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. In the link you then pass both the primary key and slug:

<a href="{% url 'article-detail' post.pk post.slug %}">

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.

Related