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 (as well as any additional data necessary to make it clear).