Django views' tests return 403/200 code instead of (presumably) 302

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.

The response.status.code for add and update functions being 200 rather than 301 suggests you are getting errors, so you are not travelling to the success_url but staying on the page you are posting to.

Try adding a form_invalid function to print out errors to your terminal

def form_invalid(self, form):
        response = super().form_invalid(form)
        print form.errors

The 403 error fore delete_topic is because you are using self.client.delete. While DELETE is a legitimate type of HTTP request, it's not commonly used for webpage interaction. You most likely want self.client.get (for a plain url or one with a querystring) or self.client.post (if you are posting form data) to communicate with the view that does the actual deletion of the model instance via code.

Вернуться на верх