Workflow Query View Integration

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

Table of Contents

  1. Overview
  2. SaveQueryView (POST)
  3. SaveQueryDetailView (GET execute=1)
  4. BuildQueryView (Template Context)
  5. Related Guides

Overview

The query builder views are the HTTP surface of the enhancements. SaveQueryView accepts annotations when saving, SaveQueryDetailView accepts object_id when executing, and BuildQueryView exposes aggregate metadata to the builder UI.

flowchart LR
    UI["query builder UI"] -->|POST JSON| SQV["SaveQueryView"]
    SQV --> SQ["SavedQuery.filters_json"]
    UI -->|GET execute| SQD["SaveQueryDetailView"]
    SQD -->|object_id| SQ
    BQV["BuildQueryView"] -->|filters_by_model_json| UI

All views live in enezaworkflowengine/views/querybuilder.py.


SaveQueryView (POST)

SaveQueryView.post() (views/querybuilder.py:79-128) accepts annotations in either of two shapes:

python
# Inside the query object (preferred):
{
    "query": {
        "condition": "AND",
        "annotations": [...],
        "rules": [...]
    }
}

# As a separate top-level key (also supported):
{
    "query": {"condition": "AND", "rules": [...]},
    "annotations": [...]
}

The view merges a separate annotations key into filters_json before saving (views/querybuilder.py:104-107):

python
filters_json = rules
annotations = data.get('annotations')
if annotations and isinstance(filters_json, dict):
    filters_json['annotations'] = annotations

It also requires name, model_slug, and query, and resolves the model by slug against get_allowed_models() (views/querybuilder.py:90-103).


SaveQueryDetailView (GET execute=1)

SaveQueryDetailView.get_context_data() executes the query when execute=1 and forwards an optional object_id (views/querybuilder.py:230-248):

python
if self.request.GET.get('execute') == '1':
    object_id = self.request.GET.get('object_id')
    queryset = self.object.execute(object_id=object_id)
    total_count = queryset.count()
    if total_count > 0:
        results = list(queryset.values()[:100])

Request shape:

text
GET /workflow/savedquery/<pk>/?execute=1&object_id=<uuid>

If execution raises, the error is captured into context['execution_error'] and the count is set to 0 (views/querybuilder.py:246-248).


BuildQueryView (Template Context)

BuildQueryView.get_context_data() builds filters_by_model and attaches an aggregates structure per model (views/querybuilder.py:68-74):

python
filters_by_model = {
    m['slug']: self.get_model_filters(m['app_label'], m['model_name'], model_config=m)
    for m in allowed_models
}
for m in allowed_models:
    filters_by_model[m['slug']]['aggregates'] = self.get_aggregate_options(m)
ctx['filters_by_model_json'] = json.dumps(filters_by_model)

get_aggregate_options() returns a hierarchical structure — not a flat option list (views/mixin.py:152-214):

json
{
  "frameworktestdocument": {
    "direct": [/* field filters */],
    "related": [/* related model filters */],
    "aggregates": {
      "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"}
      ]
    }
  }
}

The builder UI combines a related model, a numeric field, and a function to form an annotation, then derives the alias. See Workflow Aggregate Metadata API.