API Reference
models.py
- class computedfields.models.ComputedFieldsModel(*args, **kwargs)[source]
Bases:
_ComputedFieldsModelBase,ModelAbstract base class for models with computed fields. Overloads
saveto 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
@precomputeddecorator 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_computedfieldsbefore writing the instance or by a laterupdate_dependentcall.
- 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, default_on_create: bool | None = False) 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, default_on_create: bool | None = False) 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, _is_recursive: bool = False) 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:
ContentTypeProxy model to list all
ComputedFieldsModelmodels with their field dependencies in admin. This might be useful during development. To enable it, setCOMPUTEDFIELDS_ADMIN = Truein settings.py.- exception DoesNotExist
Bases:
DoesNotExist
- exception MultipleObjectsReturned
Bases:
MultipleObjectsReturned
- class computedfields.models.ContributingModelsModel(*args, **kwargs)[source]
Bases:
ContentTypeProxy 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 = Truein 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:
ComputedFieldsExceptionException raised during model and field registration or dependency resolving.
- class computedfields.resolver.Resolver[source]
Bases:
objectHolds 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 respects a certain 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.readyto 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
initializeno more models or fields can be registered to the resolver, andcomputed_modelsand 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_dependentas 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, _is_recursive: bool = False) 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_createreturns 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_createis 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
saveon 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_dependentas 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_dependentfor 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_dependentwith 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_computedfieldson 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_dependentbefore doing the bulk change. After the bulk change feed the listing back toupdate_dependentwith the old argument.With
COMPUTEDFIELDS_ADMIN = Truein 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, default_on_create: bool | None = False) Field[_ST, _GT][source]
Factory for computed fields.
The method gets exposed as
ComputedFieldto allow a more declarative code style with better separation of field declarations and function implementations. It is also used internally for thecomputeddecorator. Similar to the decorator, thecomputefunction 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, default_on_create: bool | None = False) 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
nameon 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 listed 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.
With default_on_create set to
Truethe function calculation will be skipped for newly created or copy-cloned instances, instead the value will be set from the inner field’s default argument.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
compfields 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 aCycleNodeExceptionduring startup.If you experience this in your project try to get in-depth cycle information, either by using the
rendergraphmanagement command or by directly accessing the graph objects:intermodel dependency graph:
active_resolver._graphmodel local dependency graphs:
active_resolver._graph.modelgraphs[your_model]union graph:
active_resolver._graph.get_uniongraph()
Also see the graph documentation here.
- precomputed(f: F) F[source]
- precomputed(skip_after: bool) Callable[[F], F]
Decorator for custom
savemethods, 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.savemethod 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
computeon an instance, this call overwrites computed field values on the instance (destructive).Returns
Noneor 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, db_comment=None, db_default=<class 'django.db.models.fields.NOT_PROVIDED'>)[source]
Bases:
Field,Generic[_ST,_GT]
- exception computedfields.graph.ComputedFieldsException[source]
Bases:
ExceptionBase exception raised from computed fields.
- exception computedfields.graph.CycleException[source]
Bases:
ComputedFieldsExceptionException 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:
CycleExceptionException 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:
CycleExceptionException 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:
objectClass 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:
objectClass 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:
objectSimple 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
graphvizpackage).
- 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
graphvizpackage).
- 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
graphvizpackage).
- 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_cyclesorget_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_cyclesandnode_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
entriesis 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_cyclesornode_cyclesinstead.
- class computedfields.graph.ComputedModelsGraph(computed_models: Dict[Type[Model], Dict[str, IComputedField]])[source]
Bases:
GraphClass to convert the computed fields dependency strings into a graph and generate the final resolver functions.
Steps taken:
resolve_dependenciesresolves 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_mapthe 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 are expanded via their through model
- 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_dependentwill 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.compmodelgraph 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:
GraphGraph 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_handlerhandler.pre_savesignal 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_savehandler.Directly updates dependent objects. Skipped during fixtures.
- computedfields.handlers.predelete_handler(sender: Type[Model], instance: Model, **_) None[source]
pre_deletehandler.Gets all dependent objects as pk lists and saves them in thread local storage.
admin.py
- class computedfields.admin.ComputedModelsAdmin(model, admin_site)[source]
Bases:
ModelAdminShows all
ComputedFieldsModelmodels with their field dependencies in the admin. Also renders the dependency graphs if thegraphvizpackage is installed.- actions = None
- list_display_links = None
signals.py
- computedfields.signals.resolver_start = <django.dispatch.dispatcher.Signal object>
Signal sent upon start of a tree update. sender points to the resolver instance.
- computedfields.signals.resolver_update = <django.dispatch.dispatcher.Signal object>
Signal sent after a bulk update on a model.
Arguments sent with this signal:
- sender
Resolver instance responsible for the updates.
- model
The model class.
- fields
Set of computed field names, that were updated.
- pks
List of model instance pks, that were updated.
Note that this signal is sent immediately after the bulk update within the whole (recursive) dependency tree update done by the resolver. Furthermore your handler will be called under the update’s transaction umbrella.
To not disrupt the resolver’s tree update, you must avoid any raising code pattern in your handler code. Database interactions should be avoided, as the state is not fully resynced yet.
Also refer to the manual on how to use this signal in a safe way.
- computedfields.signals.resolver_exit = <django.dispatch.dispatcher.Signal object>
Signal sent upon exit of a tree update. sender points to the resolver instance.