Skip to content

4.4 step2

Jean Cavallo edited this page Jan 22, 2019 · 8 revisions

Step 2 - Linking models together

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.

Fields

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

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.

Data Many2One

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 the genre 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.

Structural Many2One

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

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

Many2Many fields

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 the genres field on library.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 the Many2Many field definition does not directly include the fact that we are talking about the library.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 from ModelView contrary to all the other models we defined until now. This is because it is a technical model that solely exists to store the genres 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 both Many2One 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

More on fields

  • 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, then One2Many fields. The rest you can order according your perceived importance

Homework

  • 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.

What's next

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.

Clone this wiki locally