Тесты Django views возвращают код 403/200 вместо (предположительно) 302
Я тестирую представления в своем приложении на Django. Поскольку приложение является серверной частью сайта форума, я пытаюсь протестировать создание, редактирование и удаление темы.
Создание, редактирование и удаление темы реализовано в моем приложении для работы с перенаправлением:
- страница создания (Добавить тему) перенаправляет на страницу успешно созданной темы;
- редактирование первоначального комментария к теме (UpdateFirstComment) приводит к перенаправлению со страницы редактирования на страницу редактируемой темы;
- страница удаления (DeleteTopic) перенаправляет на подфорум (раздел форума), к которому принадлежала удаленная тема.
Я предполагаю (я не уверен; и, скорее всего, это моя ошибка), что код успешного перенаправления равен 302, и в утверждении тестов именно этот код следует проверить. Но на практике тесты на создание и редактирование возвращают код 200, в то время как тест на удаление возвращает код 403. И я, из-за отсутствия опыта, вряд ли смогу объяснить, почему это происходит именно так и как с этим бороться.
views.py:
forum/urls.py (только для подфорумов и тем):
<...>
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 (тесты в разделе комментариев опущены, они работают нормально):
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())
результаты тестирования:
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)
Тесты работают на заводах-изготовителях; при необходимости я могу предоставить и их.
Значение response.status.code для функций добавления и обновления, равное 200, а не 301, указывает на то, что вы получаете ошибки, поэтому вы не переходите к success_url, а остаетесь на странице, на которую отправляете сообщение.
Попробуйте добавить функцию form_invalid для вывода ошибок на ваш терминал
def form_invalid(self, form):
response = super().form_invalid(form)
print form.errors
Ошибка 403 для delete_topic связана с тем, что вы используете self.client.delete
. Хотя DELETE является допустимым типом HTTP-запроса, он обычно не используется для взаимодействия с веб-страницами. Скорее всего, вам понадобится self.client.get (для простого URL-адреса или со строкой запроса) или self.client.post (если вы публикуете данные формы) для связи с представлением, которое выполняет фактическое удаление экземпляра модели с помощью кода.
Я пытался удалить этот вопрос, так как получил помощь с ним на другом сайте, но он мне не позволяет, поэтому я опубликую ответ, чтобы он был полезен другим:
- тест_адд_топик
print(response.context["form"].errors)
предыдущие проверки показали, что проблема в том, что тема создается с уже существующей темой. Причина в том, что тест на создание темы получил тему из уже созданной темы-подделки.
Решение: вставить какую-нибудь оригинальную тему для созданной темы:
def test_add_topic(self):
data = {
'subject': "Kai Cenat fanum tax", # here
'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)
#print(response.context["form"].errors)
self.assertRedirects(
response,
f"/forum/{self.topic.subforum.slug}/topics/topic-{slugify(data['subject'])}/",
status_code=302,
target_status_code=200
)
self.assertEqual(response.status_code, 302)
self.assertEqual(Topic.objects.count(), 2)
self.assertGreater(Topic.objects.count(), old_topics_count)
- тестовое обновление_фирст_комментария
Моя ошибка. Поскольку тема является уникальным и запрашиваемым полем для темы, она должна была быть указана в data
.
Решение:
def test_update_first_comment(self):
data = {
'subject': self.topic.subject, # here
'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()
# print(response.context["form"].errors)
self.assertRedirects(
response,
f"/forum/{self.topic.subforum.slug}/topics/{self.topic.slug}/",
status_code=302,
target_status_code=200
)
self.assertEqual(response.status_code, 302)
self.assertNotEqual(self.topic.first_comment, old_first_comment)
- test_delete_topic
Из-за введенных мной ограничений для удаления темы требуются права администратора. Поэтому для настройки тестов потребовалось добавить другого пользователя с типом администратора. Соответствующий фейкер был создан в factories.py.
После этого возникла проблема с AttributeError: Generic detail view DeleteTopic must be called with either an object pk or a slug in the URLconf.
, которая на этот раз была связана с соответствующим представлением. DeleteView
должен либо содержать slug_url_kwarg
определенный атрибут, либо метод get_object
перезаписано, чтобы указать, к какому URL обращаться.
Решение:
factories.py:
class UserFactory(factory.django.DjangoModelFactory):
username = factory.Faker('user_name')
password = factory.Faker('password')
email = factory.Faker('email')
class Meta:
model = User
class AdminFactory(UserFactory):
class Params:
superuser = factory.Trait(is_superuser=True, is_staff=True)
views.py Удалить просмотр темы:
class DeleteTopic(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Topic
context_object_name = 'topic'
slug_url_kwarg = 'topic_slug' # here
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
'''
# or here; but in this separate case that's redundant
# Necessary when there are several parameters to make clear which slug to address
def get_object(self, queryset=None):
return Topic.objects.get(slug=self.kwargs['topic_slug'], subforum__slug=self.kwargs['subforum_slug'])
'''
def get_success_url(self):
return reverse('forum:subforum', kwargs={'subforum_slug': self.kwargs['subforum_slug']})
tests.py Настройка и тест_delete_topic:
def setUp(self):
self.subforum = factories.SubForumFactory()
self.user = factories.UserFactory()
self.super_admin = factories.AdminFactory(superuser=True) # here
self.topic = factories.TopicFactory(subforum=self.subforum, creator=self.user)
# self.client.force_login(self.user)
self.client.force_login(self.super_admin) # here
def test_delete_topic(self):
url = reverse("forum:delete_topic", kwargs={
'subforum_slug': self.subforum.slug,
'topic_slug': self.topic.slug
})
old_topics_count = Topic.objects.count()
response = self.client.delete(url)
# print(response)
# print(response.context["form"].errors)
self.assertRedirects(response, f"/forum/{self.subforum.slug}/", status_code=302, target_status_code=200)
self.assertEqual(response.status_code, 302)
self.assertGreater(old_topics_count, Topic.objects.count())
Приветствую всех на форуме Django. Ребята - спасители.