Introduction to models¶
Lino applications fully use Django's ORM. In Django, every database
table is described by a subclass of Model
. Every row of the
table is an instance of that class.
The models of an application are always defined in a file named
models.py
. Here is the models.py
file we are going to use in
this tutorial:
from __future__ import unicode_literals
from lino.api import dd
from django.db import models
from django.core.exceptions import ValidationError
class Author(dd.Model):
first_name = models.CharField("First name", max_length=50)
last_name = models.CharField("Last name", max_length=50)
country = models.CharField("Country", max_length=50, blank=True)
def __str__(self):
return "%s, %s" % (self.last_name, self.first_name)
class Book(dd.Model):
author = dd.ForeignKey(Author, blank=True, null=True)
title = models.CharField("Title", max_length=200)
published = models.IntegerField(
"Published",
help_text="The year of publication")
price = models.DecimalField("Price", decimal_places=2, max_digits=10)
def full_clean(self):
super(Book, self).full_clean()
if self.published > 2000 and self.price < 5:
price = dd.format_currency(self.price)
msg = "A book from {} for only {}!".format(
self.published, price)
raise ValidationError(msg)
This file is defined in the lino_book.projects.tables
demo
project. You can try the code snippets on this page from within a
Django shell in that project:
$ go tables
$ python manage.py shell
Accessing the database¶
We import our two models:
>>> from lino_book.projects.tables.models import Author, Book
Every Model
has a class attribute objects
which is
used for operations that access the database.
For example you can count how many authors are stored in our database.
>>> Author.objects.count()
3
Or you can loop over them:
>>> for a in Author.objects.all():
... print(a)
Adams, Douglas
Camus, Albert
Huttner, Hannes
You can create a new author by saying:
>>> obj = Author(first_name="Joe", last_name="Doe")
That row is not yet stored in the database, but you can already use it. For example you can access the individual fields:
>>> print(obj.first_name)
Joe
>>> print(obj.last_name)
Doe
For example it has a __str__()
method:
>>> print(obj)
Doe, Joe
You can change the value of a field:
>>> obj.last_name = "Woe"
>>> print(obj)
Woe, Joe
In order to store our object to the database, we call its save()
method:
>>> obj.full_clean() # see later
>>> obj.save()
Our database now knows a new author, Joe Woe:
>>> Author.objects.count()
4
>>> for a in Author.objects.all():
... print(a)
Adams, Douglas
Camus, Albert
Huttner, Hannes
Woe, Joe
The all()
method of the objects
of a Model
returns what Django calls a queryset. A queryset is a volatile
Python object which describes an SQL SELECT
statement. You can see
the SQL if you want:
>>> qs = Author.objects.all()
>>> print(qs.query)
SELECT "tables_author"."id", "tables_author"."first_name", "tables_author"."last_name", "tables_author"."country" FROM "tables_author"
>>> qs = Author.objects.filter(first_name="Joe")
>>> print(qs.query)
SELECT "tables_author"."id", "tables_author"."first_name", "tables_author"."last_name", "tables_author"."country" FROM "tables_author" WHERE "tables_author"."first_name" = Joe
>>> qs.count()
1
>>> qs
<QuerySet [Author #4 ('Woe, Joe')]>
Before going on we tidy up by removing Joe Woe from our demo database:
>>> obj.delete()
>>> Author.objects.count()
3
Validating data¶
You should always call the full_clean()
method of an object
before actually calling its save()
method. Django does not do
this automatically because they wanted to leave this decision to the
developer.
For example, we did not specify that the last_name
of an
author may be empty. So Django will complain if we try to create an
author without last_name:
>>> author = Author(first_name="Joe")
>>> author.full_clean()
Traceback (most recent call last):
...
ValidationError: {'last_name': [u'This field cannot be blank.']}
Note that Django complains only when we call full_clean()
(not already
when instantiating the model).
Note that the country
field is declared with blank=True, so
this field is optional.
The ValidationError
is a special kind of exception, which
contains a dictionary that can contain one error message for every
field. In the Book model we have three mandatory fields: the
title
, the price
and the year of publication
(published
). Giving only a title is not enough:
>>> book = Book(title="Foo")
>>> book.full_clean()
Traceback (most recent call last):
...
ValidationError: {'price': [u'This field cannot be null.'], 'published': [u'This field cannot be null.']}
The Book
model also shows how you can define custom validation rules
that may depend on complex conditions which involve more than one
field.
>>> book = Book(title="Foo", published=2001, price='4.2')
>>> book.full_clean()
Traceback (most recent call last):
...
ValidationError: [u'A book from 2001 for only $4.20!']
More about Django models¶
Tim Kholod wrote a nice introduction for beginners: The simple way to understand Django models
If you want to know more about Django's way to access the database using models, read the Django documentation about Models and databases.
Lino extends the Django model¶
Lino adds a series of features to Django's Model class. In a Lino
application you will define your models as subclasses of
lino.core.model.Model
(usually referred as dd.Model
), which is
a subclass of Django's django.db.models.Model
class.
When a Lino application imports plain Django Model classes, Lino will "extend" these by adding the attributes and methods defined here to these classes.
Standard virtual fields¶
Lino adds some virtual fields that you can use in your layouts:
-
Model.
overview
¶ A fragment of HTML describing this object in a customizable story of paragraphs.
Customizable using
Model.get_overview_elems()
.
-
Model.
detail_link
¶ A virtual field which displays this database row as a clickable link which opens the detail window. Functionally equivalent to a double click, but more intuitive in some places.
A virtual field that displays the workflow actions for this row. This is a compact but intuitive representation of the current workflow state, using a series of clickable actions.
Field-specific customization hooks¶
You can optionally define some field-specific customization hooks. FOO in this section is the name of a database field defined on the same model (or on a parent).
-
Model.
FOO_changed
()¶ Called when field FOO of an instance of this model has been modified through the user interface.
Example:
def city_changed(self, ar): print("User {} changed city of {} to {}!".format( ar.get_user(), self, self.city))
Note: If you want to know the old value when reacting to a change, consider writing
Model.after_ui_save()
instead.
-
Model.
FOO_choices
()¶ Return a queryset or list of allowed choices for field FOO.
For every field named "FOO", if the model has a method called "FOO_choices" (which must be decorated by
dd.chooser()
), then this method will be installed as a chooser for this field.Example of a context-sensitive chooser method:
country = dd.ForeignKey( 'countries.Country', blank=True, null=True) city = dd.ForeignKey( 'countries.City', blank=True, null=True) @chooser() def city_choices(cls,country): if country is not None: return country.place_set.order_by('name') return cls.city.field.remote_field.model.objects.order_by('name')
-
Model.
create_FOO_choice
()¶ For every field named "FOO" for which a chooser exists, if the model also has a method called "create_FOO_choice", then this chooser will be a learning chooser. That is, users can enter text into the combobox, and Lino will create a new database object from it.
This works only if FOO is (1) a foreign key and (2) has a chooser. See also learning foreign key.
-
Model.
get_choices_text
(self, request, actor, field)¶ Return the text to be displayed when an instance of this model is being used as a choice in a combobox of a ForeignKey field pointing to this model. request is the web request, actor is the requesting actor.
The default behaviour is to simply return str(self).
A usage example is
lino_xl.lib.countries.Place
.
-
Model.
disable_delete
(self, ar=None)¶ Decide whether this database object may be deleted. Return None if it is okay to delete this object, otherwise a nonempty translatable string with a message that explains in user language why this object cannot be deleted.
The argument ar contains the action request that is trying to delete. ar is possibly None when this is being called from a script or batch process.
The default behaviour checks whether there are any related objects which would not get cascade-deleted and thus produce a database integrity error.
You can override this method e.g. for defining additional conditions. Example:
def disable_delete(self, ar=None): msg = super(MyModel, self).disable_delete(ar) if msg is not None: return msg if self.is_imported: return _("Cannot delete imported records.")
When overriding, be careful to not skip the super method unless you know what you want.
Note that
lino.mixins.polymorphic.Polymorphic
overrides this.
How your model behaves in regard to other models:
Customize what happens when an instance is created:
Some methods you will use but not override:
Model.get_data_elem