drf-query-filter 0.1.8.5
A django app to apply filters on drf queryset using the query params with custom fields.
License: MIT License
Keywords: drf-query-filter filters queryparameters django restframework

# drf-query-filter

**drf-query-filter** is used to design fast and complex queries with the 'query' found in the request url.

## Table of contents

* [Installation](#installation)
* [Usage](#usage)
    * [QuickStart](#quickstart)
    * [Fields](#fields)
* [How does it work?](#how-does-it-work)

## Installation

drf-query-filter can be installed using pip

```shell
pip3 install drf-query-filter
```

## Usage

### QuickStart

In `settings.py`:

```python
INSTALLED_APPS = [
    ...,
    'drf_query_filter',
    ...
]
```

In views:

```python
...
from rest_framework import viewsets
from drf_query_filter import fields, filters

...


class ExampleViewSet(viewsets.GenericViewSet):
    ...
    filter_backends = [filters.QueryParamFilter, ]
    
    query_param = [
        fields.Field('id', 'id') & fields.Field('user_id', 'user__id'),
        fields.RangeDateTimeField('date_created', 'element__date_created'),
    ]

```

### Fields

the view needs to define the fields to be used in the query set. there are two ways of doing this.  
by attribute:

```python
query_params = [
    fields.Field('id') & fields.Field('username', 'username__icontains'),
    fields.ConcatField('full_name', ['first_name', V(' '), 'last_name'])
]
```

or by a callable:

```python
def get_query_params(self):
    return [
        fields.Field('id') & fields.Field('username', 'username__icontains'),
        fields.ConcatField('full_name', ['first_name', V(' '), 'last_name'])
    ]
```

The first value of the Field constructor is the name of the query that it will look for in the request. meaning that in
the case of using `fields.Field('username')` it will try to search for the key *'username'* in the query:

> http://localhost/path/to/somewhere/? **username** =value

With the param **target_fields** of the Field one can tell which are the target fields of the model.

Not assigning the **target_field** will assume that the name of the field is the same for the name of the target field.

```python
fields.Field('username')  # it will user `username` as the target field.
``` 

To tell what **target_field** it is use the param **target_fields**,
using only a str will target only one field in the model.

```python
fields.Field('search', 'username')
```

Using a list or a tuple will target multiple fields of the model.

```python
fields.Field('search', ['username', 'first_name', 'last_name'])
```

Meaning that the result in the field `search` *(in this case)* will be assigned to all the target fields.

### How does it work?

With the following fields arraigned like this:

```python
query_params = [
    fields.Field('id') & fields.Field('username', 'user__username__icontains'),
    fields.RangeDateTimeField('date_created', equal=True) | fields.BooleanField('vip', 'vip_status'),
    fields.ConcatField('full_name', ['user__first_name', V(' '), 'user__last_name'], lookup='icontains'),
]
```

Is equivalent to the following lines of code: *(if all values are found in the request)*:

```python
queryset = queryset.filter(Q(id='value') & Q(user__username__icontains='value'))
queryset = queryset.filter(Q(date_created__gte='value', date_created__lte='value') | Q(vip_status='value'))

queryset = queryset\
    .annotate(first_name_last_name=Concat('user__first_name', V(' '), 'user__last_name'))\
    .filter(Q(first_name_last_name__icontains='value'))
```

If some values are not found in the request, they are ignored, for example:

If the request doesn't contain `full_name` it will ignore the last field (the **annotate** and **filter**), 
And instead it will only do the first two.

**Request:** `/?id=9&username=value&date_created=2021-1-1,2021-12-31&vip=true`

```python
queryset = queryset.filter(Q(id=9) & Q(user__username__icontains='value'))
queryset = queryset.filter(Q(date_created__gte=datetime(year=2021, month=1, day=1),
                             date_created__lte=datetime(year=2021, month=12, day=1)) |
                           Q(vip_status=True))
```

Another example where we only ask for the *id* and *full_name*:

**Request:** `/?id=10&full_name=Something+Something`

```python
queryset = queryset.filter(Q(id=10))

queryset = queryset\
    .annotate(first_name_last_name=Concat('user__first_name', V(' '), 'user__last_name'))\
    .filter(Q(first_name_last_name__icontains='Something Something'))
```