https://github.com/ertgl/django-xformula
Django query evaluator, built on top of XFormula language front-end.
https://github.com/ertgl/django-xformula
django domain-specific-language dynamic-filtering python query
Last synced: 8 months ago
JSON representation
Django query evaluator, built on top of XFormula language front-end.
- Host: GitHub
- URL: https://github.com/ertgl/django-xformula
- Owner: ertgl
- License: mit
- Created: 2022-11-12T20:34:37.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-06-19T00:12:39.000Z (12 months ago)
- Last Synced: 2025-08-30T07:56:42.585Z (9 months ago)
- Topics: django, domain-specific-language, dynamic-filtering, python, query
- Language: Python
- Homepage:
- Size: 426 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# django-xformula
A dynamic formula-to-Python and formula-to-SQL evaluator for
[Django](https://www.djangoproject.com/) applications.
## Table of Contents
- [Overview](#overview)
- [Use Cases](#use-cases)
- [Features](#features)
- [Bidirectional Operators](#bidirectional-operators)
- [Zero Built-in Variables by Default](#zero-built-in-variables-by-default)
- [Customizable Attribute Getter](#customizable-attribute-getter)
- [Customizable Function Caller](#customizable-function-caller)
- [Installation](#installation)
- [Usage](#usage)
- [Operators](#operators)
- [Syntax](#syntax)
- [Troubleshooting](#troubleshooting)
- [License](#license)
## Overview
django-xformula is a flexible query evaluator built for Django applications. It
uses the default syntax grammar and parser generated by the
[XFormula](https://github.com/ertgl/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:
```sh
pip install django-xformula
```
## Usage
Here's a simple example of using django-xformula to filter a queryset with a
user-supplied formula:
```py
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:
```python
owner is me
```
- Records where `owner` is not the current user:
```python
owner is not me
```
- Records where `name` is longer than 5 characters:
```python
Length(name) > 5
```
- Records accessible if the user is staff, the owner, or the record is public:
```python
me.is_staff or me is owner or is_public
```
- Records where `version` is the current version, but only if a condition
passes first:
```python
check_condition() and version is CURRENT_VERSION
```
### Operators
For a full list of supported operators, see the
[XFormula default precedence list](https://github.com/ertgl/xformula/blob/main/src/xformula/syntax/core/operations/default_operator_precedences.py#L16).
### 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](https://github.com/ertgl/xformula) and in its
[features](https://github.com/ertgl/xformula/tree/4e1e3a88200ccbe22d8aa477001d9af68dd91ab4/src/xformula/syntax/core/features).
You can also check the EBNF grammar of the default syntax in the
[out/Grammar.lark](https://github.com/ertgl/xformula/blob/main/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](https://docs.djangoproject.com/en/5.2/ref/databases/).
## License
This project is licensed under the
[MIT License](https://opensource.org/license/mit).
See the [LICENSE](LICENSE) file for details.