Django Remote Queryset (a.k.a. DRQ) is a library to execute rich ORM queries from client-side using the same interface of Django ORM.
DRQ defines a JSON syntax to call all Django's Queryset methods.
Consider this example:
from myapp.models import MyModel
not_null_title = MyModel.objects.filter(title__isnull=False)
DRQ query syntax for the previous filter is the following
{
"_query_class" : "filter",
"_condition" : "title__isnull",
"_value" : false
}
DRQ supports Django Rest Framework and automatically decodes and applies this filter to any GET request.
DRQ is distrubuted through pypi
$ pip install django-remote-queryset
- Add django_remote_queryset to your INSTALLED_APPS:
INSTALLED_APPS = [
...
'rest_framework',
'django_remote_queryset',
...
]
Django rest_framework is strongly recommended but if you prefer to use DRQ without it you can see Advanced Usage section.
- Extend DRQModelViewSet for your model:
from django_remote_queryset.viewset import DRQModelViewSet
class MyModelModelViewSet(DRQModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelModelSerializer
And use it like any other Django REST Framework ViewSet!
- Enjoy filtering from your browser!
GET: http://yourdomain/my_model_rest_path/?query=
query = {
"_query_class" : "filter",
"_condition" : "title__isnull",
"_value" : false
}
For further informations on DRF ViewSets see Django Rest Framework documentation.
This repository contains a example_django_project folder. Try it doing the following:
$ cd example_django_app
# Install site dependencies
$ pip install -r requirements.txt
# Create sqlite database
$ python manage.py migrate
# Create demo data
$ python manage.py create_demo_data
# Run the server
$ python manage.py runserver
Then:
- Go to http://localhost:8000 and you should see the usual Django Rest Framework page.
- Go to http://localhost:8000/polls and you should see the list of all polls available
- Go to http://localhost:8000/polls/?query={"_query_class":"filter","_condition":"title__icontains","_value":"DRQ"}
It's possible to extend/customize DRQ using it in your extisting ViewSets or integrating it in your own custom Views/Django code.
DRQ is implemented using DRF's filter_backends, you can add it to your ModelViewSet easily
from django_remote_queryset.backend import DRQFilterBackend
class AnotherModelViewSet(ModelViewSet):
filter_backends = ( ..., DRQFilterBackend, ...)
You can use directly the QueryDecoder to decode and apply the json_query to any queryset
from django_remote_queryset.queries import QueryDecoder
...
original_queryset = MyModel.objects.all()
json_query = {
"_query_class" : "filter",
"_condition" : "title__isnull",
"_value" : false
}
query = QueryDecoder \
.decodeJSONQuery(json_query)
if query is not None:
filtered_queryset = query.applyOnQuerySet(original_queryset)
DRQ Queries are built using the Composite Pattern. Several queries are already available and the Framework could be extended with custom ones. All the queries are contained in django_remote_queryset.queries module.
This is the abstract one, defines the interface for every child.
class Query():
def __init__(self, json_query):
"""
Initializes the sub_tree of this query starting from the json_query parameter
:param json_query: dict
"""
pass
def applyOnQuerySet(self, queryset):
"""
Applies the sub_tree of this query to the queryset passed as parameter
:param queryset: Queryset
:return: Queryset
"""
return queryset
It's JSON syntax is the following
{
"_query_class":"query"
}
This query applies the .filter( ... ) method on the queryset
It's JSON syntax is the following
{
"_query_class": "filter",
"_condition": [string] | string,
"_value": [*] | *
}
Examples:
- Single lookup
Person.filter(age__gte=4)
{
"_query_class": "filter",
"_condition": "age__gte",
"_value": 4
}
- Multiple lookups
Person.filter(age__gte=4, name__icontains='Nic')
{
"_query_class": "filter",
"_condition": ["age__gte","name__icontains"],
"_value": [4,"Nic"]
}
This query applies the .exclude( ... ) method on the queryset
It's JSON syntax is the following
{
"_query_class": "exclude",
"_condition": [string] | string,
"_value": [*] | *
}
Examples:
- Single lookup
Person.exclude(age__gte=4)
{
"_query_class": "exclude",
"_condition": "age__gte",
"_value": 4
}
- Multiple lookups
Person.exclude(age__gte=4, name__icontains='Nic')
{
"_query_class": "exclude",
"_condition": ["age__gte","name__icontains"],
"_value": [4,"Nic"]
}
This query creates a chain of subqueries applying all it's sub_tree on the queryset.
It's JSON syntax is the following
{
"_query_class": "compositequery",
"_sub_queries":[
... other queries ...
]
}
The subqueries are applied as a chain.
for query in self._sub_queries:
queryset = query.applyOnQuerySet(queryset)
Examples:
- Chained filters
Person.objects.filter(age__gte=4).filter(name__icontains='Nic')
{
"_query_class": "compositequery",
"_sub_queries":[
{
"_query_class": "filter",
"_condition": "age__gte",
"_value": 4
},
{
"_query_class": "filter",
"_condition": "name__icontains",
"_value": "Nic"
}
]
}
- Chained mixed queries
Person.objects.filter(age__gte=4).exclude(name__icontains='Nic')
{
"_query_class": "compositequery",
"_sub_queries":[
{
"_query_class": "filter",
"_condition": "age__gte",
"_value": 4
},
{
"_query_class": "exclude",
"_condition": "name__icontains",
"_value": "Nic"
}
]
}