Skip to content

5.0 step6 correction

Jean Cavallo edited this page Jan 22, 2019 · 1 revision

Step 6 - Homework correction

Pyson/ domain exercises

Number "1"

It is a domain on "A", which should look like:

domain=[If(
        ~Eval('B') | ~Eval('C'),
        [('A', '=', 0)],
        If(Eval('B') > Eval('C'),
            [('A', '>', 0)],
            []))]

On the first condition, the | operator operands do not need to be wrapped in parenthesis, because ~ has a higher priority. We must do the checks in this order, because evaluating the comparison on empty elements may return True. Note how nested If can quickly become hard to read.

Number "2"

This one is not possible as is. As explained, you cannot Eval('B.x'). Usually, this kind of problem could be solved by defining a new Function field B_x which contains the value that we need, then using this field in the following domain:

domain=[If(
        Eval('B_x') > 0,
        [('x', '!=', 0)],
        [])]

Number "3"

This situation is even harder. There is no way to use a domain (for the same reason than above). The sole solution would be to add a Function field on the model of "A" with a searcher that checks for this condition, and use it in the domain.

Number "4"

It is a domain on "A":

domain=['OR', ('x', '>=', Eval('B')), ('x', '=', Eval('C'))]

Constraints

Number "1"

Update the death_date field as such:

death_date = fields.Date('Death date',
    domain=['OR', ('death_date', '=', None),
        ('death_date', '>', Eval('birth_date'))],
    states={'invisible': ~Eval('birth_date')},
    depends=['birth_date'])

The domain is usually set before the states, which are themselves before the depends. This is a question of priority, domains are "more important" than states (apart from the required states, they are not important server side), which are more important than depends (they are just a technical information).

The 'OR' in the domain is important, because without it an empty end_date will be invalid. Another tweak could be to check the value of birth_date in the domain to avoid comparing to an empty value, however since birth_date is required once death_date is set, it is not critical.

Number "2"

Update the states for latest_book and genres of library.author:

states={'invisible': ~Eval('books', False)}

Remember that for Function fields, the states (and domain) parameters are to be set in the "inner" field.

Here there are no need to add the depends because the books field may not be displayed. In fact, if you have lists that you expect to frequently have over a hundred elements, you should avoid displaying them directly in the main object (there are other UI options to show them). Because depends is empty, setting a default value for Eval('books') is a good idea (not doing so could lead to client crashes if books field is not displayed in the view).

Number "3"

This one is not exactly possible as is. We are in the case where we would have to write:

domain=[('genre', 'in', Eval('editor.genres'))]

which as we explained above and in the pyson / domain exercises is not possible.

There are multiple solutions to this problem:

  • Adding a editor_genres Function field on books and using it in the domain
  • Setting the constraint the other way around (limit possible editors to those that deal in the book's genre)
  • The mystery best answer

The first possibility seems not so bad. However, when the book is being created, the field will be empty, because Function fields are only calculated when the record is saved. So that will make it hard to use, having to select the editor, then save, so that finally the genre can be set.

Note: There is actually a way to do this properly, we will talk about it later

The second is good, but feels more like a hack. Wa cannot set the constraint where we want, so we find another way to ensure consistency. It is a solution that you will often use if you cannot find another way around.

The third is to add a new editors field on the library.genre model. As explained in step 2, since there already is a Many2Many field between the two models, this field is "free" in terms of database management, and can be declared as follow:

editors = fields.Many2Many('library.editor-library.genre', 'genre',
    'editor', 'Editors', readonly=True)

We can reuse the same model for the middle table, and just swap the two field names to read the table "the other way around". We set it readonly, because we want the users to add genres to editors (the opposite does not feel logical, though it is technically possible). It is not necessary to show this field in the view, it is only declared (for now) for technical purposes.

Now we can add our domain on the genre field:

domain=[('editors', '=', Eval('editor'))]

You must add the editor in the field's depends.

Whichever solution is chosen may cause problems if you need to add a new genre to the editor. Of course you can use the Editor entry point to do so, but if you imagine a "huge" library, the users which will create books in the application may not be the same that those who manage editors, but this is a "good" problem. We will talk about access rights extensively later.

Number "4"

This one obvsiouly cannot be done via domains or states, the right tool being validate. We already used it to make sure our string was actually a number, we will now expand it to validate the checksum:

@classmethod
def __setup__(cls):
    super(Book, cls).__setup__()
    cls._error_messages.update({
            'invalid_isbn': 'ISBN should only be digits',
            'bad_isbn_size': 'ISBN must have 13 digits',
            'invalid_isbn_checksum': 'ISBN checksum invalid',
            })

@classmethod
def validate(cls, books):
    for book in books:
        if not book.isbn:
            continue
        try:
            if int(book.isbn) < 0:
                raise ValueError
        except ValueError:
            cls.raise_user_error('invalid_isbn')
        if len(book.isbn) != 13:
            cls.raise_user_error('bad_isbn_size')
        checksum = 0
        for idx, digit in enumerate(book.isbn):
            checksum += int(digit) * (1 if idx % 2 else 3)
        if checksum % 10:
            cls.raise_user_error('invalid_isbn_checksum')

This is more of a python problem, you should take the time to figure out how it works if you are new to it.

Number "5"

It is a typical use case for a SQL constraint. The tuple author / title must be unique for books. In the __setup__ of library.book:

t = cls.__table__()
cls._sql_constraints += [
    ('author_title_uniq', Unique(t, t.author, t.title),
        'The title must be unique per author!'),
    ]
Clone this wiki locally