======================
Django Dynamic Filters
======================

|Package| |License| |Downloads| |Python| |Django|

.. |Package| image:: https://badge.fury.io/py/django-dynamic-filters.svg
   :target: https://pypi.python.org/pypi/django-dynamic-filters
.. |License| image:: https://img.shields.io/pypi/l/django-dynamic-filters.svg
   :target: https://raw.githubusercontent.com/caramdache/django-dynamic-filters/main/LICENSE
.. |Downloads| image:: https://img.shields.io/pypi/dm/django-dynamic-filters.svg
   :target: https://pypi.python.org/pypi/django-dynamic-filters
.. |Python| image:: https://img.shields.io/pypi/pyversions/django-dynamic-filters.svg
   :target: https://pypi.python.org/pypi/django-dynamic-filters
.. |Django| image:: https://img.shields.io/badge/django%20versions-4.0-blue.svg
   :target: https://www.djangoproject.com

A django ModelAdmin Filter which adds advanced filtering abilities to the admin.

**creating filters**

.. figure:: https://github.com/caramdache/django-dynamic-filters/raw/main/images/filter_edit.png
   :alt: Creating a filter
   :width: 768 px

**using filters**

.. figure:: https://github.com/caramdache/django-dynamic-filters/raw/main/images/filter_user.png
   :alt: Apply a filter
   :width: 768 px
   
Requirements
------------

* Django >= 4.0 on Python 3.9+/PyPy3
* django-admin-sortable2_ and furl_

.. _django-admin-sortable2 : https://github.com/jrief/django-admin-sortable2
.. _furl : https://github.com/gruns/furl

Installation & Set up
---------------------

1. Run ``pip install django-dynamic-filters`` to install dynfilters.

2. Add "dynfilters" to your INSTALLED_APPS setting like this::

    INSTALLED_APPS = [
        ...
        'adminsortable2',
        'dynfilters',
    ]

3. Add "dynfilters" URL to your urls.py file::

    urlpatterns = [
        ...
        path('dynfilters/', include('dynfilters.urls')),
    ]

4. Run ``python manage.py migrate`` to create the dynfilters models.

5. Run ``python manage.py collectstatic`` to install the dynfilters media.

Integration Example
-------------------

**models.py**

.. code-block:: python

    class Address(models.Model):
        town = models.CharField(max_length=32)

    class Person(models.Model):
        first_name = models.CharField(max_length=32)
        last_name = models.CharField(max_length=32)
        birth_date = models.DateField()
        address = models.ForeignKey(Address, on_delete=models.CASCADE)

**admin.py**

.. code-block:: python

    from dynfilters.filters import DynamicFilter

    @admin.register(Person)
    class PersonAdmin(admin.ModelAdmin):
        ...
        list_filter = (DynamicFilter,)

        dynfilters_fields = [
            '-',
            'first_name',
            'last_name',
            ('first_name|last_name', 'Name'),   # Will generate: Q(first_name=<value>) | Q(last_name=<value>)
            ('birth_date', 'Date of birth'),    # Requires the value to be: DD/MM/YYYY
            '-',
            ('address__town', 'City'),
        ]

        dynfilters_select_related = ['address'] # Optional
        dynfilters_prefetch_related = []        # Optional
        
Operators & Lookups
-------------------

The following operators and lookups are supported:

**operators**

.. code-block:: python

    OP_CHOICES = [
        ('-', '-'),
        ('!', 'NOT'),
        ('&', 'AND'),
        ('|', 'OR'),
        ('(', '('),
        (')', ')'),
    ]

**lookups**

.. code-block:: python

    LOOKUP_CHOICES = [
        ('-', '---------'),
        ('=', 'Equals'),
        ('icontains', 'Contains'),
        ('istartswith', 'Starts with'),
        ('iendswith', 'Ends with'),
        ('in', 'One of'),          # Requires the value to be: aaa,bbb,ccc
        ('-', '---------'),
        ('range', 'Date Range'),   # Requires the value to be: DD/MM/YYYY,DD/MM/YYYY
        ('year', 'Date Year'), 
        ('month', 'Date Month'),
        ('day', 'Date Day'),
        ('-', '---------'),
        ('isnull', 'Is NULL'),
        ('isnotnull', 'Is not NULL'),
        ('istrue', 'Is TRUE'),
        ('isfalse', 'Is FALSE'),
        ('-', '---------'),
        ('lt', 'Less Than'),
        ('gt', 'Greater Than'),
        ('lte', 'Less Than or Equal To'),
        ('gte', 'Greater Than or Equal To'),
    ]

Sharing
-------

There are two ways dynamic filters can be shared:

1. By marking a filter `global`. The filter will be available to all admin users.

2. By pressing the `share` icon. The filter can then be shared by email. When the recipients clicks on the received link, a copy of the filter will be created. The edits made to the copy will not affect the original filter.

Alternatives
------------

* Dynfilters was inspired by the look and feel of `django-advanced-filters`_, but is based purely on admin forms and inlines (no JSON).
* Another interesting package is `django-filter`_.
* And yet another one is `django-admin-search-builder`_.

.. _django-advanced-filters : https://github.com/modlinltd/django-advanced-filters
.. _django-filter : https://github.com/carltongibson/django-filter
.. _django-admin-search-builder : https://github.com/peppelinux/django-admin-search-builder
