-
Notifications
You must be signed in to change notification settings - Fork 30
4.4 step3
We now have s basic structure and relations in that
structure. You may update on the step3
to get a clean version:
git checkout 4.4/step3
We got a skeleton, time to flesh it up!
Models are used to store data. But for now, we got empty books and authors. We did created fields in the previous step. However the "motivation" for those fields was to define relations between models, not to store "flat" informations.
Before adding more information though, some rules on fields that you should always think about.
Data fields should be added on the model which is the more logical (the "Car color" field should be on the "Car" model). If it is not obvious, ask to one of your fellow developers, and in last resort choose the model which makes it easier for you to go on with your developments.
Inside a given model, the "data" field should be placed after the structural
Many2One
and One2Many
fields, in their relative order of importance.
For instance for a book, the title
field should probably be located before
the page_count
field since the latter is not an information you will look
for first when reading the book's data.
Think carefully when naming your fields. The field name will in most case be the only information that you will have about its contents. You do not want to have to look its definition up in the source code to understand what it contains everytime you see it somewhere.
Ideally, a field name should in itself be enough to know:
- The "type" of data it contains
- The model it is about (for relation fields only)
- The cardinality of its contents: make sure there is an "s" at the end of
every
Many2Many
orOne2Many
field name - Use camelcase with underscores (
_
) as word delimiters - No articles (
author
rather thenthe_author
)
Try to avoid abbreviations. If the field name you are thinking about is so long that abbreviations are required, you probably should think twice about it.
As much as possible, information duplication should be avoided. The main reason
is consistency across the application: if we add a name
field on our
library.author
model, and a author_name
field on the library.book
model, there could be the risk that they do not match for a given book. In that
case there is always the question of which one is right, which one is wrong,
what should be done, etc.
There are means to calculate the information in tryton, so when creating a data field, one should always check that the information does not already exist somewhere, or that it is not possible to compute it from existing informations.
- Number of exemplaries of a book: Not a data field, it can be computed from the number of elements in the list of exemplaries
- Name of an author: It's a data field, the information does not exist (yet) in any model
-
Name of a book's author: If the Name of on author field is defined, it
can be computed, since the
library.book
model already has aauthor
field -
Genres of an author: Since authors write books, and that the
library.book
model has agenre
field, it is possible to compute the genres an author writes from the books he has already written, so not a field
Note: You could (rightly) argue that the genres
field we defined on the
library.editor
model could be computed from the books that were edited by
the editor. It was easier to do so for this tutorial purpose, but it indeed
should not be a data field
Now that we are good, we are going to add some fields on our models, so that we are finally starting to have "real" informations.
One of the most common field type is the Char
field. Open library.py
and add the following field in the library.author
model, under the
books
field:
name = fields.Char('Name', required=True)
As you probably guessed, we add a new field named name
, which is
required
.
Warning: Char
fields are not "chars" but rather "strings", so not just
single characters, but any string
Char
fields are used to store "short" (i.e. less than a couple hundred
characters) strings. It is possible to limit their size with the optional
size
attribute:
my_string = fields.Char('my_string', size=10)
Such a constraint will be enforced by the client, the server, and if it supports it the database.
Char
fields are great for short strings, but if what you want is a large
text, you need something bigger. Add the following field in the
library.book
model:
summary = fields.Text('Summary')
Obviously, limiting a book summary to a couple hundred characters was going to be tricky...
There are two main differences between Char
and Text
fields:
-
Text
fields are not limited in size, whether it is by the server or the database -
Text
fields are designed and displayed by default to properly manage multiline input. It may also use to hold formatted content (more on this later)
Another frequent field is Integer
, which is used to store integers,
positive or negative. Add the following under the library.book
model:
page_count = fields.Integer('Page Count',
help='The number of page in the book')
The help
attribute, though we did not use it previously, is available on
all field types. It is a string which will be displayed client side as a pop up
when the user focuses the field. It is good to set it unless you are sure the
field will never be displayed to the client, or it's obvious from the field's
string.
Boolean
fields are "True" / "False" fields which are great to store
options, or "Yes" / "No" questions. Under the previously added page_count
field on the library.book
model add the following:
edition_stopped = fields.Boolean('Edition stopped',
help='If True, this book will not be printed again in this version')
As you will see in the next step, this field will be displayed to the user with
a checkbox that can be ticked or not. Note as this field will greatly benefit
from the help
attribute because the field name cannot contain all the
scope of its value in a reasonable number of characters.
Integer are great for countable entities, but how can I store non-integer
values ? Tryton provides the Numeric
field for that. Add one in the
library.book.exemplary
model:
acquisition_price = fields.Numeric('Acquisition Price', digits=(16, 2))
The digits
parameter is used to specify the precision with which we want
to store the information. The first value (16
) represents the maximum
number of digits that will be used to store the value. So if a number exceeds
10^16
, there will be some precision loss. You should usually use the
default value, which is 16
and will work in most cases.
The second value (2
) is the maximum allowed number of digits after the
comma. Here it is set to 2
because we assume that the library uses a
2-digits currency. This precision is checked by the client and the server, so
trying to store an acquisition_price
with more digits than allowed will
cause an error.
Warning: Tryton also provides a Float
field to store non-integer data.
You should never use it, unless absolute precision in arithmetics is not
required. See here for
more informations
Storing the date of an event is a common requirement. Open the
library.book.exemplary
model and create the acquisition_date
field:
acquisition_date = fields.Date('Acquisition Date')
Tryton provides other field types for most time information, though the most
frequently used is by far the Date
type:
-
Time
: Stores the time of a day -
DateTime
: To store a Date plus Time -
Timestamp
: Stores a Unix timestamp as adatetime
-
TimeDelta
: Store the interval between twodatetime
s
In case you need to use any of those, check the tryton documentation
Selection
fields are enumerates. The field definition includes the possible
values, which will limit user input, and be checked by the server before
storage in the database. Add a new Selection
field to the
library.author
model:
gender = fields.Selection([('man', 'Man'), ('woman', 'Woman')], 'Gender')
There are multiple variants on how to define the possible values of a
Selection
field, but for now we will stick to the simple version. The first
parameter of the field may contain a list of tuples. The first item in each
tuple will be the field value, and the second how it will be displayed to the
user.
This is something important. Selection
fields are actually stored the same
way Char
fields are, as strings in the database. When manipulating
Selection
field values in server code, we will actually use strings. Again,
making sure that the fields values are properly named is essential for later
readability.
If you want to store non-textual data in the database, you have to use
Binary
fields. Let's store the cover of our books:
cover = fields.Binary('Cover')
Done! Binary fields are not often used, and accept two storage mode:
- DB storage (the default one, which we used here), where the contents will be stored as is in the database
- Filesystem storage, where the contents are stored on the server filesystem, and the database only contains a key which allows to find it later. This form is interesting if you have numerous large files you do not want to bother your database with
These fields are non-specified Many2One
fields. Non-specified, because a
Many2One
field's first parameter is the name of the model you want to link
to, but Reference
fields do not need this. They allow a field to hold a
reference to any model which is allowed by the developer.
A typical use case would be a field on a logging model, in which you want to
reference the record that triggered the log. If you are sales, you may have a
car
model, a bike
model, and a truck
model. When you sale them, you
want to write it down in a dedicated table, with a reference to the particular
item you sold. You cannot use a Many2One
field here, because then you would
have to either have three of those (one for each possible type of vehicle you
sale), or only log the information for one of those. Reference
fields will
allow you to do all at once, but at a cost.
Many2One
fields integrity is enforced at the database level. The
ondelete
parameter allows to control the behavior when removing entries
from the database. There is no such thing for Reference
fields. You may
delete a record which is referenced somewhere, with no control. So you can end
up with "holes" in your database.
One must be very careful when creating Reference
fields.
- Make sure there is no other way to store the information, maybe tweaking the model a little
- Do manual checks on record deletions to check if they are used in your reference field
- Only use it for "expandable" data. Losing a reference in a log is bothersome, but it will not break your application. Not knowing the person who last borrowed a book that is missing can
Dict
fields are great if you need to allow your users to customize your
application. With them, you can define a dedicated model to hold "questions".
Then, you can add a Dict
fields which will prompt the user for the answer
to those questions, record by record.
These fields will be explained more thoroughly later in the training module.
There are a few other field types that are available in tryton, though they are rarely used:
-
BigInteger
are here when standardInteger
fields are not big enough to store your values -
Float
are inaccurate versions ofNumeric
, which can be considered if absolute precision is not a requirement -
One2One
fields areMany2Many
fields with a size 1 limit. They are aMany2Many
because they use an additional model between the elements to link together, but each linked element may only be linked once. An exemple would be the relation between network ports. A given port can be linked to any other port (using an Ethernet cable), but it can be only linked to one port at a time. It could be implemented with symmetricalMany2One
fields, but there would be the risk of inconsistency between relations. -
MultiValue
may be used for advanced configuration, where you typically want to store multiple values for a given key, and the good one will be found with pattern matching
Read the tryton documentation on those fields if you need to use them
Function
fields are critical to tryton development, and will be covered
separately later.
We already talked about some optional parameters of fields (required
,
help
, etc.), here are the main basic options:
-
required
: Makes the fields required at the client, server and database level -
readonly
: Makes the fieldreadonly
, but only client-side. It is always possible to modify the field value when writing server code, only the user is limited. If a field should absolutely not be modified, there are other solutions to check for this. Setting itreadonly
will only make it more difficult for the end user to do so -
help
: Helps the user by adding a descriptive text which will appear in the UI when hovering over the field widget -
select
: A little more on the advanced side, this parameter is a boolean that automates the creation of an index on the field to improve performances if this field is often used for searching
There are other common parameters, and some parameters that are specific to
some field types (ondelete
, etc.), we will cover most of those later.
You are going to add fields to our models. For each of them, ask yourself for the name it should have, if it is really needed (do not add it if it is not!), its parameters (should it be required ? readonly ? should I add some help ?).
If you feel that a given field should only be required / readonly under certain circumstances, note that information somewhere, we will come to this eventually.
Here are the fields that you should add:
- The "name" of a genre
- The "name" of an editor
- The "title" of a book
- The date at which an editor started its activity
- The number of books an author wrote
- The birth date of an author
- The death date of an author
- A one-line description of a book
- An identifier on an exemplary
You can compare your code with that of the step3_homework
branch for
differences. You can then read here about what you
should have done, and why.
Our model is now fully-fledged, we will now add a way to create some data in the database through the user interface.