A dynamic query evaluator for Django applications.
django-xformula is a powerful and flexible query evaluator designed for Django applications. Built on top of the XFormula language front-end, it enables developers to transform formulas into Django ORM query expressions. Its dynamic evaluation capabilities simplify having a DSL to define complex queries, making it easier to build advanced features.
Whether you're building customizable APIs, implementing rule-based authorization, or enabling dynamic data filtering, django-xformula helps to create efficient solutions.
Various applications can benefit from the dynamic query evaluation provided by django-xformula. Here are a few examples:
- Open APIs: Enable external clients and integrated services to filter, annotate or aggregate data using intuitive formulas.
- Authorization Rules: Implement query-based authorization rules that are stored and managed directly in the database.
- Dynamic Business Rules: Hot-swap business rules without redeploying the application, allowing some departments to define and manage their own rules.
- Personalized Experiences: Allow users to customize their interactions with the application through dynamic queries. E.g. pre-filtering, sorting, conditional webhooks, etc.
The flexibility of django-xformula makes it suitable for many other scenarios where dynamic query evaluation is required.
django-xformula provides minimal yet powerful features that enable developers to build complex query evaluators for their specific needs.
django-xformula uses the same syntax for both Python and Django query evaluations. Expressions are intelligently interpreted based on their content:
- When a
QuerySet
is present, the expression is evaluated as aQuerySet
. - When a
Q
object is involved, it is evaluated as aQ
. - If the expression includes a
Combinable
orField
, it will be processed as aCombinable
. - For expressions containing a model instance, the evaluator returns a
Value
holding the instance's primary key. - All other operations are handled using standard Python evaluation rules.
By default, any variable referenced in a formula that is not explicitly defined
within the built-in context is treated as an F
object. This ensures that only
approved variables and functions are used in query evaluations, preventing
potential security risks.
By default, accessing an object's attribute within a formula is prohibited and
will raise a ForbiddenAttribute
error (a subclass of Django's
PermissionDenied
). This behavior can be customized to specify which
attributes are accessible in formulas, adding an extra layer of security.
Function calls within formulas are restricted by default, raising a
ForbiddenCall
error (inheriting from Django's PermissionDenied
) if invoked.
The function caller can be customized to allow specific functions, balancing
flexibility with security.
django-xformula is available on PyPI
. You can install it using a compatible
package manager, such as pip
:
pip install django-xformula
Using django-xformula in your Django application is straightforward. The following example demonstrates how to filter a Django queryset using 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
def resource_view(request):
evaluator = QueryEvaluator()
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 can be dangerous.
# It is highly recommended to implement a secure function caller.
# E.g. checking if the function is in a whitelist.
call=call,
# WARNING: Direct attribute access is unsafe.
# Implement a secure attribute getter based on your requirements.
# E.g. checking if the object or attribute is in a whitelist.
getattr=getattr,
)
q_or_result = 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)
In this example, users can filter MyModel
instances using formulas that
reference the current user via the me
variable.
See the following sample formulas to get an idea of what you can achieve:
-
Get all records where
owner
is the current user:owner is me
-
Get all records where
owner
is not the current user:owner is not me
-
Get records where
name
has more than5
characters:Length(name) > 5
-
Get the records if the current user is either an staff member or the owner, or the record is public:
me.is_staff or me is owner or is_public
-
Get the records where the version is the current version, only if the condition is met before querying the database:
check_condition() and version is CURRENT_VERSION
For a full list of supported operators, see the XFormula default precedence list.
When using django-xformula, you may encounter issues related to query syntax, attribute access, or function calls. Here are some common issues and solutions:
-
Issue:
ForbiddenAttribute
error when accessing an attribute.- Solution: Ensure that the attribute is allowed in the custom attribute getter.
-
Issue:
ForbiddenCall
error when calling a function.- Solution: Ensure that the function is allowed in the custom function caller.
-
Issue: Invalid query syntax.
- Solution: Verify that the formula syntax follows the default grammar provided by XFormula.
-
Issue: Unsupported database function.
- Solution: Check if the database backend supports the database function being used in the query.
This project is licensed under the MIT License.
See the LICENSE file for more information.