#django #python-3.x
#django #python-3.x
Вопрос:
Мне удалось перейти к седьмой главе книги «Разработка на основе тестирования на Python».
Я просмотрел все темы одних и тех же ошибок в Google, но объяснения отличаются от моего тестового примера. Итак, я изо всех сил пытаюсь понять, что не так с приведенным ниже кодом. Я понимаю, что
AttributeError: 'NoneType' object has no attribute 'id'
говорит мне, что 'id'
не определен. Но я не знаю, где это исправить в Django.
Также для
self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1,
Я не знаю, где искать.
(sup) ben@ben:~/sup1/superlists$ sudo python3 manage.py test lists
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
======================================================================
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tim/sup1/superlists/lists/tests.py", line 93, in test_redirects_after_POST
self.assertRedirects(response, f'/lists/{new_list.id}/')
AttributeError: 'NoneType' object has no attribute 'id'
======================================================================
FAIL: test_can_save_a_POST_request (lists.tests.NewListTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tim/sup1/superlists/lists/tests.py", line 87, in test_can_save_a_POST_request
self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1
MODELS.PY
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List, default='', null=True, blank=True, on_delete = models.CASCADE)
TESTS.PY
from django.template.loader import render_to_string
from django.urls import resolve, reverse_lazy
from django.test import TestCase
from django.http import HttpRequest
from lists.views import home_page
from lists.models import Item, List
class HomePageTest(TestCase):
def test_uses_home_template(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
def test_displays_all_list_items(self):
Item.objects.create(text='itemey 1')
# Item.objects.create(text='itemey 2')
response = self.client.get('/')
self.assertIn('item', response.content.decode())
# self.assertIn('itemey 2', response.content.decode())
def test_only_saves_items_when_necessary(self):
self.client.get('/')
self.assertEqual(Item.objects.count(), 0)
class ListViewTest(TestCase):
def test_uses_list_template(self):
list_ = List.objects.create()
response = self.client.get(f'/lists/{list_.id}/')
self.assertTemplateUsed(response, 'list.html')
def test_displays_only_items_for_that_list(self):
correct_list = List.objects.create()
Item.objects.create(text='item', list=correct_list)
# Item.objects.create(text='itemey 2', list=correct_list)
response = self.client.get(f'/lists/{correct_list.id}/')
self.assertContains(response, 'item')
# self.assertContains(response, 'itemey 2')
def test_passes_correct_list_to_template(self):
correct_list = List.objects.create()
response = self.client.get(f'/lists/{correct_list.id}/')
self.assertEqual(response.context['list'], correct_list)
class ListAndItemModelsTest(TestCase):
def test_saving_and_retrieving_items(self):
list_ = List()
list_.save()
first_item = Item()
first_item.text = 'The first (ever) list item'
first_item.list = list_
first_item.save()
second_item = Item()
second_item.text = 'Item the second'
second_item.list = list_
second_item.save()
saved_list = List.objects.first()
self.assertEqual(saved_list, list_)
saved_items = Item.objects.all()
self.assertEqual(saved_items.count(), 2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEqual(first_saved_item.text, 'The first (ever) list item')
self.assertEqual(first_saved_item.list, list_)
self.assertEqual(second_saved_item.text, 'Item the second')
self.assertEqual(second_saved_item.list, list_)
class NewListTest(TestCase):
def test_can_save_a_POST_request(self):
self.client.post('lists/new', {'item_text': 'A new list item'})
new_item = Item.objects.first()
self.assertEqual(Item.objects.count(), 1)
self.assertEqual(new_item.text, 'A new list item')
def test_redirects_after_POST(self):
response = self.client.post('/lists/new', data={'item_text': 'A new list item'})
new_list = List.objects.first()
self.assertRedirects(response, f'/lists/{new_list.id}/')
class NewItemTest(TestCase):
def test_can_save_a_POST_request_to_an_existing_list(self):
correct_list = List.objects.create()
self.client.post(
f'/lists/{correct_list.id}/add_item',
data={'item_text': 'A new item for an existing list'}
)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new item for an existing list')
self.assertEqual(new_item.list, correct_list)
def test_redirects_to_list_view(self):
correct_list = List.objects.create()
response = self.client.post(
f'/lists/{correct_list.id}/add_item',
data={'item_text': 'A new item for an existing list'}
)
self.assertRedirects(response, f'/lists/{correct_list.id}/')
VIEWS.PY
from django.shortcuts import redirect, render
# from django.http import HttpResponse
from lists.models import Item, List
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/')
items = Item.objects.all()
return render(request, 'home.html')
def view_list(request, list_id):
list_ = List.objects.get()
return render(request, 'list.html', {'list': list_})
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect(f'/lists/{list.id}/')
def add_item(request, list_id):
list_ = List.objects.get(id=list_id)
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect(f'/lists/{list_.id}/')
СУПЕРЛИСТ — URLS.PY
from django.urls import path, re_path, include
from lists import views as list_views
from lists import urls as list_urls
urlpatterns = [
#path('admin/', admin.site.urls),
re_path('^$', list_views.home_page, name="home"),
path('lists/', include(list_urls)),
re_path('^lists/new/$', list_views.new_list, name="new_list"),
re_path('^lists/(d )/$', list_views.view_list, name="view_list"),
re_path('^lists/(d )/add_item$', list_views.add_item, name="add_item"),
]
СПИСОК — URLS.PY
#from django.contrib import admin
from django.urls import path, re_path
from lists import views
urlpatterns = [
#path('admin/', admin.site.urls),
re_path('^new/$', views.new_list, name="new_list"),
re_path('^(d )/$', views.view_list, name="view_list"),
re_path('^(d )/add_item$', views.add_item, name="add_item"),
]
HOME.HTML
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<form method="POST" action="/lists/new">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
{% csrf_token %}
</form>
</body>
</html>
LIST.HTML
{% extends 'home.html' %}
<body>
<h1>Start a new To-Do list</h1>
<form method="POST" action="/lists/{{ list.id }}/add_item">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
{% csrf_token %}
</form>
<table id="id_list_table">
{% for item in list.item_set.all %}
<tr><td>{{ forloop.counter }}: {{ item.text }}</td></tr>
{% endfor %}
</table>
</body>
Я пытаюсь сохранить пользовательский ввод и убедиться, что страницы перенаправляются правильно, но пока без особого успеха.
Ответ №1:
Оба случая связаны с тем фактом, что на момент, когда вы делаете свои утверждения, в базе данных не существует объектов.
-
В первом случае вы пытаетесь получить
id
атрибут дляNone
объекта. В отличие отget()
,first()
не вызывает ошибку, но вернутьсяNone
когда ни один объект не найден. -
Во втором случае вы подсчитываете объекты в своей базе данных. При отсутствии объектов количество, очевидно, равно нулю.
Это из-за опечаток:
- на ваш
new_list()
взгляд. Это должно быть(f'/lists/{list_.id}/')
, не(f'/lists/{list.id}/')
. - в вашем
test_can_save_a_POST_request
это должно быть'/lists/new/'
, а не'lists/new/'
. - отсутствие косой черты в конце при вызове POST с клиентом.
self.client.post('lists/new')
должно бытьself.client.post('lists/new/')
.
Это вызывает много проблем:
- Если
APPEND_SLASH
установлено значениеTrue
(по умолчанию), то ваш запрос будет перенаправляться на URL без косой черты как GET, и это может привести к потере любых данных, отправленных в запросе POST (Источник: Документы по настройкам Django). - Вы должны использовать для своих URL-адресов reverse, что позволяет избежать ошибок такого рода и является хорошей практикой для отказа от жесткого кодирования представлений в целом.
Так что это должно быть:
from django.urls import reverse
class NewListTest(TestCase):
def test_can_save_a_POST_request(self):
response = self.client.post(reverse('new_list'), data={'item_text': 'A new list item'})
new_item = Item.objects.first()
self.assertEqual(Item.objects.count(), 1)
self.assertEqual(new_item.text, 'A new list item')
def test_redirects_after_POST(self):
response = self.client.post(reverse('new_list'), data={'item_text': 'A new list item'})
new_list = List.objects.first()
self.assertRedirects(response, f'/lists/{new_list.id}/')
Я запустил проект локально с вышеупомянутыми изменениями, и хотя некоторые тесты по-прежнему не проходили, ваши ошибки были устранены.
И последнее: я не понимаю, почему вы маршрутизируете одни и те же URL-адреса в приложении list (с include
), а затем снова в папке проекта. Вы должны просто сохранить include
.
Я рекомендую вам следовать совету Зеда Шоу о том, чтобы обращать внимание на детали
Комментарии:
1. Привет, Пьер, спасибо за твой ответ. Я разберусь с этим. Это займет некоторое время, прежде чем я смогу разобраться во всем этом. Существует ли приоритетный порядок проблем, которые нужно обрабатывать в первую очередь?
2. Я думаю, что ваши объяснения для меня очень ясны, поскольку я понимаю, откуда берется проблема в коде. Но я все еще не знаю, что поместить в класс List. Я гуглил больше трех дней, но люди используют разные атрибуты в своих классах, и я не знаю, как назвать свои. Пример: ‘listid’ = Item.objects.get(id=идентификатор). Но очевидно, что этот код не работает.
3. Моя ошибка, я не видел, что вы создаете объекты с помощью запросов POST к представлениям. Обновлю свой ответ позже сегодня.
4. Большое спасибо, Пьер.
5. Я настроил проект локально и смог устранить ваши ошибки. Смотрите мои обновления.