A dynamic formula-to-Python and formula-to-SQL evaluator for Django applications.
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.
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.
django-xformula focuses on being minimal but powerful, giving you the building blocks to create query evaluators suited to your needs.
The same syntax works for both Python and Django query evaluations. Expressions are interpreted based on context:
- With a
QuerySet, evaluation returns aQuerySet. - With a
Qobject, evaluation produces aQ. - With a
CombinableorField, it's processed as aCombinable. - With a model instance, evaluation returns a
Valueholding 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.
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.
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.
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.
django-xformula is available on PyPI and can be installed with pip or any other compatible package manager:
pip install django-xformulaHere'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
owneris the current user:owner is me
-
Records where
owneris not the current user:owner is not me
-
Records where
nameis 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
versionis the current version, but only if a condition passes first:check_condition() and version is CURRENT_VERSION
For a full list of supported operators, see the XFormula default precedence list.
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.
Some common issues you might run into:
ForbiddenAttributeerror: The formula is trying to access an attribute that isn't allowed. Update your custom attribute getter to allow it.ForbiddenCallerror: 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.
This project is licensed under the MIT License.
See the LICENSE file for details.