diff --git a/src/documents/migrations/1040_customfield_customfieldboolean_customfielddate_and_more.py b/src/documents/migrations/1040_customfield_customfieldboolean_customfielddate_and_more.py index 406e74733..d3d7ebea6 100644 --- a/src/documents/migrations/1040_customfield_customfieldboolean_customfielddate_and_more.py +++ b/src/documents/migrations/1040_customfield_customfieldboolean_customfielddate_and_more.py @@ -1,13 +1,15 @@ -# Generated by Django 4.2.5 on 2023-10-22 23:47 +# Generated by Django 4.2.5 on 2023-10-25 13:49 import django.db.models.deletion import django.utils.timezone +from django.conf import settings from django.db import migrations from django.db import models class Migration(migrations.Migration): dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("documents", "1039_consumptiontemplate"), ] @@ -138,6 +140,16 @@ class Migration(migrations.Migration): to="documents.customfield", ), ), + ( + "owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), ], options={ "verbose_name": "custom field instance", diff --git a/src/documents/models.py b/src/documents/models.py index 7e298d7d5..d5423eb41 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -920,7 +920,7 @@ class CustomField(models.Model): return f"{self.name} : {self.data_type}" -class CustomFieldInstance(models.Model): +class CustomFieldInstance(ModelWithOwner): """ A single instance of a field, attached to a CustomField for the name and type and attached to a single Document to be metadata for it diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 6f20a4cf0..de93eb0a0 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -21,6 +21,8 @@ from documents import bulk_edit from documents.data_models import DocumentSource from documents.models import ConsumptionTemplate from documents.models import Correspondent +from documents.models import CustomField +from documents.models import CustomFieldInstance from documents.models import Document from documents.models import DocumentType from documents.models import MatchingModel @@ -1090,3 +1092,13 @@ class ConsumptionTemplateSerializer(serializers.ModelSerializer): ) return attrs + + +class CustomFieldSerializer(serializers.ModelSerializer): + class Meta: + model = CustomField + + +class CustomFieldInstanceSerializer(serializers.ModelSerializer): + class Meta: + model = CustomFieldInstance diff --git a/src/documents/views.py b/src/documents/views.py index 4538606eb..1d9d21d3f 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -1,8 +1,10 @@ import itertools +import json import logging import os import re import tempfile +import urllib import zipfile from datetime import datetime from pathlib import Path @@ -35,6 +37,7 @@ from django.views.decorators.cache import cache_control from django.views.generic import TemplateView from django_filters.rest_framework import DjangoFilterBackend from langdetect import detect +from packaging import version as packaging_version from rest_framework import parsers from rest_framework.decorators import action from rest_framework.exceptions import NotFound @@ -75,8 +78,7 @@ from documents.matching import match_storage_paths from documents.matching import match_tags from documents.models import ConsumptionTemplate from documents.models import Correspondent - -# from documents.models import CustomMetadata +from documents.models import CustomField from documents.models import Document from documents.models import DocumentType from documents.models import Note @@ -98,6 +100,7 @@ from documents.serialisers import BulkEditObjectPermissionsSerializer from documents.serialisers import BulkEditSerializer from documents.serialisers import ConsumptionTemplateSerializer from documents.serialisers import CorrespondentSerializer +from documents.serialisers import CustomFieldSerializer from documents.serialisers import DocumentListSerializer from documents.serialisers import DocumentSerializer from documents.serialisers import DocumentTypeSerializer @@ -110,6 +113,7 @@ from documents.serialisers import TagSerializerVersion1 from documents.serialisers import TasksViewSerializer from documents.serialisers import UiSettingsViewSerializer from documents.tasks import consume_file +from paperless import version from paperless.db import GnuPG from paperless.views import StandardPagination @@ -1241,6 +1245,47 @@ class UiSettingsView(GenericAPIView): ) +class RemoteVersionView(GenericAPIView): + def get(self, request, format=None): + remote_version = "0.0.0" + is_greater_than_current = False + current_version = packaging_version.parse(version.__full_version_str__) + try: + req = urllib.request.Request( + "https://api.github.com/repos/paperlessngx/" + "paperlessngx/releases/latest", + ) + # Ensure a JSON response + req.add_header("Accept", "application/json") + + with urllib.request.urlopen(req) as response: + remote = response.read().decode("utf8") + try: + remote_json = json.loads(remote) + remote_version = remote_json["tag_name"] + # Basically PEP 616 but that only went in 3.9 + if remote_version.startswith("ngx"): + remote_version = remote_version[len("ngx") :] + except ValueError: + logger.debug("An error occurred parsing remote version json") + except urllib.error.URLError: + logger.debug("An error occurred checking for available updates") + + is_greater_than_current = ( + packaging_version.parse( + remote_version, + ) + > current_version + ) + + return Response( + { + "version": remote_version, + "update_available": is_greater_than_current, + }, + ) + + class TasksViewSet(ReadOnlyModelViewSet): permission_classes = (IsAuthenticated,) serializer_class = TasksViewSerializer @@ -1397,3 +1442,14 @@ class ConsumptionTemplateViewSet(ModelViewSet): model = ConsumptionTemplate queryset = ConsumptionTemplate.objects.all().order_by("order") + + +class CustomFieldViewSet(ModelViewSet): + permission_classes = (IsAuthenticated, PaperlessObjectPermissions) + + serializer_class = CustomFieldSerializer + pagination_class = StandardPagination + + model = CustomField + + queryset = CustomField.objects.all().order_by("-created") diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 415efc4de..2f0c56267 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -16,6 +16,7 @@ from documents.views import BulkEditObjectPermissionsView from documents.views import BulkEditView from documents.views import ConsumptionTemplateViewSet from documents.views import CorrespondentViewSet +from documents.views import CustomFieldViewSet from documents.views import DocumentTypeViewSet from documents.views import IndexView from documents.views import LogViewSet @@ -55,6 +56,7 @@ api_router.register(r"mail_accounts", MailAccountViewSet) api_router.register(r"mail_rules", MailRuleViewSet) api_router.register(r"share_links", ShareLinkViewSet) api_router.register(r"consumption_templates", ConsumptionTemplateViewSet) +api_router.register(r"custom_fields", CustomFieldViewSet) urlpatterns = [