diff --git a/src/documents/admin.py b/src/documents/admin.py index 055a6fd93..6ec3b736e 100755 --- a/src/documents/admin.py +++ b/src/documents/admin.py @@ -4,7 +4,8 @@ from django.utils.safestring import mark_safe from whoosh.writing import AsyncWriter from . import index -from .models import Correspondent, Document, DocumentType, Log, Tag +from .models import Correspondent, Document, DocumentType, Log, Tag, \ + SavedView, SavedViewFilterRule class CorrespondentAdmin(admin.ModelAdmin): @@ -131,8 +132,22 @@ class LogAdmin(admin.ModelAdmin): list_display_links = ("created", "message") +class RuleInline(admin.TabularInline): + model = SavedViewFilterRule + + +class SavedViewAdmin(admin.ModelAdmin): + + list_display = ("name", "user") + + inlines = [ + RuleInline + ] + + admin.site.register(Correspondent, CorrespondentAdmin) admin.site.register(Tag, TagAdmin) admin.site.register(DocumentType, DocumentTypeAdmin) admin.site.register(Document, DocumentAdmin) admin.site.register(Log, LogAdmin) +admin.site.register(SavedView, SavedViewAdmin) diff --git a/src/documents/migrations/1007_savedview_savedviewfilterrule.py b/src/documents/migrations/1007_savedview_savedviewfilterrule.py new file mode 100644 index 000000000..664def5f1 --- /dev/null +++ b/src/documents/migrations/1007_savedview_savedviewfilterrule.py @@ -0,0 +1,37 @@ +# Generated by Django 3.1.4 on 2020-12-12 14:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('documents', '1006_auto_20201208_2209'), + ] + + operations = [ + migrations.CreateModel( + name='SavedView', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128)), + ('show_on_dashboard', models.BooleanField()), + ('show_in_sidebar', models.BooleanField()), + ('sort_field', models.CharField(max_length=128)), + ('sort_reverse', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='SavedViewFilterRule', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rule_type', models.PositiveIntegerField(choices=[(0, 'Title contains'), (1, 'Content contains'), (2, 'ASN is'), (3, 'Correspondent is'), (4, 'Document type is'), (5, 'Is in inbox'), (6, 'Has tag'), (7, 'Has any tag'), (8, 'Created before'), (9, 'Created after'), (10, 'Created year is'), (11, 'Created month is'), (12, 'Created day is'), (13, 'Added before'), (14, 'Added after'), (15, 'Modified before'), (16, 'Modified after'), (17, 'Does not have tag')])), + ('value', models.CharField(max_length=128)), + ('saved_view', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='filter_rules', to='documents.savedview')), + ], + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index f0678a843..1b1f697bc 100755 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -9,6 +9,7 @@ import pathvalidate import dateutil.parser from django.conf import settings +from django.contrib.auth.models import User from django.db import models from django.utils import timezone from django.utils.text import slugify @@ -305,6 +306,47 @@ class Log(models.Model): return self.message +class SavedView(models.Model): + + user = models.ForeignKey(User, on_delete=models.CASCADE) + name = models.CharField(max_length=128) + + show_on_dashboard = models.BooleanField() + show_in_sidebar = models.BooleanField() + + sort_field = models.CharField(max_length=128) + sort_reverse = models.BooleanField(default=False) + + +class SavedViewFilterRule(models.Model): + RULE_TYPES = [ + (0, "Title contains"), + (1, "Content contains"), + (2, "ASN is"), + (3, "Correspondent is"), + (4, "Document type is"), + (5, "Is in inbox"), + (6, "Has tag"), + (7, "Has any tag"), + (8, "Created before"), + (9, "Created after"), + (10, "Created year is"), + (11, "Created month is"), + (12, "Created day is"), + (13, "Added before"), + (14, "Added after"), + (15, "Modified before"), + (16, "Modified after"), + (17, "Does not have tag"), + ] + + saved_view = models.ForeignKey(SavedView, on_delete=models.CASCADE, related_name="filter_rules") + + rule_type = models.PositiveIntegerField(choices=RULE_TYPES) + + value = models.CharField(max_length=128) + + # TODO: why is this in the models file? class FileInfo: diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index db0e610d1..43b5e5992 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -3,7 +3,8 @@ from django.utils.text import slugify from rest_framework import serializers from rest_framework.fields import SerializerMethodField -from .models import Correspondent, Tag, Document, Log, DocumentType +from .models import Correspondent, Tag, Document, Log, DocumentType, \ + SavedView, SavedViewFilterRule from .parsers import is_mime_type_supported @@ -140,6 +141,39 @@ class LogSerializer(serializers.ModelSerializer): ) +class SavedViewFilterRuleSerializer(serializers.ModelSerializer): + + class Meta: + model = SavedViewFilterRule + fields = ["rule_type", "value"] + + +class SavedViewSerializer(serializers.ModelSerializer): + + filter_rules = SavedViewFilterRuleSerializer(many=True) + + class Meta: + model = SavedView + depth = 1 + fields = ["id", "name", "show_on_dashboard", "show_in_sidebar", + "sort_field", "sort_reverse", "filter_rules"] + + def update(self, instance, validated_data): + rules_data = validated_data.pop('filter_rules') + super(SavedViewSerializer, self).update(instance, validated_data) + SavedViewFilterRule.objects.filter(saved_view=instance).delete() + for rule_data in rules_data: + SavedViewFilterRule.objects.create(saved_view=instance, **rule_data) + return instance + + def create(self, validated_data): + rules_data = validated_data.pop('filter_rules') + saved_view = SavedView.objects.create(**validated_data) + for rule_data in rules_data: + SavedViewFilterRule.objects.create(saved_view=saved_view, **rule_data) + return saved_view + + class PostDocumentSerializer(serializers.Serializer): document = serializers.FileField( diff --git a/src/documents/views.py b/src/documents/views.py index b42ae1f96..36d3445c4 100755 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -38,7 +38,7 @@ from .filters import ( DocumentTypeFilterSet, LogFilterSet ) -from .models import Correspondent, Document, Log, Tag, DocumentType +from .models import Correspondent, Document, Log, Tag, DocumentType, SavedView from .parsers import get_parser_class_for_mime_type from .serialisers import ( CorrespondentSerializer, @@ -46,7 +46,8 @@ from .serialisers import ( LogSerializer, TagSerializer, DocumentTypeSerializer, - PostDocumentSerializer + PostDocumentSerializer, + SavedViewSerializer ) @@ -240,6 +241,22 @@ class LogViewSet(ReadOnlyModelViewSet): ordering_fields = ("created",) +class SavedViewViewSet(ModelViewSet): + model = SavedView + + queryset = SavedView.objects.all() + serializer_class = SavedViewSerializer + pagination_class = StandardPagination + permission_classes = (IsAuthenticated,) + + def get_queryset(self): + user = self.request.user + return SavedView.objects.filter(user=user) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) + + class PostDocumentView(APIView): permission_classes = (IsAuthenticated,) diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 9b390b139..079971bb3 100755 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -17,7 +17,8 @@ from documents.views import ( IndexView, SearchAutoCompleteView, StatisticsView, - PostDocumentView + PostDocumentView, + SavedViewViewSet ) from paperless.views import FaviconView @@ -27,6 +28,7 @@ api_router.register(r"document_types", DocumentTypeViewSet) api_router.register(r"documents", DocumentViewSet) api_router.register(r"logs", LogViewSet) api_router.register(r"tags", TagViewSet) +api_router.register(r"saved_views", SavedViewViewSet) urlpatterns = [