-
Notifications
You must be signed in to change notification settings - Fork 30
4.4 step2
We juste finished learning the module basics. To be sure, you
should update your environment on the step2
branch:
git checkout 4.4/step2
We now have five models to work with, it is time to link them all up.
We already talked about fields when writing the library.xml
file. More
specifically, we talked about the model
, type
and name
fields of
the ir.ui.view
model.
If Models are the table of a database, the fields are the columns of those models. For reference, you can use the complete documentation, but you should save it for later for more informations on less common attributes and fields types.
The point of a Model is to have fields. There are multiple types of fields The obvious one you would expect, like Integers, Strings, etc., and the slightly more complex relational fields.
Weird as it may seem, we will focus on the relational fields in this step. The reason is that they are the most important fields in tryton. An Integer is just data, but fields that link models together are those that really matter, and which structure an application. There are three relational fields that are commonly used in tryton.
Before going on, open the library.py
file and modify the first line like
so:
from trytond.model import ModelSQL, ModelView, fields
We now import fields
from trytond.model
. This is the part of tryton
that is used to declare new fields, and will be one of the most frequence
imports that you will do when writing modules.
Many2One
fields are the core of all modules. If you are familiar with
databases, they are the foreign keys. They define the relations between models,
and understanding them is essential when doing modelization work.
There are two "families" of Many2One
fields, depending on their
characteristics, and what they represent.
In library.py
, update the Book
class so that it looks like the
following:
class Book(ModelSQL, ModelView):
'Book'
__name__ = 'library.book'
genre = fields.Many2One('library.genre', 'Genre', ondelete='RESTRICT',
required=False)
This line adds a field named genre
in the library.book
model. This
field is a Many2One
field, because Many books may have the Same
(One) genre.
The first parameter of a Many2One
field is the model we
want to reference. As we named our field genre
, you guessed that it
represents the actual genre of the book, so the model is logically
library.genre
. Notice again how we use the __name__
to represent the
model rather than the Genre
class.
So a Book now has an genre
field which is a relation to the
library.genre
model.
The second parameter, 'Genre'
, is the string
under which the field should be displayed to the user. The other parameters are
specific to Many2One
fields and are used to fine-tune how the field is
represented in the database.
The ondelete
parameter controls the behavior of the database (and
consequently of tryton) when you try to delete a record of the target model
(library.genre
) which is referenced by at least one record of the current
model (library.book
). So let's assume that we have a book in our database
whose genre is "Fiction". The value of ondelete
will describe what happens
when we try to delete the Fiction genre from the database:
-
'RESTRICT'
will forbid the deletion, because it is used by our book -
'CASCADE'
will allow the deletion, and will also automatically delete all Fiction books -
'SET NULL'
will allow the deletion, and clear thegenre
field of all books which were previously Fuction
The most widely used values are 'RESTRICT'
and 'CASCADE'
. Learning when
to use one or the other is experience, what is sure is that you must ask
yourself for the proper value of the ondelete
attribute of all Many2One
fields.
Here we used the 'RESTRICT'
attribute. This is because we want to make it
difficult to delete a genre for which we already created books. Using
'RESTRICT'
will force the user to manually delete all books in order to
then delete the genre. We will give example and rationale for 'CASCADE'
later on.
The last parameter we defined is required
. As expected, setting it to
True
would make the genre
mandatory. However we set it to False
here, because there are books for which the genre is not early defined.
The default value for required
is False
, so it was not necessary
to write it here, it was done for training purposes.
Warning: required
fields are made so with hard constraints in the
database. That means that a required
field will never ever be empty once
the record has been saved in the database. Even direct database access will not
be enough to clear it.
Note: The ondelete
parameter is set to 'SET NULL'
by default, which is
usually not the expected behavior. Really ask yourself what it should be.
Every time.
Note: For models which do not inherit from ModelSQL
, the ondelete
parameter is not important and can be ignored, since it is only used for models
that are stored in the database
The genre
Many2One
is Data, because it is not necessary to the Book. A
book without a genre is just a missing information. It is good to have it, but
not a problem.
Now we can add another Many2One
field with different characteristics in the
Exemplary
class:
class Exemplary(ModelSQL, ModelView):
'Exemplary'
__name__ = 'library.book.exemplary'
book = fields.Many2One('library.book', 'Book', ondelete='CASCADE',
required=True)
You should be able to understand what this does. We basically add a relation on
library.book.exemplary
toward library.book
. This relation materialize
the fact that exemplaries are the materialization of books. The rationale
behind this if we have multiple exemplaries of the same book, we want that
modifying its title (to fix a typo for instance) should only be done once.
Another way to look at it is that an exemplary contains all the information of the book, plus a few more (for instance its inventory number in a library).
For such a tight relationship, we set ondelete
to 'CASCADE'
. If we
delete the book (that would be censorship though), the associated exemplaries
have no meaning anymore. We also set required
to True
, because an
exemplary is fundamentaly a book with a few more informations. So if we remove
the book part of it, it does not make sense to keep the rest.
If you compare the book
field on an exemplary with the genre
field of a
book, the difference should be clear. The first one is critical for the
library.book.exemplary
model, while the second one is optional information.
The fact that the library.book
.exemplary
model has a structural
Many2One
field toward a library.book
field is no surprise. Remember
the we explicitely named library.book.exemplary
because an exemplary is a
materialization of a library.book
. If you follow the naming rules, this is
something you should rather frequently see when designing modules.
One2Many
fields are strongly related to structural Many2One
fields.
As you will see, any Many2One
can be used to create a One2Many
, but in
90% of the cases it will be on a structural Many2One
. That is because a
One2Many
represents a list entities which have a common parent.
Open library.py
and add the following before the genre
field
definition in the Book
class:
exemplaries = fields.One2Many('library.book.exemplary', 'book',
'Exemplaries')
We define a new field on the library.book
model, named exemplaries
.
The naming should tell you what we are looking at: the list of the registered
exemplaries of the book. As before, One2Many stands for "One book has
Many exemplaries".
As for the Many2One
fields before, the first parameter is the __name__
(again!) of the model which is the target of our field. exemplaries
is a
field that represents a list of library.book.exemplary
.
The second parameter requires a little more explaining. 'book'
is the name
of a field of the library.book.exemplary
model. Understanding why this
parameter is required on One2Many
fields, and what its value should be is
key to working with tryton.
One2Many
are not directly stored in the database. Rather, they are
(crucial) synstastic sugar to help modelization and development with tryton.
Consider the Book - Exemplary relationship. We already said that by design, an
exemplary must have a Many2One
toward a book. So in a given library,
there may be multiple (Many) exemplaries which will be linked through the
book
field to the same (One) book instance. Or, you could think it the
other way around: One book may have Many exemplaries.
Now, in terms of database modelization, we could imagine to add a column in the
book table, with the list of its exemplaries. But that would be useless. First,
list fields in databases are not part of the default set of sql fields. Second,
the information already exists. If we want to know all the exemplaries of a
given book, we can just search all exemplaries for which the book
field's
value is the book we are considering.
So, One2Many
fields are a simple way that tryton uses to provide us with
list fields, but they are actually stored using a Many2One
on the target
model.
If we go back to our exemple, we actually inform tryton that the
library.book
model has an exemplaries
field, which is the list of all
the library.book.exemplary
instances whose book
field matches the book
we are looking at. It is inform, because removing the field does not
actually change the database structure. It will simply make it more difficult
to manipulate a given book exemplaries.
Warning: Tryton will not create the Many2One
field for you. Rather, it
will crash if you forgot to do it.
Now, we have another Many2One
field. The genre
field of
library.book
. Could we add a One2Many
field on the library.genre
which would contain the list of all the books using this genre ? We could. But
this would not be logical. Why would you use it for ? What scenario do you have
that would require that you take a given genre, iterate on all the books of
that genre and do things ? Few, because a genre is a configuration object, the
genre
field is Data, not Structure.
Rule of thumb: do not create One2Many
fields for which the associated
Many2One
field is not required
, with ondelete='CASCADE'
. Those
parameter more or less define a structural Many2One
, and are logical to
have when you cant to use a One2Many
The last standard relational field is the Many2Many
field. This field is
used to define list relations that are symmetrical.
Add a new field to the library.editor
model:
genres = fields.Many2Many('library.editor-library.genre', 'editor',
'genre', 'Genres')
So, we are creating a new genres
field for the library.editor
model.
Since we are talking Many2Many
fields, we expect it to link Many
editors to Many genres. But how ?
Add a new model to the module (remember to update __init__.py
and
__all__
, and think about where it should be in your file):
class EditorGenreRelation(ModelSQL):
'Editor - Genre relation'
__name__ = 'library.editor-library.genre'
editor = fields.Many2One('library.editor', 'Editor', required=True,
ondelete='CASCADE')
genre = fields.Many2One('library.genre', 'Genre', required=True,
ondelete='RESTRICT')
First question, obviously, "Why do I need to declare a new model?". Well, as
mentioned above, it is complicated to store lists directly in the columns of a
database. We sort of cheated for One2Many
fields because since one
exemplary could only have one book, we could just rely on the book
field to
make it work.
Here it is more complicated. A given editor may work in multiple genres, but a given genre is obiously not limited to one editor. So we have no other choice that to find another way to store the information, and that is with another model.
For a second, imagine the relation between people and bank accounts. A person
can have multiple bank accounts, and a bank account may be shared between
different persons. This is a typical Many2Many
use case. Fortunately, there
is a (simplified) way to know who has access to which account: each holder of a
bank account will have a credit card for this account. The credit card contains
both the information of the person (the name on it) and the account it gives
access to.
Back to our editors and genres. There is no such thing as a editor-genre card,
but we will create one anyway for the sake of properly storing the
Many2Many
between editors and genres: it will be our new
library.editor-library.genre
model. As you can read, this model has
Many2One
fields toward both the library.editor
and the
library.genre
models (the same way our credit card identifies both the card
holder and the associated account).
If we go back to the field definition, we can now better understand its parameters:
- First of all, the
__name__
of the model that we will use to store the data of which editor works with which genres - Second, the name of the field which corresponds to the model on which the
Many2Many
field is defined. Here, we are defining thegenres
field onlibrary.editor
, so we are talking about the (judiciously named)editor
field. - Third, the name of the field which corresponds to the target information we
are looking for. Here it is
genre
. Note that theMany2Many
field definition does not directly include the fact that we are talking about thelibrary.genre
model, hence the importance of properly naming the fields. - Finally, the string that will be used to represent the
genres
field to the end user
There are a few thing you may have noticed that requires explanations:
- The
EditorGenreRelation
class does not inherit fromModelView
contrary to all the other models we defined until now. This is because it is a technical model that solely exists to store thegenres
field data, and should not be displayed to the end user, so no need to manage views on this one - Its
__name__
contains a dash. This is the one case where using dashes in__name__
is common practise. Since those models are rather technical, we do not mind having a long__name__
, concatenating the__name__
s of the models we are linking with the field - The
ondelete
value is not the same for bothMany2One
fields. This is no rule, but structure. It is logical that when deleting an editor, the information of the genres it worked with be removed. On the contrary, removing a genre should not be possible if it is used by an editor
Note: it is always possible to define symmetrical Many2Many
fields.
However, "Just because you can doesn't mean you have to". Think it through, do
you need it? If so, then go on, it's free anyway, since the technical table is
already here. If not, do not bother, you will always be able to easily add it
later if you have to
- Think when you name your fields. Often, the field name will be the sole
information you have, so it has to be as much descriptive as possible. For
XXXtoMany
fields, use plurals. Be both short and explicit - Choose the order in which you order your fields in a given model carefully.
You may end up with tens of fields on some models, to ease readability you
should place the most important at the top. Basically, Structural
Many2One
fields first, thenOne2Many
fields. The rest you can order according your perceived importance
- Add a field on the
library.book
model to store the editor of the book - There is one very important (read: structural) relation that we did not add yet. Find it, and implement it
- This relation has a pendant on its target model. Should it be implemented ? If so, go on
You can compare your code with that of the step2_homework
branch for
differences.
You can read here about what you should have done, and why.
You can understand the data structure of a tryton modelisation, and how different models are related to one another. Next we will add some actual data to our models.