Workflow Aggregate Metadata API

Developer guide for the Enezaframework workflow engine. Part of the Workflow series.

Table of Contents

  1. Overview
  2. get_aggregate_options()
  3. Return Structure
  4. Numeric Field Types Discovered
  5. Functions Offered
  6. 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()

python
# 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

python
{
    '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, SmallIntegerField
  • PositiveIntegerField, 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.