Workflow Aggregate Metadata API
Developer guide for the Enezaframework workflow engine. Part of the Workflow series.
Table of Contents
- Overview
get_aggregate_options()- Return Structure
- Numeric Field Types Discovered
- Functions Offered
- Related Guides
Overview
QueryBuilderMixin.get_aggregate_options() (enezaworkflowengine/views/mixin.py:152-214) produces the aggregate metadata the query builder UI shows when a user adds an aggregate condition. For each relationship in a model config it collects the related model's numeric fields, and it always offers the five supported functions.
It returns a hierarchical structure — a related_models list plus a functions list — not a flat list of ready-made options. The UI combines one relationship, one field, and one function to form an annotation entry.
get_aggregate_options()
# enezaworkflowengine/views/mixin.py:152-214
def get_aggregate_options(self, model_config):
related_models = []
relationships = model_config.get('relationships', []) if model_config else []
for rel_def in relationships:
try:
rel_model = apps.get_model(rel_def['related_app'], rel_def['related_model'])
except Exception:
continue
field_name = rel_def['field_name']
rel_label = rel_def.get('label', field_name.replace('_', ' ').title())
fields = []
for field in rel_model._meta.get_fields():
if isinstance(field, (
models.IntegerField, models.BigIntegerField,
models.SmallIntegerField, models.PositiveIntegerField,
models.PositiveSmallIntegerField, models.DecimalField,
models.FloatField,
)):
field_type = 'integer' if isinstance(field, (
models.IntegerField, models.BigIntegerField,
models.SmallIntegerField, models.PositiveIntegerField,
models.PositiveSmallIntegerField,
)) else 'double'
fields.append({
'name': field.name,
'label': field.verbose_name.title() if field.verbose_name else field.name.replace('_', ' ').title(),
'type': field_type,
})
related_models.append({
'field_name': field_name,
'label': rel_label,
'fields': fields,
})
functions = [
{'name': 'sum', 'label': 'Sum'},
{'name': 'count', 'label': 'Count'},
{'name': 'avg', 'label': 'Average'},
{'name': 'min', 'label': 'Minimum'},
{'name': 'max', 'label': 'Maximum'},
]
return {
'related_models': related_models,
'functions': functions,
}
Note that it iterates model_config['relationships'] directly, so a model whose relationships key is omitted (auto-discovery) yields an empty related_models list here even though get_model_filters() auto-discovers related filters. To surface aggregates, declare relationships explicitly.
Return Structure
{
'related_models': [
{
'field_name': 'lines',
'label': 'Test Document Line',
'fields': [
{'name': 'amount', 'label': 'Amount', 'type': 'double'},
{'name': 'quantity', 'label': 'Quantity', 'type': 'integer'},
],
},
],
'functions': [
{'name': 'sum', 'label': 'Sum'},
{'name': 'count', 'label': 'Count'},
{'name': 'avg', 'label': 'Average'},
{'name': 'min', 'label': 'Minimum'},
{'name': 'max', 'label': 'Maximum'},
],
}
BuildQueryView and SaveQueryDetailView attach this under filters_by_model[<slug>]['aggregates'] (views/querybuilder.py:73, :261). The UI's job is to turn a (related_model, field, function) selection into an annotation dict with a generated alias for the annotations array.
flowchart LR
CFG["model_config.relationships"] --> GA["get_aggregate_options()"]
GA --> RM["related_models[] with numeric fields"]
GA --> FN["functions[]"]
RM --> UI["Builder UI"]
FN --> UI
UI --> ANN["annotations[] entry"]Numeric Field Types Discovered
Detected as integer:
IntegerField,BigIntegerField,SmallIntegerFieldPositiveIntegerField,PositiveSmallIntegerField
Detected as double:
DecimalField,FloatField
All seven types are aggregatable; the type value drives UI input typing.
Functions Offered
| Function | Label |
|---|---|
sum |
Sum |
count |
Count |
avg |
Average |
min |
Minimum |
max |
Maximum |
These match AGGREGATE_FUNCTION_MAP in enezaworkflowengine/utils.py:180-186.