I'm testing views in my Django app. As the app is a backend of a forum site, I'm trying to test the creation, editing and deletion of a topic. Creation, editing and deletion of a topic are implemented in my app to work via redirect: creation page (AddTopic) redirects to a succefully created topic's page; editing the topic's initial comment (UpdateFirstComment) redirects from the editing page to the edited topic's page; deletion page (DeleteTopic) redirects to a subforum (a chapter of a forum) where the deleted topic had belonged. I presume (I'm not sure; and, most possibly, here is my mistake) that the successful redirect code is 302, and in the tests' assertion that's the code which should be checked. But in practice, creation and editing tests return code 200, while the deletion test returns code 403. And I, due to the lack of experience, hardly can explain why it happens this way and how to deal with it. views.py: class TopicListView(FilterView): paginate_by = 20 model = Topic template_name = "forum/subforum.html" slug_url_kwarg = 'subforum_slug' context_object_name = 'topics' filterset_class = TopicFilter def get_queryset(self): qs = self.model.objects.all() if self.kwargs.get('subforum_slug'): qs = qs.filter(subforum__slug=self.kwargs['subforum_slug']) return qs class ShowTopic(DetailView): model = Topic template_name = "forum/topic.html" slug_url_kwarg = 'topic_slug' context_object_name = 'topic' paginate_by = 5 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) topic = self.get_object() comments_list = Comment.objects.filter(topic=topic).order_by('created') paginator = Paginator(comments_list, self.paginate_by) page_number = self.request.GET.get('page', 1) page_obj = paginator.get_page(page_number) context.update({ 'menu': menu, 'comments': page_obj, 'page_obj': page_obj, 'is_paginated': page_obj.has_other_pages(), 'paginator': paginator, 'comm_num': comments_list.count(), #'topic_rep': topic.total_rep, }) return context class AddTopic(LoginRequiredMixin, CreateView): form_class = AddTopicForm template_name = 'forum/addtopic.html' page_title = 'Create new topic' def get_success_url(self): return reverse('forum:topic', kwargs={ 'subforum_slug': self.kwargs['subforum_slug'], 'topic_slug': self.object.slug}) def form_valid(self, form): subforum = Subforum.objects.get(slug=self.kwargs['subforum_slug']) form.instance.creator = self.request.user form.instance.subforum = subforum return super(AddTopic, self).form_valid(form) class UpdateFirstComment(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model = Topic form_class = AddTopicForm template_name = 'forum/editcomment.html' page_title = 'Edit topic' def test_func(self): topic = self.get_object() if self.request.user == topic.creator or self.request.user.is_superuser: return True return False def get_success_url(self): return reverse('forum:topic', kwargs={ 'subforum_slug': self.kwargs['subforum_slug'], 'topic_slug': self.kwargs['topic_slug'] }) def get_object(self, queryset=None): return Topic.objects.get(slug=self.kwargs['topic_slug'], subforum__slug=self.kwargs['subforum_slug']) def form_valid(self, form): self.object = form.save(commit=False) first_comment = self.object.first_comment form.instance.creator = self.request.user form.instance.topic = self.object form.instance.first_comment = first_comment return super(UpdateFirstComment, self).form_valid(form) class DeleteTopic(LoginRequiredMixin, UserPassesTestMixin, DeleteView): model = Topic context_object_name = 'topic' template_name = 'forum/topic_confirm_delete.html' page_title = "Delete topic" fields = '__all__' def test_func(self): if self.request.user.is_superuser: return True return False def get_success_url(self): return reverse('forum:subforum', kwargs={'subforum_slug': self.kwargs['subforum_slug']}) forum/urls.py (only for subforums and topics): <...> app_name = 'forum' urlpatterns = [ path('', SubForumListView.as_view(), name='forum'), path('<slug:subforum_slug>/', TopicListView.as_view(), name='subforum'), path('<slug:subforum_slug>/add_topic/', AddTopic.as_view(), name="add_topic"), path('<slug:subforum_slug>/topics/<slug:topic_slug>/', ShowTopic.as_view(), name='topic'), path('<slug:subforum_slug>/topics/<slug:topic_slug>/edit_topic/', UpdateFirstComment.as_view(), name='edit_topic'), path('<slug:subforum_slug>/topics/<slug:topic_slug>/delete_topic/', DeleteTopic.as_view(), name='delete_topic'), ] tests.py (comments' section tests are omitted, they work fine): from django.test import TestCase from django.urls import reverse from . import factories, models from .models import Topic, Comment class SubforumTestCase(TestCase): def setUp(self): self.subforum = factories.SubForumFactory() self.user = factories.UserFactory() self.topic = factories.TopicFactory(subforum=self.subforum, creator=self.user) self.client.force_login(self.user) def test_get_topic_list(self): url = reverse('forum:subforum', kwargs={'subforum_slug': self.subforum.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['topics'].count(), models.Topic.objects.count()) def test_get_topic_detail(self): url = reverse("forum:topic", kwargs={'subforum_slug': self.subforum.slug, 'topic_slug': self.topic.slug}) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "forum/topic.html") def test_add_topic(self): data = { 'subject': self.topic.subject, 'first_comment': self.topic.first_comment } url = reverse("forum:add_topic", kwargs={'subforum_slug': self.subforum.slug}) old_topics_count = Topic.objects.count() response = self.client.post(url, data=data) self.assertEqual(response.status_code, 302) self.assertEqual(Topic.objects.count(), 2) self.assertGreater(Topic.objects.count(), old_topics_count) def test_update_first_comment(self): data = { 'first_comment': "Chebuldyk" } url = reverse("forum:edit_topic", kwargs={ 'subforum_slug': self.subforum.slug, 'topic_slug': self.topic.slug }) old_first_comment = self.topic.first_comment response = self.client.post(url, data=data) self.topic.refresh_from_db() self.assertEqual(response.status_code, 302) self.assertNotEqual(self.topic.first_comment, old_first_comment) def test_delete_topic(self): url = reverse("forum:delete_topic", kwargs={ 'subforum_slug': self.topic.subforum.slug, 'topic_slug': self.topic.slug }) old_topics_count = Topic.objects.count() response = self.client.delete(url) self.assertEqual(response.status_code, 302) self.assertGreater(old_topics_count, Topic.objects.count()) results of testing: Found 8 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). ...FF..F ====================================================================== FAIL: test_add_topic (forum.tests.SubforumTestCase.test_add_topic) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\PyCharm Community Edition 2024.1.3\PycharmProjects\...\forum\tests.py", line 38, in test_add_topic self.assertEqual(response.status_code, 302) AssertionError: 200 != 302 ====================================================================== FAIL: test_delete_topic (forum.tests.SubforumTestCase.test_delete_topic) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\PyCharm Community Edition 2024.1.3\...\forum\tests.py", line 65, in test_delete_topic self.assertEqual(response.status_code, 302) AssertionError: 403 != 302 ====================================================================== FAIL: test_update_first_comment (forum.tests.SubforumTestCase.test_update_first_comment) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\PyCharm Community Edition 2024.1.3\PycharmProjects\...\forum\tests.py", line 54, in test_update_first_comment self.assertEqual(response.status_code, 302) AssertionError: 200 != 302 ---------------------------------------------------------------------- Ran 8 tests in 0.111s FAILED (failures=3) Tests work basing on factories; if necessary, I can provide them as well. Continue reading...