- Notifications
You must be signed in to change notification settings - Fork15
cloudblue/django-rql
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
django-rql
is the Django app, that adds RQL filtering to your application.This library is based on corelib-rql library.
RQL (Resource query language) is designed for modern application development. It is built for the web, ready for NoSQL, and highly extensible with simple syntax.This is a query language fast and convenient database interaction. RQL was designed for use in URLs to request object-style data structures.
- Comparison (eq, ne, gt, ge, lt, le, like, ilike, search)
- List (in, out)
- Logical (and, or, not)
- Constants (null(), empty())
- Ordering (ordering)
- Select (select)
- Tuple (t)
Full documentation is available athttps://django-rql.readthedocs.org.
fromdj_rql.filter_clsimportRQLFilterClass,RQL_NULLfrompy_rql.constantsimportFilterLookupsclassModelFilterClass(RQLFilterClass):""" MODEL - Django ORM model FILTERS - List of filters EXTENDED_SEARCH_ORM_ROUTES - List of additional Django ORM fields for search DISTINCT - Boolean flag, that specifies if queryset must always be DISTINCT SELECT - Boolean flag, that specifies if Filter Class supports select operations and queryset optimizations OPENAPI_SPECIFICATION - Python class that renders OpenAPI specification MAX_ORDERING_LENGTH_IN_QUERY - Integer max allowed number of provided ordering filters in query ordering expression ALLOWED_ORDERING_PERMUTATIONS_IN_QUERY - Set of tuples of strings to specify a set of allowed ordering permutations Filters can be set in two ways: 1) string (default settings are calculated from ORM) 2) dict (overriding settings for specific cases) Filter Dict Structure { 'filter': str # or 'namespace': str 'source': str # or 'sources': iterable # or 'custom': bool # or 'dynamic': bool 'field': obj 'lookups': set 'qs': obj 'use_repr': bool # can't be used in namespaces 'ordering': bool # can't be true if 'use_repr=True' 'search': bool # can't be true if 'use_repr=True' 'hidden': bool } """MODEL=ModelFILTERS= ['id', {# `null_values` can be set to override ORM is_null behaviour# RQL_NULL is the default value if NULL lookup is supported by field'filter':'title','null_values': {RQL_NULL,'NULL_ID'},'ordering':False, }, {# `ordering` can be set to True, if filter must support ordering (sorting)# `ordering` can't be applied to non-db fields'filter':'status','ordering':True, }, {# `search` must be set to True for filter to be used in searching# `search` must be applied only to text db-fields, which have ilike lookup'filter':'author__email','search':True, }, {# `source` must be set when filter name doesn't match ORM path'filter':'name','source':'author__name', }, {# `namespace` is useful for API consistency, when dealing with related models'namespace':'author','filters': ['id','name'],# will be converted to `author.id` and `author.name` },{# `distinct` needs to be setup for filters that require QS to work in DISTINCT mode# `openapi` configuration is automatically collected by OpenAPI autogenerator'filter':'published.at','source':'published_at','distinct':True,'openapi': {'required':True,'deprecated':True,'description':'Good description','hidden':False,# can be set to avoid collecting by autogenerator# type and format are collected automatically and shouldn't be setup, in general'type':'string','format':'date', }, }, {# `use_repr` flag is used to filter by choice representations'filter':'rating.blog','source':'blog_rating','use_repr':True, }, {# `hidden` flag is used to set default select behaviour for associated field'filter':'rating.blog_int','source':'blog_rating','use_repr':False,'ordering':True,'hidden':True, }, {# We can change default lookups for a certain filter'filter':'amazon_rating','lookups': {FilterLookups.GE,FilterLookups.LT}, }, {# Sometimes it's needed to filter by several sources at once (distinct is always True).# F.e. this could be helpful for searching.'filter':'d_id','sources': {'id','author__id'},'ordering':True, }, {# Some fields may have no DB representation or non-typical ORM filtering# `custom` option must be set to True for such fields'filter':'custom_filter','custom':True,'lookups': {FilterLookups.EQ,FilterLookups.IN,FilterLookups.I_LIKE},'ordering':True,'search':True,# Optional ORM field for query parameter value validation'field':IntegerField(),'custom_data': [1], }]fromdj_rql.drf.backendimportRQLFilterBackendfromdj_rql.drf.paginationsimportRQLContentRangeLimitOffsetPaginationclassDRFViewSet(mixins.ListModelMixin,GenericViewSet):queryset=MODEL.objects.all()serializer_class=ModelSerializerrql_filter_class=ModelFilterClasspagination_class=RQLContentRangeLimitOffsetPaginationfilter_backends= (RQLFilterBackend,)
- Values with whitespaces or special characters, like ',' need to have "" or ''
- Supported date format is ISO8601: 2019-02-12
- Supported datetime format is ISO8601: 2019-02-12T10:02:00 / 2019-02-12T10:02Z / 2019-02-12T10:02:00+03:00
- Support for Choices() fields fromDjango Model Utilities is added
- Library supportscaching with different strategies for queryset building, which can be very useful for collections, which use
select()
.
Queryset execution result (filtered data) is NOT cached (!), only queryset building is cached.
fromdj_rql.filter_clsimportRQLFilterClassfromcachetoolsimportLRUCacheclassMyFilterClass(RQLFilterClass):SELECT=TrueQUERIES_CACHE_BACKEND=LRUCacheQUERIES_CACHE_SIZE=100
There is a Django commandgenerate_rql_class
to decrease development and integration efforts for filtering.This command automatically generates a filter class for a given model with all relations and all optimizations (!) to the specified depth.
django-admin generate_rql_class --settings=tests.dj_rf.settings tests.dj_rf.models.Publisher --depth=1 --exclude=authors,fk2
This command for the modelPublisher
from tests package will produce the following output to stdout:
fromtests.dj_rf.modelsimportPublisherfromdj_rql.filter_clsimportRQLFilterClassfromdj_rql.qsimportNSRclassPublisherFilters(RQLFilterClass):MODEL=PublisherSELECT=TrueEXCLUDE_FILTERS= ['authors','fk2']FILTERS= [ {"filter":"id","ordering":True,"search":False }, {"filter":"name","ordering":True,"search":True }, {"namespace":"fk1","filters": [ {"filter":"id","ordering":True,"search":False } ],"qs":NSR('fk1') }]
- Pagination (limit, offset)
- Support for custom fields, inherited at any depth from basic model fields, like CharField().
- Backend
DjangoFiltersRQLFilterBackend
with automatic conversion ofDjango-Filters query to RQL query. - OpenAPI docs are autogenerated for filter classes.
- Use
dj_rql.utils.assert_filter_cls
to test your API view filters. If the mappings are correct and there is no custom filtering logic, then it's practically guaranteed, that filtering will work correctly. - Prefer using
custom=True
withRQLFilterClass.build_q_for_custom_filter
overriding over overridingRQLFilterClass.build_q_for_filter
. - Custom filters may support ordering (
ordering=True
) withbuild_name_for_custom_ordering
. - Django JSON fields can't be used as namespaces currently, but can be supported via
dynamic=True
, for example:
{'filter':'json_data.key','source':'json_data__key','dynamic':True,'field':CharField(null=True),},
- Python 3.8+
- Install poetry:
pip install poetry
- Install dependencies:
poetry install
- We use
isort
library to order and format our imports, andblack
- to format the code.We check it usingflake8-isort
andflake8-black
libraries (automatically onflake8
run).
For convenience you may runisort . && black .
to format the code. - Run flake8:
poetry run flake8
- Python 3.8+
- Install poetry:
pip install poetry
- Install dependencies:
poetry install
Check code style:poetry run flake8
Run tests:poetry run pytest
Tests reports are generated intests/reports
.
out.xml
- JUnit test resultscoverage.xml
- Coverage xml results
To generate HTML coverage reports use:--cov-report html:tests/reports/cov_html