6.6 Alterando Modelos
Vamos caminhar agora para a solução do nosso problema mais complicado que é o de lidar com diferentes URLs para usuários distintos.
Iniciamos alterando nossos testes unitários para lidar com o que precisamos. O novo conjunto de teste unitário ficou conforme abaixo. Incluí um # ao final de cada linha que foi incluída.
from django.urls import resolve
from django.test import TestCase
from lists.views import home_page
from lists.models import Item, List #
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEquals(found.func, home_page)
def test_home_page_returns_correct_html(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
def test_only_saves_items_when_necessary(self):
self.client.get('/')
self.assertEquals(Item.objects.count(), 0)
class NewListTest(TestCase):
def test_can_save_a_POST_request(self):
self.client.post('/lists/new', data={'item_text': 'A new list item'})
self.assertEquals(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEquals(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'})
self.assertRedirects(response, '/lists/the-only-list-in-the-world/')
class ListViewTest(TestCase):
def test_uses_list_template(self):
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertTemplateUsed(response, 'list.html')
def test_displays_all_list_itens(self):
Item.objects.create(text='itemey 1')
Item.objects.create(text='itemey 2')
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertContains(response, 'itemey 1')
self.assertContains(response, 'itemey 2')
class ListAndItemModelTest(TestCase): #
def test_saving_and_retriving_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.assertEquals(saved_list, list_) #
saved_items = Item.objects.all()
self.assertEquals(saved_items.count(),2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEquals(first_saved_item.text, 'The first (ever) list item')
self.assertEquals(first_saved_item.list, list_) #
self.assertEquals(second_saved_item.text, 'Item the second')
self.assertEquals(second_saved_item.list, list_) #
Outra forma de ver as alterações que Percival utilizou em seu livro, foi por meio do git diff, conforme ilustrado abaixo. Linhas com + foram adicionadas
diff --git a/lists/tests.py b/lists/tests.py
index 5a2f58d..a35f831 100644
--- a/lists/tests.py
+++ b/lists/tests.py
@@ -1,7 +1,7 @@
from django.urls import resolve
from django.test import TestCase
from lists.views import home_page
-from lists.models import Item
+from lists.models import Item, List #
class HomePageTest(TestCase):
@@ -48,17 +48,25 @@ class ListViewTest(TestCase):
self.assertContains(response, 'itemey 2')
-class ItemModelTest(TestCase):
+class ListAndItemModelTest(TestCase): #
def test_saving_and_retriving_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.assertEquals(saved_list, list_) #
+
saved_items = Item.objects.all()
self.assertEquals(saved_items.count(),2)
saved_items = Item.objects.all()
self.assertEquals(saved_items.count(),2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEquals(first_saved_item.text, 'The first (ever) list item')
+ self.assertEquals(first_saved_item.list, list_) #
self.assertEquals(second_saved_item.text, 'Item the second')
+ self.assertEquals(second_saved_item.list, list_) #
Basicamente as alterações incluem a criação de um objeto List
; a atribuição de cada item a esse objeto por meio da propriedade .list
. Em seguida, fizemos o teste se a lista foi salva e se os dois itens salvaram seu relacionamento com a lista.
Elaborado o teste, podemos executá-lo para iniciar o ciclo teste de unidade/código.
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
System check identified no issues (0 silenced).
E
======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
File "/usr/lib/python3.8/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)...
from lists.models import Item, List #
ImportError: cannot import name 'List' from 'lists.models'
(/home/mlptdd/superlists/superlists/lists/models.py)
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
Para corrigir, precisamos implementar a classe List em lists/models.py, conforme abaixo:
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
Ao reexecutar os testes obtemos o seguinte erro:
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
Traceback (most recent call last):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 423, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: no such table: lists_list
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "manage.py", line 22, in <module>
main()
File "manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
utility.execute()
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/__init__.py", line 413, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/commands/test.py", line 23, in run_from_argv
super().run_from_argv(argv)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/base.py", line 354, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/base.py", line 398, in execute
output = self.handle(*args, **options)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/management/commands/test.py", line 55, in handle
failures = test_runner.run_tests(test_labels)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/test/runner.py", line 725, in run_tests
old_config = self.setup_databases(aliases=databases)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/test/runner.py", line 643, in setup_databases
return _setup_databases(
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/test/utils.py", line 179, in setup_databases
connection.creation.create_test_db(
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/base/creation.py", line 90, in create_test_db
self.connection._test_serialized_contents = self.serialize_db_to_string()
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/base/creation.py", line 136, in serialize_db_to_string
serializers.serialize("json", get_objects(), indent=None, stream=out)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/serializers/__init__.py", line 129, in serialize
s.serialize(queryset, **options)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/core/serializers/base.py", line 90, in serialize
for count, obj in enumerate(queryset, start=1):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/base/creation.py", line 133, in get_objects
yield from queryset.iterator()
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/models/query.py", line 353, in _iterator
yield from self._iterable_class(self, chunked_fetch=use_chunked_fetch, chunk_size=chunk_size)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1169, in execute_sql
cursor.execute(sql, params)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 66, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 423, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: lists_list
Pela mensagem é um erro relacionado com a base de dados. Lembre-se que quando mudamos o modelo é necessário que façamos o migration para que as informações das tabelas do banco de dados sejam atualizadas a contento.
(superlists) auri@av:~/superlists/superlists$ python manage.py makemigrations
Migrations for 'lists':
lists/migrations/0003_list.py
- Create model List
Feito isso o resultados dos testes passam a ser conforme abaixo:
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...E....
======================================================================
ERROR: test_saving_and_retriving_items (lists.tests.ListAndItemModelTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/superlists/lists/tests.py", line 77, in test_saving_and_retriving_items
self.assertEquals(first_saved_item.list, list_) #
AttributeError: 'Item' object has no attribute 'list'
----------------------------------------------------------------------
Ran 8 tests in 0.017s
FAILED (errors=1)
Destroying test database for alias 'default'...
O erro indica que não temos ainda o atributo .list
na classe Item. Podemos incluí-lo da mesma forma que fizemos com o atributo .text
.
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.TextField(default='')
Como alteramos novamente o modelo, nossos testes, se forem executados, iram nos lembrar que precisamos fazer uma migração.
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
Traceback (most recent call last):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 423, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: no such column: lists_item.list
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "manage.py", line 22, in <module>
main()
File "manage.py", line 18, in main
execute_from_command_line(sys.argv)
...
django.db.utils.OperationalError: no such column: lists_item.list
(superlists) auri@av:~/superlists/superlists$ python manage.py makemigrations
Migrations for 'lists':
lists/migrations/0004_item_list.py
- Add field list to item
Executada a migração agora nossos testes apresentam o erro abaixo:
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...F....
======================================================================
FAIL: test_saving_and_retriving_items (lists.tests.ListAndItemModelTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/superlists/lists/tests.py", line 77, in test_saving_and_retriving_items
self.assertEquals(first_saved_item.list, list_) #
AssertionError: 'List object (1)' != <List: List object (1)>
----------------------------------------------------------------------
Ran 8 tests in 0.017s
FAILED (failures=1)
Destroying test database for alias 'default'...
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List,on_delete=models.SET_DEFAULT,default=None)
Alterado o modelo devemos fazer a migração novamente. Entretanto, como a anterior não foi bem sucedida podemos removê-la antes. Cuidado, entretanto, ao remover migrações. Só o faça se as mesmas ainda não foram usadas.
from django.db import models
class List(models.Model):
pass
class Item(models.Model):
text = models.TextField(default='')
list = models.ForeignKey(List,on_delete=models.SET_DEFAULT,default=None)
Adaptando o Restante do Código ao Novo Modelo
Feita a correção e a migração acima, ao executar os testes novamente temos as seguintes mensagens de erro:
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....E.EE
======================================================================
ERROR: test_displays_all_list_itens (lists.tests.ListViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
...
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
======================================================================
ERROR: test_can_save_a_POST_request (lists.tests.NewListTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
...
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
======================================================================
ERROR: test_redirects_after_POST (lists.tests.NewListTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
...
django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
----------------------------------------------------------------------
Ran 8 tests in 0.034s
FAILED (errors=3)
Destroying test database for alias 'default'...
Ao todo, temos três erros de violação de restrição do tipo NOT_NULL
. Como incluímos uma dependência entre Items
e Lists
, agora cada item tem que ter uma lista-pai associada a ele e, por isso, três de nossos testes unitários falharam.
O primeiro passo então é corrigir nossos testes unitários conforme abaixo. Veja as linhas 42 a 44 na qual é feita a associação entre o item e a lista.
from django.urls import resolve
from django.test import TestCase
from lists.views import home_page
from lists.models import Item, List #
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEquals(found.func, home_page)
def test_home_page_returns_correct_html(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
def test_only_saves_items_when_necessary(self):
self.client.get('/')
self.assertEquals(Item.objects.count(), 0)
class NewListTest(TestCase):
def test_can_save_a_POST_request(self):
self.client.post('/lists/new', data={'item_text': 'A new list item'})
self.assertEquals(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEquals(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'})
self.assertRedirects(response, '/lists/the-only-list-in-the-world/')
class ListViewTest(TestCase):
def test_uses_list_template(self):
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertTemplateUsed(response, 'list.html')
def test_displays_all_list_itens(self):
list_ = List.objects.create()
Item.objects.create(text='itemey 1', list=list_)
Item.objects.create(text='itemey 2', list=list_)
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertContains(response, 'itemey 1')
self.assertContains(response, 'itemey 2')
class ListAndItemModelTest(TestCase): #
def test_saving_and_retriving_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.assertEquals(saved_list, list_) #
saved_items = Item.objects.all()
self.assertEquals(saved_items.count(),2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEquals(first_saved_item.text, 'The first (ever) list item')
self.assertEquals(first_saved_item.list, list_) #
self.assertEquals(second_saved_item.text, 'Item the second')
self.assertEquals(second_saved_item.list, list_) #
Com isso, nossos testes avanças e ficam restando dois com falha, ambos relacionados ao POST
para a inclusão de um novo item e nossa função de view que faz o tratamento a requisição também não faz a associação do item com um lista. A correção é dada nas linhas 13 e 14 abaixo:
from django.shortcuts import redirect, render
from lists.models import Item, List
# Create your views here.
def home_page(request):
return render(request, 'home.html')
def view_list(request):
items = Item.objects.all()
return render(request, 'list.html', {'items': items})
def new_list(request):
list_ = List.objects.create()
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/the-only-list-in-the-world/')
Com isso, ao executar os testes de unidade obtemos sucesso na execução.
(superlists) auri@av:~/superlists/superlists$ python manage.py test lists
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........
----------------------------------------------------------------------
Ran 8 tests in 0.019s
OK
Destroying test database for alias 'default'...
Desse modo, para nos assegurar que está tudo bem, ao executar nossos testes funcionais podemos observar que chagamos a um estado já conhecido e consistente.
(superlists) auri@av:~/superlists/superlists$ python manage.py test functional_tests
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.F
======================================================================
FAIL: test_multiple_users_can_start_lists_at_different_urls (tests.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mlptdd/superlists/superlists/functional_tests/tests.py", line 117, in test_multiple_users_can_start_lists_at_different_urls
self.wait_for_row_in_list_table('1: Buy milk')
...
AssertionError: '1: Buy milk' not found in ['1: Buy peacock feathers',
'2: Buy milk']
----------------------------------------------------------------------
Ran 2 tests in 19.385s
FAILED (failures=1)
Destroying test database for alias 'default'...
Após as mudanças e a chegada a um ponto consistente com o que já tínhamos é um bom momento para colocarmos as mudanças sob controle de versão.
(superlists) auri@av:~/superlists/superlists$ git add lists
(superlists) auri@av:~/superlists/superlists$ git commit -am "Test case updated to check connection between items and lists. Models updated to include List."
[master 20f8359] Test case updated to check connection between items and lists. Models updated to include List.
5 files changed, 63 insertions(+), 9 deletions(-)
create mode 100644 lists/migrations/0003_list.py
create mode 100644 lists/migrations/0004_item_list.py
(superlists) auri@av:~/superlists/superlists$ git push
Username for 'https://github.com': aurimrv
Password for 'https://aurimrv@github.com':
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 12 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.58 KiB | 1.58 MiB/s, done.
Total 9 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
To https://github.com/aurimrv/superlists.git
89328a3..20f8359 master -> master
Ao verificar nossa lista de itens a cumprir, podemos riscar mais um.
Last updated
Was this helpful?