Skip to content

ertgl/django-xformula

Repository files navigation

django-xformula

A dynamic formula-to-Python and formula-to-SQL evaluator for Django applications.

Table of Contents

Overview

django-xformula is a flexible query evaluator built for Django applications. It uses the default syntax grammar and parser generated by the XFormula project, to translate formulas into Django ORM query expressions. With its dynamic evaluation engine, it becomes very easy to design a DSL (domain-specific language) for defining, storing, and loading complex queries, opening the door to a wide range of advanced use cases.

Use Cases

Here are some typical ways django-xformula can be used:

  • Open APIs: Let external clients or integrations filter, annotate, or aggregate data with combinable formulas.
  • Authorization Rules: Store and enforce query-based authorization rules directly in the database.
  • Dynamic Business Rules: Update business rules on the fly without redeploying, giving departments control over their own rules.
  • Personalized Experiences: Allow end users to adjust how they interact with your app, such as pre-filtering, sorting, or pre-defining conditions for webhooks.

Of course, these are just examples. Any situation where you need dynamic query evaluation can benefit.

Features

django-xformula focuses on being minimal but powerful, giving you the building blocks to create query evaluators suited to your needs.

Bidirectional Operators

The same syntax works for both Python and Django query evaluations. Expressions are interpreted based on context:

  • With a QuerySet, evaluation returns a QuerySet.
  • With a Q object, evaluation produces a Q.
  • With a Combinable or Field, it's processed as a Combinable.
  • With a model instance, evaluation returns a Value holding the instance's primary key.
  • Otherwise, expressions fall back to standard Python rules.

This makes it possible to use a formula both as a database query and as a Python expression. It is particularly useful in scenarios such as filtering data in GraphQL subscriptions, etc.

Zero Built-in Variables by Default

By default, any undefined variable in a formula is treated as a Django F object. This ensures that only approved variables and functions are used in query evaluations, preventing potential security risks.

Customizable Attribute Getter

Direct attribute access is blocked by default. Attempting it raises a ForbiddenAttribute error (a subclass of Django's PermissionDenied). You can customize this to allow safe attributes, adding a strong security layer.

Customizable Function Caller

Function calls are also restricted by default. Unauthorized calls raise a ForbiddenCall error (also a subclass of PermissionDenied). You can configure the evaluator to only allow specific functions, balancing flexibility with safety.

Installation

django-xformula is available on PyPI and can be installed with pip or any other compatible package manager:

pip install django-xformula

Usage

Here's a simple example of using django-xformula to filter a queryset with a user-supplied formula:

from operator import call

from django.db.models import Q, QuerySet
from django.db.models.functions import Length
from django_xformula import QueryEvaluator

# Import your models.
from myapp.models import MyModel


query_evaluator = QueryEvaluator()


def resource_view(request):
    query = request.GET.get("q", "")
    context = QueryEvaluator.Context(
      # Provide Python objects to the formula context.
      builtins={
        "Length": Length,
        "me": request.user,
      },
      # WARNING: Allowing arbitrary function calls is unsafe.
      # Use a secure function caller (e.g., whitelist checking).
      call=call,
      # WARNING: Direct attribute access is unsafe.
      # Restrict allowed attributes through a secure getter (e.g., whitelist
      # checking per object type).
      getattr=getattr,
    )
    q_or_result = query_evaluator.evaluate(q, context)
    if isinstance(q_or_result, QuerySet):
        return render_table(q_or_result)
    if isinstance(q_or_result, Q):
        queryset = MyModel.objects.filter(q_or_result)
        return render_table(queryset)
    # If the formula does not represent a database query,
    # return the result of the evaluated expression (e.g., "1 + 1").
    return render_result(q_or_result)

Sample formulas you can use with the built-ins defined in the example above:

  • Records where owner is the current user:

    owner is me
  • Records where owner is not the current user:

    owner is not me
  • Records where name is longer than 5 characters:

    Length(name) > 5
  • Records accessible if the user is staff, the owner, or the record is public:

    me.is_staff or me is owner or is_public
  • Records where version is the current version, but only if a condition passes first:

    check_condition() and version is CURRENT_VERSION

Operators

For a full list of supported operators, see the XFormula default precedence list.

Syntax

The XFormula project provides a default syntax, where the top-level non-terminal type is expression. django-xformula uses this syntax by default.
If customization is needed, you can find the relevant examples in the XFormula README and in its features.

You can also check the EBNF grammar of the default syntax in the out/Grammar.lark file.

Troubleshooting

Some common issues you might run into:

  • ForbiddenAttribute error: The formula is trying to access an attribute that isn't allowed. Update your custom attribute getter to allow it.
  • ForbiddenCall error: The formula is trying to call a function that isn't allowed. Update your custom function caller to whitelist the function.
  • Invalid query syntax: The formula doesn't match XFormula grammar. Double-check the syntax.
  • Unsupported database function: The database backend doesn't support the function you're trying to use. Verify backend capabilities.

License

This project is licensed under the MIT License.

See the LICENSE file for details.

About

A dynamic formula-to-Python and formula-to-SQL evaluator for Django applications.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •