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 | ||||
| django-crispy-forms>=1.6.0 | ||||
| django-extensions>=1.6.1 | ||||
| django-filter>=0.12.0,<1.0 | ||||
| django-filter>=1.0 | ||||
| djangorestframework>=3.4.4 | ||||
| djangorestframework-filters>=0.8.0 | ||||
| filemagic>=1.6 | ||||
| langdetect>=1.0.5 | ||||
| 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 Document, Correspondent, Tag | ||||
|  | ||||
| # | ||||
| # I hate how copy/pastey this file is.  Recommendations are welcome. | ||||
| # | ||||
| from .models import Correspondent, Document, Tag | ||||
|  | ||||
|  | ||||
| # Filters | ||||
|  | ||||
|  | ||||
| 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 CorrespondentFilterSet(FilterSet): | ||||
|  | ||||
|     class Meta(object): | ||||
|         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): | ||||
|         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( | ||||
|         name="title", lookup_type="startswith", | ||||
|         label="Title starts with (case sensitive)" | ||||
|     ) | ||||
|     title__istartswith = django_filters.CharFilter( | ||||
|         name="title", lookup_type="istartswith", | ||||
|         label="Title starts with (case insensitive)" | ||||
|     ) | ||||
|     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)" | ||||
|     CHAR_KWARGS = { | ||||
|         "lookup_expr": ( | ||||
|             "startswith", | ||||
|             "endswith", | ||||
|             "contains", | ||||
|             "istartswith", | ||||
|             "iendswith", | ||||
|             "icontains" | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     content__contains = django_filters.CharFilter( | ||||
|         name="content", lookup_type="contains") | ||||
|     content__icontains = django_filters.CharFilter( | ||||
|         name="content", lookup_type="icontains") | ||||
|  | ||||
|     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") | ||||
|     correspondent__name = CharFilter(name="correspondent__name", **CHAR_KWARGS) | ||||
|     correspondent__slug = CharFilter(name="correspondent__slug", **CHAR_KWARGS) | ||||
|     tags__name = CharFilter(name="tags__name", **CHAR_KWARGS) | ||||
|     tags__slug = CharFilter(name="tags__slug", **CHAR_KWARGS) | ||||
|  | ||||
|     class Meta(object): | ||||
|         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.http import HttpResponse | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
| from django.views.generic import FormView, DetailView, TemplateView | ||||
|  | ||||
| from rest_framework import filters | ||||
| from django.views.generic import DetailView, FormView, TemplateView | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from rest_framework.filters import SearchFilter, OrderingFilter | ||||
| from paperless.db import GnuPG | ||||
| from rest_framework.mixins import ( | ||||
|     RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin) | ||||
|     DestroyModelMixin, | ||||
|     ListModelMixin, | ||||
|     RetrieveModelMixin, | ||||
|     UpdateModelMixin | ||||
| ) | ||||
| from rest_framework.pagination import PageNumberPagination | ||||
| from rest_framework.permissions import IsAuthenticated | ||||
| from rest_framework.viewsets import ( | ||||
|     ModelViewSet, ReadOnlyModelViewSet, GenericViewSet) | ||||
|     GenericViewSet, | ||||
|     ModelViewSet, | ||||
|     ReadOnlyModelViewSet | ||||
| ) | ||||
|  | ||||
| from paperless.db import GnuPG | ||||
|  | ||||
| from .filters import DocumentFilterSet, CorrespondentFilterSet, TagFilterSet | ||||
| from .filters import CorrespondentFilterSet, DocumentFilterSet, TagFilterSet | ||||
| from .forms import UploadForm | ||||
| from .models import Correspondent, Tag, Document, Log | ||||
| from .models import Correspondent, Document, Log, Tag | ||||
| from .serialisers import ( | ||||
|     CorrespondentSerializer, TagSerializer, DocumentSerializer, LogSerializer) | ||||
|     CorrespondentSerializer, | ||||
|     DocumentSerializer, | ||||
|     LogSerializer, | ||||
|     TagSerializer | ||||
| ) | ||||
|  | ||||
|  | ||||
| class IndexView(TemplateView): | ||||
| @@ -94,7 +104,7 @@ class CorrespondentViewSet(ModelViewSet): | ||||
|     serializer_class = CorrespondentSerializer | ||||
|     pagination_class = StandardPagination | ||||
|     permission_classes = (IsAuthenticated,) | ||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) | ||||
|     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||
|     filter_class = CorrespondentFilterSet | ||||
|     ordering_fields = ("name", "slug") | ||||
|  | ||||
| @@ -105,7 +115,7 @@ class TagViewSet(ModelViewSet): | ||||
|     serializer_class = TagSerializer | ||||
|     pagination_class = StandardPagination | ||||
|     permission_classes = (IsAuthenticated,) | ||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) | ||||
|     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||
|     filter_class = TagFilterSet | ||||
|     ordering_fields = ("name", "slug") | ||||
|  | ||||
| @@ -120,11 +130,7 @@ class DocumentViewSet(RetrieveModelMixin, | ||||
|     serializer_class = DocumentSerializer | ||||
|     pagination_class = StandardPagination | ||||
|     permission_classes = (IsAuthenticated,) | ||||
|     filter_backends = ( | ||||
|         filters.DjangoFilterBackend, | ||||
|         filters.SearchFilter, | ||||
|         filters.OrderingFilter | ||||
|     ) | ||||
|     filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) | ||||
|     filter_class = DocumentFilterSet | ||||
|     search_fields = ("title", "correspondent__name", "content") | ||||
|     ordering_fields = ( | ||||
| @@ -137,5 +143,5 @@ class LogViewSet(ReadOnlyModelViewSet): | ||||
|     serializer_class = LogSerializer | ||||
|     pagination_class = StandardPagination | ||||
|     permission_classes = (IsAuthenticated,) | ||||
|     filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter) | ||||
|     filter_backends = (DjangoFilterBackend, OrderingFilter) | ||||
|     ordering_fields = ("time",) | ||||
|   | ||||
| @@ -36,7 +36,6 @@ if os.path.exists("/etc/paperless.conf"): | ||||
|     load_dotenv("/etc/paperless.conf") | ||||
|  | ||||
|  | ||||
|  | ||||
| # Application definition | ||||
|  | ||||
| INSTALLED_APPS = [ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Daniel Quinn
					Daniel Quinn