lino.core.model

Defines the Model class.

(This module's source code is available here.)

Functions

pre_delete_handler(sender[, instance])

Before actually deleting an object, we override Django's behaviour concerning related objects via a GFK field.

Classes

Model(*args, **kwargs)

Lino adds a series of features to Django's Model class.If a Lino application includes plain Django Model classes, Lino will "extend" these by adding the attributes and methods defined here to these classes..

class lino.core.model.Model(*args, **kwargs)

Bases: django.db.models.base.Model, lino.core.fields.TableRow

Lino adds a series of features to Django's Model class. If a Lino application includes plain Django Model classes, Lino will "extend" these by adding the attributes and methods defined here to these classes.

workflow_buttons

A virtual field that displays the workflow actions for this row. This is a compact but intuitive representation of the current workflow state, together with a series of clickable actions.

overview

A fragment of HTML describing this object in a customizable story of paragraphs.

Customizable using get_overview_elems().

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.

full_clean()

This is defined by Django.

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 after_ui_save() instead.

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')
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.

allow_cascaded_copy = frozenset({})

A set of names of ForeignKey or GenericForeignKey fields of this model that cause objects to be automatically duplicated when their master gets duplicated.

If this is a simple string, Lino expects it to be a space-separated list of filenames and convert it into a set at startup.

allow_cascaded_delete = frozenset({})

A set of names of ForeignKey or GenericForeignKey fields of this model that allow for cascaded delete.

Unlike with Dango's on_delete attribute you control cascaded delete behaviour on the model whose instances are going to be deleted.

If this is a simple string, Lino expects it to be a space-separated list of filenames and convert it into a set at startup.

Lino by default forbids to delete any object that is referenced by other objects. Users will get a message of type "Cannot delete X because n Ys refer to it".

Example: Lino should not refuse to delete a Mail just because it has some Recipient. When deleting a Mail, Lino should also delete its Recipients. That's why lino_xl.lib.outbox.models.Recipient has allow_cascaded_delete = 'mail'.

This is also used by lino.mixins.duplicable.Duplicate to decide whether slaves of a record being duplicated should be duplicated as well.

At startup (in kernel_startup) Lino automatically sets on_delete to PROTECT for all FK fields that are not listed in the allow_cascaded_delete of their model.

submit_insert

This is the action represented by the "Create" button of an Insert window. See lino.mixins.dupable for an example of how to override it.

allow_merge_action = False

Whether this model should have a merge action.

See lino.core.merge.MergeAction

Set this to False if you really don't want this model to occur in the site-wide search (lino.modlib.about.SiteSearch).

quick_search_fields = None

Explicitly specify the fields to be included in queries with a quick search value.

In your model declarations this should be either None or a string containing a space-separated list of field names. During server startup resolves it into a tuple of data elements.

If it is None, Lino installs a list of all CharFields on the model.

If you want to not inherit this field from a parent using standard MRO, then you must set that field explictly to None.

This is also used when a gridfilter has been set on a foreign key column which points to this model.

Special quick search strings

If the search string starts with "#", then Lino searches for a row with that primary key.

If the search string starts with "*", then Lino searches for a row with that reference.

quick_search_fields_digit = None

Same as quick_search_fields, but this list is used when the search text contains only digits (and does not start with '0').

active_fields = frozenset({})

If specified, this is the default value for active_fields of every Table on this model.

hidden_elements = frozenset({})

If specified, this is the default value for hidden_elements of every Table on this model.

preferred_foreignkey_width = None

The default preferred width (in characters) of widgets that display a ForeignKey to this model.

If not specified, the default default preferred_width for ForeignKey fields is 20.

workflow_state_field = None

If this is set on a Model, then it will be used as default value for workflow_state_field of all tables based on this Model.

workflow_owner_field = None

If this is set on a Model, then it will be used as default value for lino.core.table.Table.workflow_owner_field on all tables based on this Model.

change_watcher_spec = None

Internally used by watch_changes()

disable_create_choice = False

Whether to disable any automatic creation by learning choosers.

summary_row_template = None

An optional name of a template to use for as_summary_row().

classmethod collect_virtual_fields()

Declare every virtual field defined on this model to Django.

We use Django's undocumented add_field() method.

Make a copy if the field is inherited, in order to avoid side effects like #2592.

Raise an exception if the model defines both a database field and a virtual field of same name.

disable_delete(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 which 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.

Note that lino.mixins.polymorphic.Polymorphic overrides this.

disabled_fields(ar)

Return a set of names of fields that should be disabled (not editable) for this record.

See Disabling individual fields.

delete(**kw)

Double-check to avoid "murder bug" (20150623).

delete_veto_message(m, n)

Return the message Cannot delete X because N Ys refer to it.

classmethod define_action(**kw)

Adds one or several actions or other class attributes to this model.

Attributes must be specified using keyword arguments, the specified keys must not yet exist on the model.

Used e.g. in lino_xl.lib.cal to add the UpdateReminders action to :class: lino.modlib.users.models.User.

Or in lino_xl.lib.invoicing.models for defining a custom chooser.

classmethod hide_elements(*names)

Mark the named data elements (fields) as hidden. They remain in the database but are not visible in the user interface.

classmethod add_param_filter(qs, lookup_prefix='', **kwargs)

Add filters to queryset using table parameter fields.

This is called for every simple parameter.

Usage examples: DeploymentsByTicket, lino_book.projects.min3.lib.contacts.

classmethod lookup_or_create(lookup_field, value, **known_values)

Look up whether there is a model instance having lookup_field with value value (and optionally other known_values matching exactly).

If it doesn't exist, create it and emit an auto_create signal.

classmethod quick_search_filter(search_text, prefix='')

Return the filter expression to apply when a quick search text is specified.

on_create(ar)

Override this to set default values that depend on the request.

The difference with Django's pre_init and post_init signals is that (1) you override the method instead of binding a signal and (2) you get the action request as argument.

Used e.g. by lino_xl.lib.notes.Note.

before_ui_save(ar)

A hook for adding custom code to be executed each time an instance of this model gets updated via the user interface and before the changes are written to the database.

Deprecated. Use the pre_ui_save signal instead.

Example in lino_xl.lib.cal.Event to mark the event as user modified by setting a default state.

after_ui_save(ar, cw)

Like before_ui_save(), but after the changes are written to the database.

Arguments:

ar: the action request

cw: the ChangeWatcher

object, or None if this is a new instance.

Called after a PUT or POST on this row, and after the row has been saved.

Used by lino_welfare.modlib.debts.models.Budget to fill default entries to a new Budget, or by lino_welfare.modlib.cbss.models.CBSSRequest to execute the request, or by lino_welfare.modlib.jobs.models.Contract lino_welfare.modlib.pcsw.models.Coaching lino.modlib.vat.models.Vat. Or lino_welfare.modlib.households.models.Household overrides this in order to call its populate method.

after_ui_create(ar)

Hook to define custom behaviour to run when a user has created a new instance of this model.

save_new_instance(ar)

Save this instance and fire related behaviour.

get_row_permission(ar, state, ba)

Returns True or False whether this database object gives permission to the ActionRequest ar to run the specified action.

update_owned_instance(controllable)

Called by lino.modlib.gfks.Controllable.

after_update_owned_instance(controllable)

Called by lino.modlib.gfks.Controllable.

get_mailable_recipients()

Return or yield a list of (type,partner) tuples to be used as recipents when creating an outbox.Mail from this object.

get_postable_recipients()

Return or yield a list of Partners to be used as recipents when creating a posting.Post from this object.

on_duplicate(ar, master)

Called after duplicating a row on the new row instance.

Also called recursively on all related objects. Where "related objects" means those which point to the master using a FK which is listed in allow_cascaded_delete.

Called by the lino.mixins.duplicable.Duplicate action.

ar is the action request that asked to duplicate.

If master is not None, then this is a cascaded duplicate initiated by a duplicate() on the specified master.

Note that this is called before saving the object for the first time.

Obsolete: On the master (i.e. when master is None), this is called after having saved the new object for a first time, and for related objects (master is not None) it is called before saving the object. But even when an overridden on_duplicate() method modifies a master, you don't need to save() because Lino checks for modifications and saves the master a second time when needed.

after_duplicate(ar, master)

Called by lino.mixins.duplicable.Duplicate on the new copied row instance, after the row and it's related fields have been saved.

ar is the action request that asked to duplicate.

ar.selected_rows[0] contains the original row that is being copied, which is the master parameter

before_state_change(ar, old, new)

Called by set_workflow_state() before a state change.

after_state_change(ar, old, new)

Called by set_workflow_state() after a state change.

set_workflow_state(ar, state_field, target_state)

Called by workflow actions (ChangeStateAction) to perform the actual state change.

after_send_mail(mail, ar, kw)

Called when an outbox email controlled by self has been sent (i.e. when the lino_xl.lib.outbox.models.SendMail action has successfully completed).

as_summary_row(ar)

Return a raw HTML string representing this object in a data view as a single paragraph.

The string should represent a single <p>.

If summary_row_template is set, this will render this object using the named template, otherwise it will call summary_row() and wrap the result into a paragraph.

summary_row(ar, **kw)

Yield a sequence of ElementTree elements that represent this database object in a summary panel.

The elements will be wrapped into a <p> by as_summary_row().

The default representation is the text returned by __str__() in a link that opens the detail view on this database object.

The description may vary depending on the given action request.

For example a partner model of a given application may want to always show the city of a partner unless city is among the known values:

def summary_row(self, ar):
    elems = [ar.obj2html(self)]
    if self.city and not "city" in ar.known_values:
        elems += [" (", ar.obj2html(self.city), ")"]
    return E.p(*elems)

Note that this is called by the class method of same name on lino.core.actors.Actor, which can potentially be customized and can potentially decide to not call the model method.

TODO: rename this to get_row_description and write documentation.

get_typed_instance(model)

Used when implementing Polymorphism.

classmethod set_widget_options(name, **options)

Set default values for the widget options of a given element.

Usage example:

JobSupplyment.set_widget_options('duration', width=10)

has the same effect as specifying duration:10 each time when using this element in a layout.

List of widget options that can be set:

width preferred_width label editable preferred_height hide_sum

classmethod get_request_queryset(ar, **filter)

Return the base queryset for tables on this object.

The optional filter keyword arguments, if present, are applied as additional filter. This is used only in UNION tables on abstract model mixins where filtering cannot be done after the join.

classmethod get_user_queryset(user, **filter)

Get the base queryset, used for user level row filtering in lino_xl.lib.tickets.Ticket

classmethod resolve_states(states)

Convert the given string states into a set of state objects.

The states specifier must be either a set containing state objects or a string containing a space-separated list of valid state names. If it is a string, convert it to the set.

classmethod get_actions_hotkeys()

Return or yield a list of hotkeys to be linked to named actions.

[{'key': key, 'ctrl': Bool, 'shift': Bool, 'ba': action_name}]

classmethod get_layout_aliases()

Yield a series of (ALIAS, repl) tuples that cause a name ALIAS in a layout based on this model to be replaced by its replacement repl.

classmethod django2lino(model)

Injects additional methods into a pure Django model that does not inherit from lino.core.model.Model. Used by lino.core.kernel

classmethod get_subclasses_graph()

Returns an internationalized graphviz directive representing the polymorphic forms of this model.

Usage example:

.. django2rst::

    with dd.translation.override('de'):
        contacts.Partner.print_subclasses_graph()
lino.core.model.pre_delete_handler(sender, instance=None, **kw)

Before actually deleting an object, we override Django's behaviour concerning related objects via a GFK field.

In Lino you can configure the cascading behaviour using allow_cascaded_delete.

See also GenericForeignKey fields.

It seems that Django deletes generic related objects only if the object being deleted has a GenericRelation field (according to Why won't my GenericForeignKey cascade when deleting?). OTOH this statement seems to be wrong: it happens also in my projects which do not use any GenericRelation. As test_broken_gfk shows.

TODO: instead of calling disable_delete here again (it has been called earlier by the delete action before asking for user confirmation), Lino might change the on_delete attribute of all ForeignKey fields which are not in allow_cascaded_delete from CASCADE to PROTECTED at startup.