API Reference
models.py
- class computedfields.models.ComputedFieldsModel(*args, **kwargs)[source]
Bases:
_ComputedFieldsModelBase
,Model
Abstract base class for models with computed fields. Overloads
save
to update local computed field values before they are written to the database.All models containing a computed field must be derived from this class.
- save(force_insert: bool = False, force_update: bool = False, using: str | None = None, update_fields: Iterable[str] | None = None, skip_computedfields: bool = False) None [source]
Overloaded to update computed field values before writing to the database.
The method understands a special argument skip_computedfields to skip the calculation of local computed fields. This is used by the
@precomputed
decorator to allow to skip a second field calculation with@precomputed(skip_after=True)
. If you use skip_computedfields yourself, make sure to synchronize computed fields yourself by other means, e.g. by callingupdate_computedfields
before writing the instance or by a laterupdate_dependent
call.
- computedfields.models.ComputedField(field: Field[_ST, _GT], compute: Callable[[...], _ST], depends: Sequence[Tuple[str, Sequence[str]]] | None = None, select_related: Sequence[str] | None = None, prefetch_related: Sequence[Any] | None = None, querysize: int | None = None) Field[_ST, _GT]
Convenient access to
computedfield_factory
.
- computedfields.models.computed(field: Field[_ST, _GT], depends: Sequence[Tuple[str, Sequence[str]]] | None = None, select_related: Sequence[str] | None = None, prefetch_related: Sequence[Any] | None = None, querysize: int | None = None) Callable[[Callable[[...], _ST]], Field[_ST, _GT]]
Convenient access to the decorator
@computed
.
- computedfields.models.precomputed(*dargs, **dkwargs) F | Callable[[F], F]
Convenient access to the decorator
@precomputed
.
- computedfields.models.update_computedfields(instance: Model, update_fields: Iterable[str] | None = None) Iterable[str] | None
Convenient access to
update_computedfields
.
- computedfields.models.update_dependent(instance: QuerySet | Model, model: Type[Model] | None = None, update_fields: Iterable[str] | None = None, old: Dict[Type[Model], List[Any]] | None = None, update_local: bool = True, querysize: int | None = None) None
Convenient access to
update_dependent
.
- computedfields.models.preupdate_dependent(instance: QuerySet | Model, model: Type[Model] | None = None, update_fields: Iterable[str] | None = None) Dict[Type[Model], List[Any]]
Convenient access to
preupdate_dependent
.
- computedfields.models.has_computedfields(model: Type[Model]) bool
Convenient access to
has_computedfields
.
- computedfields.models.get_computedfields(model: Type[Model]) Iterable[str]
Convenient access to
get_computedfields
.
- computedfields.models.is_computedfield(model: Type[Model], fieldname: str) bool
Convenient access to
is_computedfield
.
- computedfields.models.get_contributing_fks() Dict[Type[Model], Set[str]]
Convenient access to
get_contributing_fks
.
- class computedfields.models.ComputedFieldsAdminModel(*args, **kwargs)[source]
Bases:
ContentType
Proxy model to list all
ComputedFieldsModel
models with their field dependencies in admin. This might be useful during development. To enable it, setCOMPUTEDFIELDS_ADMIN = True
in settings.py.- exception DoesNotExist
Bases:
DoesNotExist
- exception MultipleObjectsReturned
Bases:
MultipleObjectsReturned
- class computedfields.models.ContributingModelsModel(*args, **kwargs)[source]
Bases:
ContentType
Proxy model to list all models in admin, that contain foreign key relations contributing to computed fields. This might be useful during development. To enable it, set
COMPUTEDFIELDS_ADMIN = True
in settings.py.Note
A foreign key relation is considered contributing, if it is part of a computed field dependency in reverse direction.
- exception DoesNotExist
Bases:
DoesNotExist
- exception MultipleObjectsReturned
Bases:
MultipleObjectsReturned
resolver.py
Contains the resolver logic for automated computed field updates.
- exception computedfields.resolver.ResolverException[source]
Bases:
ComputedFieldsException
Exception raised during model and field registration or dependency resolving.
- class computedfields.resolver.Resolver[source]
Bases:
object
Holds the needed data for graph calculations and runtime dependency resolving.
Basic workflow:
On django startup a resolver gets instantiated early to track all project-wide model registrations and computed field decorations (collector phase).
On app.ready the computed fields are associated with their models to build a resolver-wide map of models with computed fields (
computed_models
).After that the resolver maps are created (see graph.ComputedModelsGraph).
- models: Set[Type[Model]]
Models from class_prepared signal hook during collector phase.
- computedfields: Set[IComputedField]
Computed fields found during collector phase.
- add_model(sender: Type[Model], **kwargs) None [source]
class_prepared signal hook to collect models during ORM registration.
- add_field(field: IComputedField) None [source]
Collects fields from decoration stage of @computed.
- seal() None [source]
Seal the resolver, so no new models or computed fields can be added anymore.
This marks the end of the collector phase and is a basic security measure to catch runtime model creations with computed fields.
(Currently runtime creation of models with computed fields is not supported, trying to do so will raise an exception. This might change in future versions.)
- property models_with_computedfields: Generator[Tuple[Type[Model], Set[IComputedField]], None, None]
Generator of tracked models with their computed fields.
This cannot be accessed during the collector phase.
- property computedfields_with_models: Generator[Tuple[IComputedField, Set[Type[Model]]], None, None]
Generator of tracked computed fields and their models.
This cannot be accessed during the collector phase.
- property computed_models: Dict[Type[Model], Dict[str, IComputedField]]
Mapping of ComputedFieldModel models and their computed fields.
The data is the single source of truth for the graph reduction and map creations. Thus it can be used to decide at runtime whether the active resolver a certain as a model with computed fields.
Note
The resolver will only list models here, that actually have a computed field defined. A model derived from ComputedFieldsModel without a computed field will not be listed.
- extract_computed_models() Dict[Type[Model], Dict[str, IComputedField]] [source]
Creates computed_models mapping from models and computed fields found in collector phase.
- initialize(models_only: bool = False) None [source]
Entrypoint for
app.ready
to seal the resolver and trigger the resolver map creation.Upon instantiation the resolver is in the collector phase, where it tracks model registrations and computed field decorations.
After calling
initialize
no more models or fields can be registered to the resolver, andcomputed_models
and the resolver maps get loaded.
- load_maps(_force_recreation: bool = False) None [source]
Load all needed resolver maps. The steps are:
create intermodel graph of the dependencies
remove redundant paths with cycling check
create modelgraphs for local MRO
merge graphs to uniongraph with cycling check
create final resolver maps
lookup_map: intermodel dependencies as queryset access strings
fk_map: models with their contributing fk fields
local_mro: MRO of local computed fields per model
- get_local_mro(model: Type[Model], update_fields: Iterable[str] | None = None) List[str] [source]
Return MRO for local computed field methods for a given set of update_fields. The returned list of fieldnames must be calculated in order to correctly update dependent computed field values in one pass.
Returns computed fields as self dependent to simplify local field dependency calculation.
- preupdate_dependent(instance: QuerySet | Model, model: Type[Model] | None = None, update_fields: Iterable[str] | None = None) Dict[Type[Model], List[Any]] [source]
Create a mapping of currently associated computed field records, that might turn dirty by a follow-up bulk action.
Feed the mapping back to
update_dependent
as old argument after your bulk action to update de-associated computed field records as well.
- update_dependent(instance: QuerySet | Model, model: Type[Model] | None = None, update_fields: Iterable[str] | None = None, old: Dict[Type[Model], List[Any]] | None = None, update_local: bool = True, querysize: int | None = None) None [source]
Updates all dependent computed fields on related models traversing the dependency tree as shown in the graphs.
This is the main entry hook of the resolver to do updates on dependent computed fields during runtime. While this is done automatically for model instance actions from signal handlers, you have to call it yourself after changes done by bulk actions.
To do that, simply call this function after the update with the queryset containing the changed objects:
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False) >>> update_dependent(Entry.objects.filter(pub_date__year=2010))
This can also be used with
bulk_create
. Sincebulk_create
returns the objects in a python container, you have to create the queryset yourself, e.g. with pks:>>> objs = Entry.objects.bulk_create([ ... Entry(headline='This is a test'), ... Entry(headline='This is only a test'), ... ]) >>> pks = set(obj.pk for obj in objs) >>> update_dependent(Entry.objects.filter(pk__in=pks))
Note
Getting pks from
bulk_create
is not supported by all database adapters. With a local computed field you can “cheat” here by providing a sentinel:>>> MyComputedModel.objects.bulk_create([ ... MyComputedModel(comp='SENTINEL'), # here or as default field value ... MyComputedModel(comp='SENTINEL'), ... ]) >>> update_dependent(MyComputedModel.objects.filter(comp='SENTINEL'))
If the sentinel is beyond reach of the method result, this even ensures to update only the newly added records.
instance can also be a single model instance. Since calling
save
on a model instance will trigger this function by the post_save signal already it should not be called for single instances, if they get saved anyway.update_fields can be used to indicate, that only certain fields on the queryset changed, which helps to further narrow down the records to be updated.
Special care is needed, if a bulk action contains foreign key changes, that are part of a computed field dependency chain. To correctly handle that case, provide the result of
preupdate_dependent
as old argument like this:>>> # given: some computed fields model depends somehow on Entry.fk_field >>> old_relations = preupdate_dependent(Entry.objects.filter(pub_date__year=2010)) >>> Entry.objects.filter(pub_date__year=2010).update(fk_field=new_related_obj) >>> update_dependent(Entry.objects.filter(pub_date__year=2010), old=old_relations)
update_local=False disables model local computed field updates of the entry node. (used as optimization during tree traversal). You should not disable it yourself.
- bulk_updater(queryset: QuerySet, update_fields: Set[str] | None = None, return_pks: bool = False, local_only: bool = False, querysize: int | None = None) Set[Any] | None [source]
Update local computed fields and descent in the dependency tree by calling
update_dependent
for dependent models.This method does the local field updates on queryset:
eval local MRO of computed fields
expand update_fields
apply optional select_related and prefetch_related rules to queryset
walk all records and recalculate fields in update_fields
aggregate changeset and save as batched bulk_update to the database
By default this method triggers the update of dependent models by calling
update_dependent
with update_fields (next level of tree traversal). This can be suppressed by setting local_only=True.If return_pks is set, the method returns a set of altered pks of queryset.
- compute(instance: Model, fieldname: str) Any [source]
Returns the computed field value for
fieldname
. This method allows to inspect the new calculated value, that would be written to the database by a followingsave()
.Other than calling
update_computedfields
on an model instance this call is not destructive for old computed field values.
Get defined select_related rules for fields (all if none given).
Get defined prefetch_related rules for fields (all if none given).
- get_contributing_fks() Dict[Type[Model], Set[str]] [source]
Get a mapping of models and their local foreign key fields, that are part of a computed fields dependency chain.
Whenever a bulk action changes one of the fields listed here, you have to create a listing of the associated records with
preupdate_dependent
before doing the bulk change. After the bulk change feed the listing back toupdate_dependent
with the old argument.With
COMPUTEDFIELDS_ADMIN = True
in settings.py this mapping can also be inspected as admin view.
- computedfield_factory(field: Field[_ST, _GT], compute: Callable[[...], _ST], depends: Sequence[Tuple[str, Sequence[str]]] | None = None, select_related: Sequence[str] | None = None, prefetch_related: Sequence[Any] | None = None, querysize: int | None = None) Field[_ST, _GT] [source]
Factory for computed fields.
The method gets exposed as
ComputedField
to allow a more declarative code style with better separation of field declarations and function implementations. It is also used internally for thecomputed
decorator. Similar to the decorator, thecompute
function expects a single argument as model instance of the model it got applied to.Usage example:
from computedfields.models import ComputedField def calc_mul(inst): return inst.a * inst.b class MyModel(ComputedFieldsModel): a = models.IntegerField() b = models.IntegerField() sum = ComputedField( models.IntegerField(), depends=[('self', ['a', 'b'])], compute=lambda inst: inst.a + inst.b ) mul = ComputedField( models.IntegerField(), depends=[('self', ['a', 'b'])], compute=calc_mul )
- computed(field: Field[_ST, _GT], depends: Sequence[Tuple[str, Sequence[str]]] | None = None, select_related: Sequence[str] | None = None, prefetch_related: Sequence[Any] | None = None, querysize: int | None = None) Callable[[Callable[[...], _ST]], Field[_ST, _GT]] [source]
Decorator to create computed fields.
field should be a model concrete field instance suitable to hold the result of the decorated method. The decorator expects a keyword argument depends to indicate dependencies to model fields (local or related). Listed dependencies will automatically update the computed field.
Examples:
create a char field with no further dependencies (not very useful)
@computed(models.CharField(max_length=32)) def ...
create a char field with a dependency to the field
name
on a foreign key relationfk
@computed(models.CharField(max_length=32), depends=[('fk', ['name'])]) def ...
Dependencies should be listed as
['relation_name', concrete_fieldnames]
. The relation can span serveral models, simply name the relation in python style with a dot (e.g.'a.b.c'
). A relation can be any of foreign key, m2m, o2o and their back relations. The fieldnames must point to concrete fields on the foreign model.Note
Dependencies to model local fields should be list with
'self'
as relation name.With select_related and prefetch_related you can instruct the dependency resolver to apply certain optimizations on the update queryset.
Note
select_related and prefetch_related are stacked over computed fields of the same model during updates, that are marked for update. If your optimizations contain custom attributes (as with to_attr of a Prefetch object), these attributes will only be available on instances during updates from the resolver, never on newly constructed instances or model instances pulled by other means, unless you applied the same lookups manually.
To keep the computed field methods working under any circumstances, it is a good idea not to rely on lookups with custom attributes, or to test explicitly for them in the method with an appropriate plan B.
Caution
With the dependency resolver you can easily create recursive dependencies by accident. Imagine the following:
class A(ComputedFieldsModel): @computed(models.CharField(max_length=32), depends=[('b_set', ['comp'])]) def comp(self): return ''.join(b.comp for b in self.b_set.all()) class B(ComputedFieldsModel): a = models.ForeignKey(A) @computed(models.CharField(max_length=32), depends=[('a', ['comp'])]) def comp(self): return a.comp
Neither an object of A or B can be saved, since the
comp
fields depend on each other. While it is quite easy to spot for this simple case it might get tricky for more complicated dependencies. Therefore the dependency resolver tries to detect cyclic dependencies and might raise aCycleNodeException
during startup.If you experience this in your project try to get in-depth cycle information, either by using the
rendergraph
management command or by directly accessing the graph objects:intermodel dependency graph:
active_resolver._graph
mode local dependency graphs:
active_resolver._graph.modelgraphs[your_model]
union graph:
active_resolver._graph.get_uniongraph()
Note that there is not graph object, when running with
COMPUTEDFIELDS_MAP = True
. In that case either comment out that line settings.py and restart the server or build the graph at runtime with:>>> from computedfields.graph import ComputedModelsGraph >>> from computedfields.resolver import active_resolver >>> graph = ComputedModelsGraph(active_resolver.computed_models)
Also see the graph documentation here.
- precomputed(f: F) F [source]
- precomputed(skip_after: bool) Callable[[F], F]
Decorator for custom
save
methods, that expect local computed fields to contain already updated values on enter.By default local computed field values are only calculated once by the
ComputedFieldModel.save
method after your own save method.By placing this decorator on your save method, the values will be updated before entering your method as well. Note that this comes for the price of doubled local computed field calculations (before and after your save method).
To avoid a second recalculation, the decorator can be called with skip_after=True. Note that this might lead to desychronized computed field values, if you do late field changes in your save method without another resync afterwards.
- update_computedfields(instance: Model, update_fields: Iterable[str] | None = None) Iterable[str] | None [source]
Update values of local computed fields of instance.
Other than calling
compute
on an instance, this call overwrites computed field values on the instance (destructive).Returns
None
or an updated set of field names for update_fields. The returned fields might contained additional computed fields, that also changed based on the input fields, thus should extend update_fields on a save call.
- is_computedfield(model: Type[Model], fieldname: str) bool [source]
Indicate whether fieldname on model is a computed field.
- get_graphs() Tuple[Graph, Dict[Type[Model], ModelGraph], Graph] [source]
Return a tuple of all graphs as
(intermodel_graph, {model: modelgraph, ...}, union_graph)
.
- computedfields.resolver.active_resolver = <computedfields.resolver.Resolver object>
Currently active resolver.
- computedfields.resolver.BOOT_RESOLVER = <computedfields.resolver.Resolver object>
Resolver used during django bootstrapping. This is currently the same as active_resolver (treated as global singleton).
graph.py
Module containing the graph logic for the dependency resolver.
Upon application initialization a dependency graph of all project wide
computed fields is created. The graph does a basic cycle check and
removes redundant dependencies. Finally the dependencies are translated
to the resolver map to be used later by update_dependent
and in
the signal handlers.
- class computedfields.graph.IComputedField(verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=<class 'django.db.models.fields.NOT_PROVIDED'>, editable=True, serialize=True, unique_for_date=None, unique_for_month=None, unique_for_year=None, choices=None, help_text='', db_column=None, db_tablespace=None, auto_created=False, validators=(), error_messages=None)[source]
Bases:
Field
,Generic
[_ST
,_GT
]
- exception computedfields.graph.ComputedFieldsException[source]
Bases:
Exception
Base exception raised from computed fields.
- exception computedfields.graph.CycleException[source]
Bases:
ComputedFieldsException
Exception raised during path linearization, if a cycle was found. Contains the found cycle either as list of edges or nodes in
args
.
- exception computedfields.graph.CycleEdgeException[source]
Bases:
CycleException
Exception raised during path linearization, if a cycle was found. Contains the found cycle as list of edges in
args
.
- exception computedfields.graph.CycleNodeException[source]
Bases:
CycleException
Exception raised during path linearization, if a cycle was found. Contains the found cycle as list of nodes in
args
.
- class computedfields.graph.Edge(*args)[source]
Bases:
object
Class for representing an edge in
Graph
. The instances are created as singletons, callingEdge('A', 'B')
multiple times will always point to the same object.
- class computedfields.graph.Node(*args)[source]
Bases:
object
Class for representing a node in
Graph
. The instances are created as singletons, callingNode('A')
multiple times will always point to the same object.
- class computedfields.graph.Graph[source]
Bases:
object
Simple directed graph implementation.
- remove_node(node: Node) None [source]
Remove a node from the graph.
Warning
Removing edges containing the removed node is not implemented.
- add_edge(edge: Edge) None [source]
Add an edge to the graph. Automatically inserts the associated nodes.
- remove_edge(edge: Edge) None [source]
Removes an edge from the graph.
Warning
Does not remove leftover contained nodes.
- get_dot(format: str = 'pdf', mark_edges: Dict[Edge, Dict[str, Any]] | None = None, mark_nodes: Dict[Node, Dict[str, Any]] | None = None)[source]
Returns the graphviz object of the graph (needs the
graphviz
package).
- render(filename: PathLike | str | None = None, format: str = 'pdf', mark_edges: Dict[Edge, Dict[str, Any]] | None = None, mark_nodes: Dict[Node, Dict[str, Any]] | None = None) None [source]
Renders the graph to file (needs the
graphviz
package).
- view(format: str = 'pdf', mark_edges: Dict[Edge, Dict[str, Any]] | None = None, mark_nodes: Dict[Node, Dict[str, Any]] | None = None) None [source]
Directly opens the graph in the associated desktop viewer (needs the
graphviz
package).
- static edgepath_to_nodepath(path: Sequence[Edge]) List[Node] [source]
Converts a list of edges to a list of nodes.
- static nodepath_to_edgepath(path: Sequence[Node]) List[Edge] [source]
Converts a list of nodes to a list of edges.
- get_edgepaths() List[List[Edge]] [source]
Returns a list of all edge paths. An edge path is represented as list of edges.
Might raise a
CycleEdgeException
. For in-depth cycle detection useedge_cycles
, node_cycles` orget_cycles()
.
- get_nodepaths() List[List[Node]] [source]
Returns a list of all node paths. A node path is represented as list of nodes.
Might raise a
CycleNodeException
. For in-depth cycle detection useedge_cycles
,node_cycles
orget_cycles()
.
- get_cycles() Dict[FrozenSet[Edge], ICycleData] [source]
Gets all cycles in graph.
This is not optimised by any means, it simply walks the whole graph recursively and aborts as soon a seen edge gets entered again. Therefore use this and all dependent properties (
edge_cycles
andnode_cycles
) for in-depth cycle inspection only.As a start node any node on the left side of an edge will be tested.
Returns a mapping of
{frozenset(<cycle edges>): { 'entries': set(edges leading to the cycle), 'path': list(cycle edges in last seen order) }}
An edge in
entries
is not necessarily part of the cycle itself, but once entered it will lead to the cycle.
- property edge_cycles: List[List[Edge]]
Returns all cycles as list of edge lists. Use this only for in-depth cycle inspection.
- property node_cycles: List[List[Node]]
Returns all cycles as list of node lists. Use this only for in-depth cycle inspection.
- property is_cyclefree: bool
True if the graph contains no cycles.
For faster calculation this property relies on path linearization instead of the more expensive full cycle detection. For in-depth cycle inspection use
edge_cycles
ornode_cycles
instead.
- class computedfields.graph.ComputedModelsGraph(computed_models: Dict[Type[Model], Dict[str, IComputedField]])[source]
Bases:
Graph
Class to convert the computed fields dependency strings into a graph and generate the final resolver functions.
Steps taken:
resolve_dependencies
resolves the depends field strings to real model fields.The dependencies are rearranged to adjacency lists for the underlying graph.
The graph does a cycle check and removes redundant edges to lower the database penalty.
In
generate_lookup_map
the path segments of remaining edges are collected into the final lookup map.
- resolve_dependencies(computed_models: Dict[Type[Model], Dict[str, IComputedField]]) IResolvedDeps [source]
Converts depends rules into ORM lookups and checks the source fields’ existance.
Basic depends rules: - left side may contain any relation path accessible from an instance as
'a.b.c'
- right side may contain real database source fields (never relations)Deeper nested relations get automatically added to the resolver map:
fk relations are added on the model holding the fk field
reverse fk relations are added on related model holding the fk field
m2m fields and backrelations are added on the model directly, but only used for inter-model resolving, never for field lookups during
save
- generate_maps() Tuple[Dict[Type[Model], Dict[str, Dict[Type[Model], Tuple[Set[str], Set[str]]]]], Dict[Type[Model], Set[str]]] [source]
Generates the final lookup map and the fk map.
Schematically the lookup map is a reversed adjacency list of every source model with its fields mapping to the target models with computed fields it would update through a certain filter string:
src_model:[src_field, ...] --> target_model:[(cf_field, filter_string), ...]
During runtime
update_dependent
will use the the information to create select querysets on the target_models (roughly):qs = target_model._base_manager.filter(filter_string=src_model.instance) qs |= target_model._base_manager.filter(filter_string2=src_model.instance) ... bulk_updater(qs, cf_fields)
The fk map list all models with fk fieldnames, that contribute to computed fields.
Returns a tuple of (lookup_map, fk_map).
- generate_local_mro_map() Dict[Type[Model], ILocalMroData] [source]
Generate model local computed fields mro maps. Returns a mapping of models with local computed fields dependencies and their mro, example:
{ modelX: { 'base': ['c1', 'c2', 'c3'], 'fields': { 'name': ['c2', 'c3'], 'c2': ['c2', 'c3'] } } }
In the example modelX would have 3 computed fields, where c2 somehow depends on the field name. c3 itself depends on changes to c2, thus a change to name should run c2 and c3 in that specific order.
base lists all computed fields in topological execution order (mro). It is also used at runtime to cover a full update of an instance (
update_fields=None
).Note
Note that the actual values in fields are bitarrays to index positions of base, which allows quick field update merges at runtime by doing binary OR on the bitarrays.
- get_uniongraph() Graph [source]
Build a union graph of intermodel dependencies and model local dependencies. This graph represents the final update cascades triggered by certain field updates. The union graph is needed to spot cycles introduced by model local dependencies, that otherwise might went unnoticed, example:
global dep graph (acyclic):
A.comp --> B.comp, B.comp2 --> A.comp
modelgraph of B (acyclic):
B.comp --> B.comp2
Here the resulting union graph is not a DAG anymore, since both subgraphs short-circuit to a cycle of
A.comp --> B.comp --> B.comp2 --> A.comp
.
- class computedfields.graph.ModelGraph(model: Type[Model], local_dependencies: Dict[str, Set[str]], computed_fields: Dict[str, IComputedField])[source]
Bases:
Graph
Graph to resolve model local computed field dependencies in right calculation order.
- transitive_reduction() None [source]
Remove redundant single edges. Also checks for cycles. Note: Other than intermodel dependencies local dependencies must always be cyclefree.
- get_topological_paths() Dict[Node, List[Node]] [source]
Creates a map of all possible entry nodes and their topological update path (computed fields mro).
- generate_field_paths(tpaths: Dict[Node, List[Node]]) Dict[str, List[str]] [source]
Convert topological path node mapping into a mapping containing the fieldnames.
- generate_local_mapping(field_paths: Dict[str, List[str]]) ILocalMroData [source]
Generates the final model local update table to be used during
ComputedFieldsModel.save
. Output is a mapping of local fields, that also update local computed fields, to a bitarray containing the computed fields mro, and the base topologcial order for a full update.
handlers.py
Module containing the database signal handlers.
The handlers are registered during application startup
in apps.ready
.
Note
The handlers are not registered in the managment
commands makemigrations
, migrate
and help
.
- computedfields.handlers.get_old_handler(sender: Type[Model], instance: Model, **kwargs) None [source]
get_old_handler
handler.pre_save
signal handler to spot incoming fk relation changes. This is needed to correctly update old relations after fk changes, that would contain dirty computed field values after a save. The actual updates on old relations are done duringpost_save
. Skipped during fixtures.
- computedfields.handlers.postsave_handler(sender: Type[Model], instance: Model, **kwargs) None [source]
post_save
handler.Directly updates dependent objects. Skipped during fixtures.
- computedfields.handlers.predelete_handler(sender: Type[Model], instance: Model, **_) None [source]
pre_delete
handler.Gets all dependent objects as pk lists and saves them in thread local storage.
- computedfields.handlers.postdelete_handler(sender: Type[Model], instance: Model, **kwargs) None [source]
post_delete
handler.Loads the dependent objects from the previously saved pk lists and updates them.
- computedfields.handlers.merge_pk_maps(obj1: Dict[Type[Model], List[Any]], obj2: Dict[Type[Model], List[Any]]) Dict[Type[Model], List[Any]] [source]
Merge pk map in obj2 on obj1. Updates obj1 inplace and also returns it.
- computedfields.handlers.merge_qs_maps(obj1: Dict[Type[Model], List[Any]], obj2: Dict[Type[Model], List[Any]]) Dict[Type[Model], List[Any]] [source]
Merge queryset map in obj2 on obj1. Updates obj1 inplace and also returns it.
- computedfields.handlers.m2m_handler(sender: Type[Model], instance: Model, **kwargs) None [source]
m2m_change
handler.Works like the other handlers but on the corresponding m2m actions.
Note
The handler triggers updates for both ends of the m2m relation, which might lead to massive database interaction.
admin.py
- class computedfields.admin.ComputedModelsAdmin(model, admin_site)[source]
Bases:
ModelAdmin
Shows all
ComputedFieldsModel
models with their field dependencies in the admin. Also renders the dependency graphs if thegraphviz
package is installed.- actions = None
- list_display_links = None