NoReverseMatch: Reverse not found from model with get_absolute_url

Humbly asking for the community’s help; looked through earlier similar questions, but they didn’t help me.

I’m creating a forum project (as a part of a site), it’s structure is roughly: “Forum → subforums → topics → comments”. So, opening “site/forum” page, there would be several forum sections (“subforums”), clicking on each of them will lead to the list of corresponding topics (not all of them, but only made within that subforum specially - that’s what I mean by the word “corresponding” here); clicking on every topic will lead to the topic’s content and its corresponding comments.

I use slug, class-based-views, get_absolute_url method in models, and “app_name” in urls.py.
In my DB there are 2 filler topics, one for “News” subforum, another for “Production” subforum. For some certain long time I’ve tried to organize the slug for topics the way like (f"topic-{self.id}"), but failed in that undertaking, thus choosing the slugify option.

And now, when the problem with slug was solved, and forum page became achievable, filled with buttons for subforums, after clicking on subforum buttons to check whether the list of topics is displayed there, django debug returns an error:

“django.urls.exceptions.NoReverseMatch: Reverse for ‘topic’ with keyword arguments ‘{‘topic_slug’: ‘topic-scenario-timing-plan’}’ not found. 1 pattern(s) tried: [‘forum/(?P<subforum_slug>[-a-zA-Z0-9_]+)/(?P<topic_slug>[-a-zA-Z0-9_]+)/\Z’]”

And I am kind of ignorant of where from this problem proceeds. ‘topic-scenario-timing-plan’ is a slug of one of the filler topics I’ve created. If I click on any of subforums’ buttons, the same error will be displayed.

Below are corresponding files, which might be useful to look at:
models.py:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.text import slugify

from .consts import *


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    surname = models.CharField(max_length=32, default='')
    name = models.CharField(max_length=32, default='')
    email = models.EmailField(max_length=254, blank=True, unique=True)
    bio = models.TextField(max_length=500, default="Write a couple of words about yourself")
    avatar = models.ImageField(default=None, blank=True, max_length=255)
    status = models.CharField(max_length=25, blank=True, default='')
    slug = models.SlugField()
    age = models.IntegerField(verbose_name='Возраст', null=True, blank=True)
    gender = models.CharField(verbose_name='Пол', max_length=32, choices=Genders.GENDER_CHOICES, default="H", blank=True)
    reputation = models.IntegerField(verbose_name='Репутация', default=0)

    def __str__(self):
        return f'{self.user} profile'

    def get_absolute_url(self):
        return reverse('forum:user_profile', kwargs={'profile_slug': self.slug})

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slugify(self.user.username)
            return super(Profile, self).save(*args, **kwargs)


class Subforum(models.Model):
    title = models.CharField(verbose_name='Название', max_length=32, choices=Theme.THEME_CHOICES, default=1)
    slug = models.SlugField(default='News')
    objects = models.Manager()

    class Meta:
        ordering = ['title']
        verbose_name = 'Разделы форума'
        verbose_name_plural = 'Разделы форума'

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = slugify(self.title)
            return super(Subforum, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('forum:subforum', kwargs={'subforum_slug': self.slug})


class Topic(models.Model):
    subject = models.CharField(verbose_name='Заголовок', max_length=255, unique=True)
    first_comment = models.TextField(verbose_name='Сообщение', max_length=2000, default='')
    slug = models.SlugField(default='', unique=True, max_length=25, editable=False)
    subforum = models.ForeignKey('Subforum',
                                 verbose_name='Раздел',
                                 on_delete=models.CASCADE,
                                 related_name='subforum')
    creator = models.ForeignKey(User,
                                verbose_name='Создатель темы',
                                on_delete=models.SET('deleted'),
                                related_name='creator')
    created = models.DateTimeField(auto_now_add=True)
    closed = models.BooleanField(default=False)
    objects = models.Manager()

    class Meta:
        ordering = ['id']
        verbose_name = 'Обсуждения'
        verbose_name_plural = 'Обсуждения'

    def __str__(self):
        return self.subject

    def save(self, *args, **kwargs):
        if not self.id:
            self.slug = f'topic-{slugify(self.subject)}'
            return super(Topic, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('forum:topic', kwargs={'topic_slug': self.slug})


class Comment(models.Model):
    topic = models.ForeignKey('Topic',
                              verbose_name='Тема',
                              on_delete=models.CASCADE,
                              related_name='topic')
    author = models.ForeignKey(User,
                               verbose_name='Комментатор',
                               on_delete=models.SET('deleted'),
                               related_name='author')
    content = models.TextField(verbose_name='Текст', max_length=2000)
    created = models.DateTimeField(verbose_name='Дата публикации', auto_now_add=True)
    updated = models.DateTimeField(verbose_name='Дата изменения', auto_now=True)
    objects = models.Manager()

    class Meta:
        ordering = ['created']
        verbose_name = 'Комментарии'
        verbose_name_plural = 'Комментарии'

    def __str__(self):
        return f'Post of {self.topic.subject} is posted by {self.author.username}.'

views.py:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from core.views import menu
from .forms import AddTopicForm, AddCommentForm
from .models import Subforum, Topic, Comment, Profile
from .utils import DataMixin


class SubForumListView(ListView):
    model = Subforum
    context_object_name = 'subforum_list'
    template_name = "forum/forum.html"

    def get_context_data(self, **kwargs):
        subforums = Subforum.objects.all()
        context = {'subforums': subforums}
        return context


class TopicListView(ListView):
    model = Topic
    template_name = "forum/subforum.html"
    slug_url_kwarg = 'subforum_slug'
    context_object_name = 'subforum'

    def get_context_data(self, **kwargs):
        topics = Topic.objects.all()
        context = {'topics': topics}
        return context


class ShowTopic(DetailView):
    model = Topic
    template_name = "forum/topic.html"
    slug_url_kwarg = 'topic_slug'
    context_object_name = 'topic'

    def get_context_data(self, topic_slug, **kwargs):
        topic = get_object_or_404(Topic, slug=topic_slug)
        comments = Comment.objects.filter(topic=topic)
        comments_number = len(Comment.objects.filter(topic=topic))
        context = {'menu': menu,
                   'topic': topic,
                   'comments': comments,
                   'comm_num': comments_number}
        return context


class AddTopic(LoginRequiredMixin, DataMixin, CreateView):
    form_class = AddTopicForm
    template_name = 'forum/addtopic.html'
    page_title = 'Создание новой темы'


class AddComment(LoginRequiredMixin, DataMixin, CreateView):
    form_class = AddCommentForm
    template_name = 'forum/addcomment.html'
    page_title = 'Оставить комментарий'
    success_url = reverse_lazy('topic')


class UpdateComment(LoginRequiredMixin, DataMixin, UpdateView):
    form_class = AddCommentForm
    template_name = 'forum/addcomment.html'
    page_title = 'Редактировать комментарий'
    success_url = reverse_lazy('topic')


class UserProfile(DetailView):
    model = Profile
    template_name = "profile.html"

urls.py:

from django.urls import path

from forum.views import *


app_name = 'forum'

urlpatterns = [
    path('', SubForumListView.as_view(), name='forum'),
    path('<slug:subforum_slug>/', TopicListView.as_view(), name='subforum'),
    path('<slug:subforum_slug>/<slug:topic_slug>/', ShowTopic.as_view(), name='topic'),
    path('<slug:subforum_slug>/add-topic/', AddTopic.as_view(), name="add_topic"),
    path('<slug:subforum_slug>/<slug:topic_slug>/add-comment/', AddComment.as_view(), name="add_comment"),
    path('<slug:subforum_slug>/<slug:topic_slug>/edit/<int:id>/', UpdateComment.as_view(), name="edit_comment"),
]

If some additional files/information are necessary to solve the problem, I’d be glad to provide them.
Thank you for your help in advance.

Your topic URL uses both subforum_slug and topic_slug, but Topic.get_absolute_url only passes topic_slug; try something like this:

return reverse('forum:topic', kwargs={'topic_slug': self.slug, 'subforum_slug': self.subforum.slug})

You need to pass all placeholders, there’s no magic inheritance or something. The same may apply to other URLs in your project, I didn’t check them.

Indeed! :slight_smile: Thanks a lot, that really did help! Kind of obvious, but not so easily tracked nuance.

1 Like