-
Notifications
You must be signed in to change notification settings - Fork 30
5.0 step5 correction
This is a real field on library.book
, the information does not exist
yet:
publishing_date = fields.Date('Publishing date')
This is a Function
field of the Many2Many
type on library.author
.
Calculating this field could be expensive (it requires to iterate over all
the books the author wrote), however there is no reason for it to be calculated
on many records at once, so it can be an instancemethod. A classmethod would be
ideal, though more complicated to write. A searcher could be interesting for
searching all authors that wrote in the same genre:
genres = fields.Function(
fields.Many2Many('library.genre', None, None, 'Genres'),
'getter_genres', searcher='searcher_genres')
Note that when a Many2Many
field is used as the first parameter of a
Function
field, the parameters are slightly changed. Remember, the standard
definition requires the __name__
of an intermediary model, and that of the
two fields to use on this model to map the source and destination models. Here,
the first parameter is directly the __name__
of the model we want as output
for our field (here library.genre
), and the second and third parameters are
empty.
Note: For One2Many
Function
fields, the second parameter (referencing
the Many2One
field to use in the database for materializing the relation)
should be set to None
as well
Here is an exemple implementation for an instancemethod getter (with an empty searcher):
def getter_genres(self, name):
genres = set()
for book in self.books:
if book.genre:
genres.add(book.genre.id)
return list(genres)
@classmethod
def searcher_genres(cls, name, clause):
return []
Python: set
objects are uniquified lists
The getter for a Many2Many
Function
field must return a list of ids for
the target model. The searcher's default value is an empty list, so we will
stick to that for now.
This is a Many2One
Function
field on library.book
. Same as above,
it will not be used often enough to justify using a classmethod, though it
would increase performances. A searcher is probably not necessary here, it is
really more of an informative field:
latest_exemplary = fields.Function(
fields.Many2One('library.book.exemplary', 'Latest exemplary'),
'getter_latest_exemplary')
def getter_latest_exemplary(self, name):
latest = None
for exemplary in self.exemplaries:
if not exemplary.acquisition_date:
continue
if not latest or(
latest.acquisition_date < exemplary.acquisition_date):
latest = exemplary
return latest.id if latest else None
Here, using a classmethod would improve performances, because even though
self.exemplaries[0]
only tries to access the first ([0]
) element of
self.exemplaries
, it requires tryton to load the exemplaries
field,
which is all exemplaries, not just the first one. In a classmethod, we could do
it all in a query.
Also, we have an example of how Function
fields may be complicated: how to
handle the case of incomplete data? Here we chose to ignore exemplaries for
which the acquisition_date
is unknown, but that could depend on your
requirements.
Note: We could not juste return latest
, because the getter of a
Many2One
Function
field must return an id
, not a python object
This is a Char
field on library.book
, for which we can use the size
attribute to limit it to 13 characters. We could have wanted an Integer
field here, but:
- 13 digits is too much for an
Integer
to hold - Adding the constraint, though possible, would have been a little more complicated
isbn = fields.Char('ISBN', size=13,
help='The International Standard Book Number')
This field should be added in both the form and tree views of the
library.book
model.
A Many2One
Function
field on library.author
. This is an information
you may want to see on the list view of an author, so a classmethod may be
advisable. A searcher is not necessary though ("I want to know the author whose
latest book is XXX" => Better to open the book and see the author for yourself
directly):
latest_book = fields.Function(
fields.Many2One('library.book', 'Latest Book'),
'getter_latest_book')
@classmethod
def getter_latest_book(cls, authors, name):
result = {x.id: 0 for x in authors}
Book = Pool().get('library.book')
book = Book.__table__()
sub_book = Book.__table__()
cursor = Transaction().connection.cursor()
sub_query = sub_book.select(sub_book.author,
Max(Coalesce(sub_book.publishing_date, datetime.date.min),
window=Window([sub_book.author])).as_('max_date'),
where=sub_book.author.in_([x.id for x in authors]))
cursor.execute(*book.join(sub_query,
condition=(book.author == sub_query.author)
& (Coalesce(book.publishing_date, datetime.date.min)
== sub_query.max_date)
).select(book.author, book.id))
for author_id, book in cursor.fetchall():
result[author_id] = book
return result
The implementation here is complicated, because the actual SQL query is. The basics are identical anyway to the classmethod getter we already wrote, only the query itself is different. If you are familiar enough with SQL, you can compare this implementation with:
SELECT
"a"."author",
"a"."id"
FROM
"library_book" AS "a"
INNER JOIN
(SELECT
"c"."author",
MAX(COALESCE("c"."publishing_date", '0001-01-01'::date)) OVER "d" AS "max_date"
FROM "library_book" AS "c"
WHERE ("c"."author" IN (1, 2, 3, 4))
WINDOW "d" AS (PARTITION BY "c"."author")
) AS "b"
ON (
("a"."author" = "b"."author") AND
(coalesce("a"."publishing_date", '0001-01-01'::date) = "b"."max_date"))
This Integer
Function
field on library.book
should be fairly easy
to implement, since it is very similar to the number_of_books
field of
library.author
. There is no need for a searcher.
Same as above, but on library.editor
. Be careful to properly use the
editor
field of the library.book
model in the query to match the
editors we are working on.
This one is similar to the genres
field we created on the
library.author
model, but with an additional constraint on the publishing
date. Also, the number of books an editor publishes is vastly superior to that
of a single author, so iterating on them in an instancemethod may be a
performance killer. So it should be a classmethod.
We will not implement it for now, it is possible to get this information another way that we will talk about later.