mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Fixes #164
There appears to be quite the mess out there with regard to how DRF
handles filtering.  DRF has its own built-in stuff, but recommends
django_filter for the advanced stuff, which has its own overriding
module that explodes with this message when used as per the
documentation:
  AttributeError: 'NoneType' object has no attribute 'DjangoFilterBackend'
Then there's djangorestframework-filter, another package that claims to
do the same thing, that does everything just differently enough that
nothing worked while I had it enabled.
I ended up using django_filter, but doing so importing each element
explicitly, rather than just using the recommended (and broken, at least
in this project) method of:
    import django_filter.restframework as fitlers
Anyway, this should bring the dependencies up to date, and strips out a
lot of redundant code.
			
			
This commit is contained in:
		| @@ -1,10 +1,9 @@ | |||||||
| Django==1.10.3 | Django==1.10.4 | ||||||
| Pillow>=3.1.1 | Pillow>=3.1.1 | ||||||
| django-crispy-forms>=1.6.0 | django-crispy-forms>=1.6.0 | ||||||
| django-extensions>=1.6.1 | django-extensions>=1.6.1 | ||||||
| django-filter>=0.12.0,<1.0 | django-filter>=1.0 | ||||||
| djangorestframework>=3.4.4 | djangorestframework>=3.4.4 | ||||||
| djangorestframework-filters>=0.8.0 |  | ||||||
| filemagic>=1.6 | filemagic>=1.6 | ||||||
| langdetect>=1.0.5 | langdetect>=1.0.5 | ||||||
| pyocr>=0.3.1 | pyocr>=0.3.1 | ||||||
|   | |||||||
| @@ -1,152 +1,58 @@ | |||||||
| import django_filters | from django_filters.rest_framework import CharFilter, FilterSet | ||||||
|  |  | ||||||
| from rest_framework import filters | from .models import Correspondent, Document, Tag | ||||||
|  |  | ||||||
| from .models import Document, Correspondent, Tag |  | ||||||
|  |  | ||||||
| # |  | ||||||
| # I hate how copy/pastey this file is.  Recommendations are welcome. |  | ||||||
| # |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Filters | class CorrespondentFilterSet(FilterSet): | ||||||
|  |  | ||||||
|  |  | ||||||
| class RelatedFilter(django_filters.MethodFilter): |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |  | ||||||
|         self.key = kwargs.pop("key") |  | ||||||
|         self.lookup_type = kwargs.get("lookup_type") |  | ||||||
|         django_filters.MethodFilter.__init__(self, *args, **kwargs) |  | ||||||
|  |  | ||||||
|     def filter(self, qs, value): |  | ||||||
|         if not value: |  | ||||||
|             return qs |  | ||||||
|         return qs.filter(**{"tags__{}".format(self.key): value}) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # FilterSets |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SluggableFilterSet(filters.FilterSet): |  | ||||||
|  |  | ||||||
|     name__startswith = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="startswith", |  | ||||||
|         label="Name starts with (case sensitive)" |  | ||||||
|     ) |  | ||||||
|     name__istartswith = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="istartswith", |  | ||||||
|         label="Name starts with (case insensitive)" |  | ||||||
|     ) |  | ||||||
|     name__endswith = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="endswith", |  | ||||||
|         label="Name ends with (case sensitive)" |  | ||||||
|     ) |  | ||||||
|     name__iendswith = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="endswith", |  | ||||||
|         label="Name ends with (case insensitive)" |  | ||||||
|     ) |  | ||||||
|     name__contains = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="contains", |  | ||||||
|         label="Name contains (case sensitive)" |  | ||||||
|     ) |  | ||||||
|     name__icontains = django_filters.CharFilter( |  | ||||||
|         name="name", lookup_type="icontains", |  | ||||||
|         label="Name contains (case insensitive)" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     slug__istartswith = django_filters.CharFilter( |  | ||||||
|         name="slug", lookup_type="istartswith", |  | ||||||
|         label="Slug starts with (case insensitive)" |  | ||||||
|     ) |  | ||||||
|     slug__iendswith = django_filters.CharFilter( |  | ||||||
|         name="slug", lookup_type="endswith", |  | ||||||
|         label="Slug ends with (case insensitive)" |  | ||||||
|     ) |  | ||||||
|     slug__icontains = django_filters.CharFilter( |  | ||||||
|         name="slug", lookup_type="icontains", |  | ||||||
|         label="Slug contains (case insensitive)" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CorrespondentFilterSet(SluggableFilterSet): |  | ||||||
|  |  | ||||||
|     class Meta(object): |     class Meta(object): | ||||||
|         model = Correspondent |         model = Correspondent | ||||||
|         fields = ["name"] |         fields = { | ||||||
|  |             'name': [ | ||||||
|  |                 "startswith", "endswith", "contains", | ||||||
|  |                 "istartswith", "iendswith", "icontains" | ||||||
|  |             ], | ||||||
|  |             "slug": ["istartswith", "iendswith", "icontains"] | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
| class TagFilterSet(SluggableFilterSet): | class TagFilterSet(FilterSet): | ||||||
|  |  | ||||||
|     class Meta(object): |     class Meta(object): | ||||||
|         model = Tag |         model = Tag | ||||||
|         fields = ["name", "slug"] |         fields = { | ||||||
|  |             'name': [ | ||||||
|  |                 "startswith", "endswith", "contains", | ||||||
|  |                 "istartswith", "iendswith", "icontains" | ||||||
|  |             ], | ||||||
|  |             "slug": ["istartswith", "iendswith", "icontains"] | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
| class DocumentFilterSet(filters.FilterSet): | class DocumentFilterSet(FilterSet): | ||||||
|  |  | ||||||
|     title__startswith = django_filters.CharFilter( |     CHAR_KWARGS = { | ||||||
|         name="title", lookup_type="startswith", |         "lookup_expr": ( | ||||||
|         label="Title starts with (case sensitive)" |             "startswith", | ||||||
|     ) |             "endswith", | ||||||
|     title__istartswith = django_filters.CharFilter( |             "contains", | ||||||
|         name="title", lookup_type="istartswith", |             "istartswith", | ||||||
|         label="Title starts with (case insensitive)" |             "iendswith", | ||||||
|     ) |             "icontains" | ||||||
|     title__endswith = django_filters.CharFilter( |         ) | ||||||
|         name="title", lookup_type="endswith", |     } | ||||||
|         label="Title ends with (case sensitive)" |  | ||||||
|     ) |  | ||||||
|     title__iendswith = django_filters.CharFilter( |  | ||||||
|         name="title", lookup_type="endswith", |  | ||||||
|         label="Title ends with (case insensitive)" |  | ||||||
|     ) |  | ||||||
|     title__contains = django_filters.CharFilter( |  | ||||||
|         name="title", lookup_type="contains", |  | ||||||
|         label="Title contains (case sensitive)" |  | ||||||
|     ) |  | ||||||
|     title__icontains = django_filters.CharFilter( |  | ||||||
|         name="title", lookup_type="icontains", |  | ||||||
|         label="Title contains (case insensitive)" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     content__contains = django_filters.CharFilter( |     correspondent__name = CharFilter(name="correspondent__name", **CHAR_KWARGS) | ||||||
|         name="content", lookup_type="contains") |     correspondent__slug = CharFilter(name="correspondent__slug", **CHAR_KWARGS) | ||||||
|     content__icontains = django_filters.CharFilter( |     tags__name = CharFilter(name="tags__name", **CHAR_KWARGS) | ||||||
|         name="content", lookup_type="icontains") |     tags__slug = CharFilter(name="tags__slug", **CHAR_KWARGS) | ||||||
|  |  | ||||||
|     tags__name = RelatedFilter(key="name") |  | ||||||
|     tags__name__startswith = RelatedFilter(key="name__startswith") |  | ||||||
|     tags__name__istartswith = RelatedFilter(key="name__istartswith") |  | ||||||
|     tags__name__endswith = RelatedFilter(key="name__endswith") |  | ||||||
|     tags__name__iendswith = RelatedFilter(key="name__iendswith") |  | ||||||
|     tags__name__contains = RelatedFilter(key="name__contains") |  | ||||||
|     tags__name__icontains = RelatedFilter(key="name__icontains") |  | ||||||
|  |  | ||||||
|     tags__slug = RelatedFilter(key="slug") |  | ||||||
|     tags__slug__startswith = RelatedFilter(key="slug__startswith") |  | ||||||
|     tags__slug__istartswith = RelatedFilter(key="slug__istartswith") |  | ||||||
|     tags__slug__endswith = RelatedFilter(key="slug__endswith") |  | ||||||
|     tags__slug__iendswith = RelatedFilter(key="slug__iendswith") |  | ||||||
|     tags__slug__contains = RelatedFilter(key="slug__contains") |  | ||||||
|     tags__slug__icontains = RelatedFilter(key="slug__icontains") |  | ||||||
|  |  | ||||||
|     correspondent__name = RelatedFilter(key="name") |  | ||||||
|     correspondent__name__startswith = RelatedFilter(key="name__startswith") |  | ||||||
|     correspondent__name__istartswith = RelatedFilter(key="name__istartswith") |  | ||||||
|     correspondent__name__endswith = RelatedFilter(key="name__endswith") |  | ||||||
|     correspondent__name__iendswith = RelatedFilter(key="name__iendswith") |  | ||||||
|     correspondent__name__contains = RelatedFilter(key="name__contains") |  | ||||||
|     correspondent__name__icontains = RelatedFilter(key="name__icontains") |  | ||||||
|  |  | ||||||
|     correspondent__slug = RelatedFilter(key="slug") |  | ||||||
|     correspondent__slug__startswith = RelatedFilter(key="slug__startswith") |  | ||||||
|     correspondent__slug__istartswith = RelatedFilter(key="slug__istartswith") |  | ||||||
|     correspondent__slug__endswith = RelatedFilter(key="slug__endswith") |  | ||||||
|     correspondent__slug__iendswith = RelatedFilter(key="slug__iendswith") |  | ||||||
|     correspondent__slug__contains = RelatedFilter(key="slug__contains") |  | ||||||
|     correspondent__slug__icontains = RelatedFilter(key="slug__icontains") |  | ||||||
|  |  | ||||||
|     class Meta(object): |     class Meta(object): | ||||||
|         model = Document |         model = Document | ||||||
|         fields = ["title"] |         fields = { | ||||||
|  |             "title": [ | ||||||
|  |                 "startswith", "endswith", "contains", | ||||||
|  |                 "istartswith", "iendswith", "icontains" | ||||||
|  |             ], | ||||||
|  |             "content": ["contains", "icontains"], | ||||||
|  |         } | ||||||
|   | |||||||
| @@ -1,23 +1,33 @@ | |||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.http import HttpResponse | from django.http import HttpResponse | ||||||
| from django.views.decorators.csrf import csrf_exempt | from django.views.decorators.csrf import csrf_exempt | ||||||
| from django.views.generic import FormView, DetailView, TemplateView | from django.views.generic import DetailView, FormView, TemplateView | ||||||
|  | from django_filters.rest_framework import DjangoFilterBackend | ||||||
| from rest_framework import filters | from rest_framework.filters import SearchFilter, OrderingFilter | ||||||
|  | from paperless.db import GnuPG | ||||||
| from rest_framework.mixins import ( | from rest_framework.mixins import ( | ||||||
|     RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin) |     DestroyModelMixin, | ||||||
|  |     ListModelMixin, | ||||||
|  |     RetrieveModelMixin, | ||||||
|  |     UpdateModelMixin | ||||||
|  | ) | ||||||
| from rest_framework.pagination import PageNumberPagination | from rest_framework.pagination import PageNumberPagination | ||||||
| from rest_framework.permissions import IsAuthenticated | from rest_framework.permissions import IsAuthenticated | ||||||
| from rest_framework.viewsets import ( | from rest_framework.viewsets import ( | ||||||
|     ModelViewSet, ReadOnlyModelViewSet, GenericViewSet) |     GenericViewSet, | ||||||
|  |     ModelViewSet, | ||||||
|  |     ReadOnlyModelViewSet | ||||||
|  | ) | ||||||
|  |  | ||||||
| from paperless.db import GnuPG | from .filters import CorrespondentFilterSet, DocumentFilterSet, TagFilterSet | ||||||
|  |  | ||||||
| from .filters import DocumentFilterSet, CorrespondentFilterSet, TagFilterSet |  | ||||||
| from .forms import UploadForm | from .forms import UploadForm | ||||||
| from .models import Correspondent, Tag, Document, Log | from .models import Correspondent, Document, Log, Tag | ||||||
| from .serialisers import ( | from .serialisers import ( | ||||||
|     CorrespondentSerializer, TagSerializer, DocumentSerializer, LogSerializer) |     CorrespondentSerializer, | ||||||
|  |     DocumentSerializer, | ||||||
|  |     LogSerializer, | ||||||
|  |     TagSerializer | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class IndexView(TemplateView): | class IndexView(TemplateView): | ||||||
| @@ -94,7 +104,7 @@ class CorrespondentViewSet(ModelViewSet): | |||||||
|     serializer_class = CorrespondentSerializer |     serializer_class = CorrespondentSerializer | ||||||
|     pagination_class = StandardPagination |     pagination_class = StandardPagination | ||||||
|     permission_classes = (IsAuthenticated,) |     permission_classes = (IsAuthenticated,) | ||||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) |     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||||
|     filter_class = CorrespondentFilterSet |     filter_class = CorrespondentFilterSet | ||||||
|     ordering_fields = ("name", "slug") |     ordering_fields = ("name", "slug") | ||||||
|  |  | ||||||
| @@ -105,7 +115,7 @@ class TagViewSet(ModelViewSet): | |||||||
|     serializer_class = TagSerializer |     serializer_class = TagSerializer | ||||||
|     pagination_class = StandardPagination |     pagination_class = StandardPagination | ||||||
|     permission_classes = (IsAuthenticated,) |     permission_classes = (IsAuthenticated,) | ||||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) |     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||||
|     filter_class = TagFilterSet |     filter_class = TagFilterSet | ||||||
|     ordering_fields = ("name", "slug") |     ordering_fields = ("name", "slug") | ||||||
|  |  | ||||||
| @@ -120,11 +130,7 @@ class DocumentViewSet(RetrieveModelMixin, | |||||||
|     serializer_class = DocumentSerializer |     serializer_class = DocumentSerializer | ||||||
|     pagination_class = StandardPagination |     pagination_class = StandardPagination | ||||||
|     permission_classes = (IsAuthenticated,) |     permission_classes = (IsAuthenticated,) | ||||||
|     filter_backends = ( |     filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) | ||||||
|         filters.DjangoFilterBackend, |  | ||||||
|         filters.SearchFilter, |  | ||||||
|         filters.OrderingFilter |  | ||||||
|     ) |  | ||||||
|     filter_class = DocumentFilterSet |     filter_class = DocumentFilterSet | ||||||
|     search_fields = ("title", "correspondent__name", "content") |     search_fields = ("title", "correspondent__name", "content") | ||||||
|     ordering_fields = ( |     ordering_fields = ( | ||||||
| @@ -137,5 +143,5 @@ class LogViewSet(ReadOnlyModelViewSet): | |||||||
|     serializer_class = LogSerializer |     serializer_class = LogSerializer | ||||||
|     pagination_class = StandardPagination |     pagination_class = StandardPagination | ||||||
|     permission_classes = (IsAuthenticated,) |     permission_classes = (IsAuthenticated,) | ||||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) |     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||||
|     ordering_fields = ("time",) |     ordering_fields = ("time",) | ||||||
|   | |||||||
| @@ -36,7 +36,6 @@ if os.path.exists("/etc/paperless.conf"): | |||||||
|     load_dotenv("/etc/paperless.conf") |     load_dotenv("/etc/paperless.conf") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application definition | # Application definition | ||||||
|  |  | ||||||
| INSTALLED_APPS = [ | INSTALLED_APPS = [ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Daniel Quinn
					Daniel Quinn