Как исправить AttributeError: у объекта 'NoneType' нет атрибута 'id' и self.assertEqual (Item.objects.count (), 1) AssertionError: 0! = 1 - PullRequest
1 голос
/ 02 апреля 2019

Мне удалось перейти к седьмой главе книги «Разработка через тестирование на Python».

Я просмотрел все темы об одних и тех же ошибках в Google, но объяснения отличаются от моего теста. Поэтому я изо всех сил пытаюсь понять, что не так с кодом ниже. Я понимаю, что

AttributeError: 'NoneType' object has no attribute 'id'

говорит мне, 'id' не определено. Но я не знаю, где это исправить в Джанго.

Также для

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}/')

SUPERLIST - 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>

Я пытаюсь сохранить пользовательский ввод и убедиться, что страницы перенаправляются правильно, но пока не достигли большого успеха.

Ответы [ 2 ]

0 голосов
/ 02 апреля 2019

Оба случая связаны с тем, что в момент создания ваших утверждений в базе данных не было объектов.

  • В первом случае вы пытаетесь получить 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/'.
  • отсутствие косой черты при вызовеПОСТ с клиентом.self.client.post('lists/new') должно быть self.client.post('lists/new/').

Это вызывает много проблем:

  • Если для APPEND_SLASH установлено значение True (по умолчанию), то ваш запрос будетперенаправить на URL без косой черты как GET, и может привести к потере любых данных, отправленных в запросе POST (Источник: Django Settings Docs ).
  • Youследует использовать reverse для ваших URL-адресов, что позволяет избежать подобных ошибок и является хорошей практикой, чтобы не жестко кодировать представления в целом.

Так должно быть:

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-адреса в приложении списка (с помощью include), а затем снова в папку проекта.Вы должны просто сохранить include.

Я рекомендую вам следовать совету Зеда Шоу о , обращая внимание на детали ;)

0 голосов
/ 02 апреля 2019

Проблема в том, что вы устанавливаете значение по умолчанию в вас Item

class Item(models.Model):
    text = models.TextField(default='')
    list = models.ForeignKey(List, null=True, blank=True, 
                             on_delete=models.CASCADE)

И попробуйте создать List и добавить его к Item и запустить тест

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...