Extend an existing Blok
In this chapter we will discover how to add a field to the ̀ `Address`` model.
Note: You can use
III-01_external-blok
directory from AnyBlok/anyblok-book-examples repository to get ready to start with this chapter.
Clean up examples
Before extending the anyblok_address
in the next chapter let's do some
clean-up and remove examples generated by cookiecutter template in
the previous section
- Start by removing the following files:
rm rooms_booking/room/model.py \
rooms_booking/room/tests/test_model.py \
rooms_booking/room/tests/test_pyramid.py \
rooms_booking/room/views.py
- Remove pyramid configurations:
# file: ``/rooms_booking/room/__init__.py``
- @classmethod
- def pyramid_load_config(cls, config):
- config.add_route("root", "/")
- config.add_route("example_list", "/example")
- config.add_route("example", "/example/{id}")
- config.scan(cls.__module__ + '.views')
Extend the Address model
We need to add an access
information field on each address.
Let's add two new files in your project:
rooms_booking/room/address.py
: We are going to extend Address model in this python modulerooms_booking/room/tests/test_address.py
: here we are going to test our additional features on this model.
In order to import the address python module (the address.py
file), in an
Anyblok project you must do it in the import declaration module method and the reload declaration module method in the Blok class declaration.
# file: ``/rooms_booking/room/__init__.py``
# class: Room(Blok)
@classmethod
def import_declaration_module(cls):
"""Python module to import bloks in the given order at start-up
"""
from . import address # noqa
@classmethod
def reload_declaration_module(cls, reload):
"""Python module to import while reloading server (i.e. when
adding Blok at runtime)
"""
from . import address # noqa
reload(address)
Those methods are called when the registry is created or reloaded for a given database.
In our case if the Room blok state is installed
in a database
address.py
will be imported. Otherwise all our code will be present but
not used because the blok is uninstalled.
In the same file, adapt the update method. At the moment we are not going to do anything here while updating or installing the Room Blok:
# file: ``/rooms_booking/room/__init__.py``
# class: Room(Blok)
def update(self, latest_version):
"""Update blok"""
# if we install this blok in the database we add a new record
if not latest_version:
self.install()
def install(self):
pass
Note: When you need to know if a blok is installed, launch an
anyblok_interpreter and query registry.System.Blok
In [1]: registry.System.Blok.query().all()
Out[1]:
[anyblok-core (installed),
address (installed),
auth (uninstalled),
auth-password (uninstalled),
authorization (uninstalled),
model_authz (uninstalled),
room (installed),
anyblok-test (uninstalled)]
In [2]: exit
(loving TDD) Before you start coding, add the following unit tests. This way, we can test that we can add some access information on an Address:
# file: rooms_booking/room/tests/test_address.py
class TestAddress:
"""Test extended registry.Address model"""
def test_create_address(self, rollback_registry):
registry = rollback_registry
address_count = registry.Address.query().count()
queens_college_address = registry.Address.insert(
first_name="The Queen's College",
last_name="University of oxford",
street1="High Street",
zip_code="OX1 4AW",
city="Oxford",
country="GBR",
access="Kick the door to open it!"
)
assert registry.Address.query().count() == address_count + 1
assert queens_college_address.access == "Kick the door to open it!"
Before starting tests, you may need to create test database using
make setup-tests
and then run anyblok_updatedb -c app.test.cfg --install-bloks address
in order to install address blok if it is not already.
If you run this test you'll probably notice the following error as we haven't
created the access
field on our Address
model yet.
make setup-tests
make test
ANYBLOK_CONFIG_FILE=app.test.cfg py.test -v -s rooms_booking
========================================== test session starts ==========================================
platform linux -- Python 3.5.3, pytest-4.6.3, py-1.8.0, pluggy-0.12.0 -- ~/anyblok/venvs/book/bin/python3
cachedir: .pytest_cache
rootdir: ~/anyblok/anyblok-book-examples, inifile: tox.ini
plugins: cov-2.7.1
collected 1 item
rooms_booking/room/tests/test_address.py::TestAddress::test_create_address AnyBlok Load init: EntryPoint.parse('anyblok_pyramid_config = anyblok_pyramid:anyblok_init_config')
Loading config file '/etc/xdg/AnyBlok/conf.cfg'
Loading config file '~/.config/AnyBlok/conf.cfg'
Loading config file '~/anyblok/anyblok-book-examples/app.test.cfg'
Loading config file '~/anyblok/anyblok-book-examples/app.cfg'
Loading config file '/etc/xdg/AnyBlok/conf.cfg'
Loading config file '~/.config/AnyBlok/conf.cfg'
FAILED
=============================================== FAILURES ================================================
____________________________________ TestAddress.test_create_address ____________________________________
self = <rooms_booking.room.tests.test_address.TestAddress object at 0x7f6765e0bcc0>
rollback_registry = <anyblok.registry.Registry object at 0x7f676583fe10>
def test_create_address(self, rollback_registry):
registry = rollback_registry
address_count = registry.Address.query().count()
queens_college_address = registry.Address.insert(
first_name="The Queen's College",
last_name="University of oxford",
street1="High Street",
zip_code="OX1 4AW",
city="Oxford",
country="GBR",
> access="Kick the door to open it!"
)
rooms_booking/room/tests/test_address.py:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venvs/book/lib/python3.5/site-packages/anyblok/bloks/anyblok_core/core/sqlbase.py:605: in insert
instance = cls(**kwargs)
<string>:4: in __init__
???
../venvs/book/lib/python3.5/site-packages/sqlalchemy/orm/state.py:441: in _initialize_instance
manager.dispatch.init_failure(self, args, kwargs)
../venvs/book/lib/python3.5/site-packages/sqlalchemy/util/langhelpers.py:68: in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
../venvs/book/lib/python3.5/site-packages/sqlalchemy/util/compat.py:154: in reraise
raise value
../venvs/book/lib/python3.5/site-packages/sqlalchemy/orm/state.py:438: in _initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Address: None, None, None, None, None, Country(alpha_2='GB', alpha_3='GBR', name='United Kingdom', numeric='826', official_name='United Kingdom of Great Britain and Northern Ireland') [RO=None] >
kwargs = {'access': 'Kick the door to open it!', 'city': 'Oxford', 'country': 'GBR', 'first_name': "The Queen's College", ...}
cls_ = <class 'anyblok.model.factory.ModelAddress'>, k = 'access'
def _declarative_constructor(self, **kwargs):
"""A simple constructor that allows initialization from kwargs.
Sets attributes on the constructed instance using the names and
values in ``kwargs``.
Only keys that are present as
attributes of the instance's class are allowed. These could be,
for example, any mapped columns or relationships.
"""
cls_ = type(self)
for k in kwargs:
if not hasattr(cls_, k):
raise TypeError(
> "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
)
E TypeError: 'access' is an invalid keyword argument for ModelAddress
../venvs/book/lib/python3.5/site-packages/sqlalchemy/ext/declarative/base.py:840: TypeError
=========================================== warnings summary ============================================
[...]
======================================== short test summary info ========================================
FAILED rooms_booking/room/tests/test_address.py::TestAddress::test_create_address - TypeError: 'access...
================================= 1 failed, 1 warnings in 2.06 seconds ==================================
Makefile:57 : la recette pour la cible « test » a échouée
make: *** [test] Erreur 1
It's time to extend the Address model to make our previous test successful:
# file: rooms_booking/room/address.py
from anyblok import Declarations
from anyblok.column import String
Model = Declarations.Model
register = Declarations.register
@register(Model)
class Address:
"""Extend and specialize anyblok_address blok"""
access = String(label="Access information")
As you can see, it's simple to overload a model you only have to use a
decorator @register(Model)
on your class using the
model name (here Address
) and declare a new field access
using a
String
column.
If you're wondering which columns types are supported you may read columns types in the documentation.
You can add other column type support not provided by AnyBlok Core like
AnyBlok postgres package that provide JsonB
support
while using Postgresql database.
Your test should pass but before any further step, you have to update your blok in order to create missing fields in your test database (or re-create the database from scratch using make setup-tests):
# Be sure your project python package is installed in develop mode
pip install -e .
# Update the database model to add the access field
anyblok_updatedb -c app.test.cfg --update-bloks room
# Run unit test
make test