paperless-ngx/src/documents/views.py

758 lines
25 KiB
Python
Raw Normal View History

2022-04-01 15:31:10 -07:00
import json
import logging
2020-11-25 14:48:36 +01:00
import os
import tempfile
2022-04-01 01:48:05 -07:00
import urllib
2021-01-26 00:51:20 +01:00
import uuid
import zipfile
from datetime import datetime
from time import mktime
from unicodedata import normalize
from urllib.parse import quote
2020-11-25 14:48:36 +01:00
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Case
from django.db.models import Count
from django.db.models import IntegerField
from django.db.models import Max
from django.db.models import When
2020-12-28 15:59:06 +01:00
from django.db.models.functions import Lower
from django.http import Http404
from django.http import HttpResponse
from django.http import HttpResponseBadRequest
from django.utils.decorators import method_decorator
from django.utils.translation import get_language
from django.views.decorators.cache import cache_control
from django.views.generic import TemplateView
from django_filters.rest_framework import DjangoFilterBackend
from django_q.tasks import async_task
2022-04-01 01:48:05 -07:00
from packaging import version as packaging_version
from paperless import version
from paperless.db import GnuPG
from paperless.views import StandardPagination
from rest_framework import parsers
from rest_framework.decorators import action
2021-04-03 21:50:23 +02:00
from rest_framework.exceptions import NotFound
from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter
2021-03-16 20:47:45 +01:00
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import DestroyModelMixin
from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.mixins import UpdateModelMixin
2016-03-01 18:57:12 +00:00
from rest_framework.permissions import IsAuthenticated
2020-11-12 21:09:45 +01:00
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ViewSet
from .bulk_download import ArchiveOnlyStrategy
from .bulk_download import OriginalAndArchiveStrategy
from .bulk_download import OriginalsOnlyStrategy
from .classifier import load_classifier
from .filters import CorrespondentFilterSet
from .filters import DocumentFilterSet
from .filters import DocumentTypeFilterSet
from .filters import TagFilterSet
from .matching import match_correspondents
from .matching import match_document_types
from .matching import match_tags
from .models import Correspondent
from .models import Document
from .models import DocumentType
from .models import SavedView
from .models import Tag
from .parsers import get_parser_class_for_mime_type
from .serialisers import BulkDownloadSerializer
from .serialisers import BulkEditSerializer
from .serialisers import CorrespondentSerializer
from .serialisers import DocumentListSerializer
from .serialisers import DocumentSerializer
from .serialisers import DocumentTypeSerializer
from .serialisers import PostDocumentSerializer
from .serialisers import SavedViewSerializer
from .serialisers import TagSerializer
from .serialisers import TagSerializerVersion1
2022-05-07 08:11:10 -07:00
from .serialisers import UiSettingsViewSerializer
2016-01-01 16:13:59 +00:00
2021-02-05 01:10:29 +01:00
logger = logging.getLogger("paperless.api")
2016-03-03 18:09:10 +00:00
class IndexView(TemplateView):
template_name = "index.html"
2016-02-16 09:28:34 +00:00
2021-01-02 01:19:01 +01:00
def get_language(self):
# This is here for the following reason:
# Django identifies languages in the form "en-us"
# However, angular generates locales as "en-US".
# this translates between these two forms.
lang = get_language()
if "-" in lang:
2022-02-27 15:26:41 +01:00
first = lang[: lang.index("-")]
second = lang[lang.index("-") + 1 :]
2021-01-02 01:19:01 +01:00
return f"{first}-{second.upper()}"
else:
return lang
2020-12-17 21:46:56 +01:00
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
2022-02-27 15:26:41 +01:00
context["cookie_prefix"] = settings.COOKIE_PREFIX
context["username"] = self.request.user.username
context["full_name"] = self.request.user.get_full_name()
context["styles_css"] = f"frontend/{self.get_language()}/styles.css"
context["runtime_js"] = f"frontend/{self.get_language()}/runtime.js"
context["polyfills_js"] = f"frontend/{self.get_language()}/polyfills.js"
2022-02-27 15:26:41 +01:00
context["main_js"] = f"frontend/{self.get_language()}/main.js"
context[
"webmanifest"
] = f"frontend/{self.get_language()}/manifest.webmanifest" # noqa: E501
2022-02-27 15:26:41 +01:00
context[
"apple_touch_icon"
] = f"frontend/{self.get_language()}/apple-touch-icon.png" # noqa: E501
2020-12-17 21:46:56 +01:00
return context
2016-02-16 09:28:34 +00:00
class CorrespondentViewSet(ModelViewSet):
model = Correspondent
2020-11-21 14:03:45 +01:00
queryset = Correspondent.objects.annotate(
document_count=Count("documents"),
last_correspondence=Max("documents__created"),
2022-02-27 15:26:41 +01:00
).order_by(Lower("name"))
2020-11-21 14:03:45 +01:00
serializer_class = CorrespondentSerializer
2016-02-21 00:55:38 +00:00
pagination_class = StandardPagination
2016-03-01 18:57:12 +00:00
permission_classes = (IsAuthenticated,)
filter_backends = (DjangoFilterBackend, OrderingFilter)
2020-11-17 22:31:43 +01:00
filterset_class = CorrespondentFilterSet
2020-11-21 14:03:45 +01:00
ordering_fields = (
"name",
"matching_algorithm",
"match",
"document_count",
2022-02-27 15:26:41 +01:00
"last_correspondence",
)
2016-02-16 09:28:34 +00:00
class TagViewSet(ModelViewSet):
model = Tag
2020-11-21 14:03:45 +01:00
2022-02-27 15:26:41 +01:00
queryset = Tag.objects.annotate(document_count=Count("documents")).order_by(
Lower("name"),
2022-02-27 15:26:41 +01:00
)
2020-11-21 14:03:45 +01:00
2021-02-24 23:54:19 +01:00
def get_serializer_class(self):
if int(self.request.version) == 1:
return TagSerializerVersion1
else:
return TagSerializer
2016-02-21 00:55:38 +00:00
pagination_class = StandardPagination
2016-03-01 18:57:12 +00:00
permission_classes = (IsAuthenticated,)
filter_backends = (DjangoFilterBackend, OrderingFilter)
2020-11-17 22:31:43 +01:00
filterset_class = TagFilterSet
ordering_fields = ("name", "matching_algorithm", "match", "document_count")
2016-02-16 09:28:34 +00:00
2018-09-05 15:25:14 +02:00
class DocumentTypeViewSet(ModelViewSet):
model = DocumentType
2020-11-21 14:03:45 +01:00
queryset = DocumentType.objects.annotate(
document_count=Count("documents"),
2022-02-27 15:26:41 +01:00
).order_by(Lower("name"))
2020-11-21 14:03:45 +01:00
2018-09-05 15:25:14 +02:00
serializer_class = DocumentTypeSerializer
pagination_class = StandardPagination
permission_classes = (IsAuthenticated,)
filter_backends = (DjangoFilterBackend, OrderingFilter)
2020-11-17 22:31:43 +01:00
filterset_class = DocumentTypeFilterSet
ordering_fields = ("name", "matching_algorithm", "match", "document_count")
2018-09-05 15:25:14 +02:00
2022-02-27 15:26:41 +01:00
class DocumentViewSet(
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin,
ListModelMixin,
GenericViewSet,
):
2016-02-16 09:28:34 +00:00
model = Document
2016-02-21 00:55:38 +00:00
queryset = Document.objects.all()
2016-02-16 09:28:34 +00:00
serializer_class = DocumentSerializer
2016-02-21 00:55:38 +00:00
pagination_class = StandardPagination
2016-03-01 18:57:12 +00:00
permission_classes = (IsAuthenticated,)
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
2020-11-17 22:31:43 +01:00
filterset_class = DocumentFilterSet
2016-03-09 01:05:46 +00:00
search_fields = ("title", "correspondent__name", "content")
2016-03-13 16:45:12 +00:00
ordering_fields = (
2020-11-21 14:03:45 +01:00
"id",
"title",
"correspondent__name",
"document_type__name",
"created",
"modified",
"added",
2022-02-27 15:26:41 +01:00
"archive_serial_number",
)
def get_queryset(self):
return Document.objects.distinct()
def get_serializer(self, *args, **kwargs):
2022-02-27 15:26:41 +01:00
fields_param = self.request.query_params.get("fields", None)
if fields_param:
fields = fields_param.split(",")
else:
fields = None
serializer_class = self.get_serializer_class()
2022-02-27 15:26:41 +01:00
kwargs.setdefault("context", self.get_serializer_context())
kwargs.setdefault("fields", fields)
return serializer_class(*args, **kwargs)
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
2021-02-15 13:26:36 +01:00
from documents import index
2022-02-27 15:26:41 +01:00
index.add_or_update_document(self.get_object())
return response
def destroy(self, request, *args, **kwargs):
2021-02-15 13:26:36 +01:00
from documents import index
2022-02-27 15:26:41 +01:00
index.remove_document_from_index(self.get_object())
return super().destroy(request, *args, **kwargs)
2020-11-25 14:48:36 +01:00
@staticmethod
def original_requested(request):
return (
2022-02-27 15:26:41 +01:00
"original" in request.query_params
and request.query_params["original"] == "true"
2020-11-25 14:48:36 +01:00
)
2020-11-25 14:48:36 +01:00
def file_response(self, pk, request, disposition):
doc = Document.objects.get(id=pk)
if not self.original_requested(request) and doc.has_archive_version:
2020-11-25 14:48:36 +01:00
file_handle = doc.archive_file
filename = doc.get_public_filename(archive=True)
2022-02-27 15:26:41 +01:00
mime_type = "application/pdf"
else:
2020-11-25 20:16:27 +01:00
file_handle = doc.source_file
filename = doc.get_public_filename()
2020-11-25 20:16:27 +01:00
mime_type = doc.mime_type
2020-11-25 20:16:27 +01:00
if doc.storage_type == Document.STORAGE_TYPE_GPG:
file_handle = GnuPG.decrypted(file_handle)
2020-11-25 14:48:36 +01:00
response = HttpResponse(file_handle, content_type=mime_type)
# Firefox is not able to handle unicode characters in filename field
# RFC 5987 addresses this issue
# see https://datatracker.ietf.org/doc/html/rfc5987#section-4.2
2022-02-27 15:26:41 +01:00
filename_normalized = normalize("NFKD", filename).encode("ascii", "ignore")
filename_encoded = quote(filename)
2022-02-27 15:26:41 +01:00
content_disposition = (
f"{disposition}; "
f'filename="{filename_normalized}"; '
f"filename*=utf-8''{filename_encoded}"
)
response["Content-Disposition"] = content_disposition
return response
def get_metadata(self, file, mime_type):
2020-12-08 15:28:09 +01:00
if not os.path.isfile(file):
return None
parser_class = get_parser_class_for_mime_type(mime_type)
if parser_class:
2021-01-04 23:05:16 +01:00
parser = parser_class(progress_callback=None, logging_group=None)
try:
return parser.extract_metadata(file, mime_type)
except Exception:
# TODO: cover GPG errors, remove later.
return []
else:
return []
2020-12-08 15:28:09 +01:00
def get_filesize(self, filename):
if os.path.isfile(filename):
return os.stat(filename).st_size
else:
return None
2022-02-27 15:26:41 +01:00
@action(methods=["get"], detail=True)
def metadata(self, request, pk=None):
try:
doc = Document.objects.get(pk=pk)
2021-01-29 16:45:23 +01:00
except Document.DoesNotExist:
raise Http404()
2020-12-08 16:09:47 +01:00
2021-01-29 16:45:23 +01:00
meta = {
"original_checksum": doc.checksum,
"original_size": self.get_filesize(doc.source_path),
2021-01-29 16:45:23 +01:00
"original_mime_type": doc.mime_type,
"media_filename": doc.filename,
"has_archive_version": doc.has_archive_version,
2022-02-27 15:26:41 +01:00
"original_metadata": self.get_metadata(doc.source_path, doc.mime_type),
"archive_checksum": doc.archive_checksum,
2022-02-27 15:26:41 +01:00
"archive_media_filename": doc.archive_filename,
2021-01-29 16:45:23 +01:00
}
if doc.has_archive_version:
2022-02-27 15:26:41 +01:00
meta["archive_size"] = self.get_filesize(doc.archive_path)
meta["archive_metadata"] = self.get_metadata(
doc.archive_path,
"application/pdf",
2022-02-27 15:26:41 +01:00
)
2021-01-29 16:45:23 +01:00
else:
2022-02-27 15:26:41 +01:00
meta["archive_size"] = None
meta["archive_metadata"] = None
2021-01-29 16:45:23 +01:00
return Response(meta)
2020-12-08 16:09:47 +01:00
2022-02-27 15:26:41 +01:00
@action(methods=["get"], detail=True)
2021-01-29 16:45:23 +01:00
def suggestions(self, request, pk=None):
try:
doc = Document.objects.get(pk=pk)
except Document.DoesNotExist:
raise Http404()
classifier = load_classifier()
2021-01-29 16:45:23 +01:00
2022-02-27 15:26:41 +01:00
return Response(
{
"correspondents": [c.id for c in match_correspondents(doc, classifier)],
"tags": [t.id for t in match_tags(doc, classifier)],
"document_types": [
dt.id for dt in match_document_types(doc, classifier)
],
},
2022-02-27 15:26:41 +01:00
)
@action(methods=["get"], detail=True)
def preview(self, request, pk=None):
try:
2022-02-27 15:26:41 +01:00
response = self.file_response(pk, request, "inline")
return response
except (FileNotFoundError, Document.DoesNotExist):
raise Http404()
2022-02-27 15:26:41 +01:00
@action(methods=["get"], detail=True)
@method_decorator(cache_control(public=False, max_age=315360000))
def thumb(self, request, pk=None):
try:
doc = Document.objects.get(id=pk)
if doc.storage_type == Document.STORAGE_TYPE_GPG:
handle = GnuPG.decrypted(doc.thumbnail_file)
else:
handle = doc.thumbnail_file
2021-02-09 21:53:10 +01:00
# TODO: Send ETag information and use that to send new thumbnails
# if available
2022-02-27 15:26:41 +01:00
return HttpResponse(handle, content_type="image/png")
except (FileNotFoundError, Document.DoesNotExist):
raise Http404()
2022-02-27 15:26:41 +01:00
@action(methods=["get"], detail=True)
def download(self, request, pk=None):
try:
2022-02-27 15:26:41 +01:00
return self.file_response(pk, request, "attachment")
except (FileNotFoundError, Document.DoesNotExist):
raise Http404()
2016-03-01 18:57:12 +00:00
class SearchResultSerializer(DocumentSerializer):
def to_representation(self, instance):
2022-02-27 15:26:41 +01:00
doc = Document.objects.get(id=instance["id"])
r = super().to_representation(doc)
2022-02-27 15:26:41 +01:00
r["__search_hit__"] = {
"score": instance.score,
2022-02-27 15:26:41 +01:00
"highlights": instance.highlights("content", text=doc.content)
if doc
else None,
2022-02-27 15:26:41 +01:00
"rank": instance.rank,
}
2021-04-04 00:04:00 +02:00
return r
class UnifiedSearchViewSet(DocumentViewSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.searcher = None
def get_serializer_class(self):
if self._is_search_request():
return SearchResultSerializer
else:
return DocumentSerializer
def _is_search_request(self):
2022-02-27 15:26:41 +01:00
return (
"query" in self.request.query_params
or "more_like_id" in self.request.query_params
)
def filter_queryset(self, queryset):
if self._is_search_request():
from documents import index
if "query" in self.request.query_params:
query_class = index.DelayedFullTextQuery
elif "more_like_id" in self.request.query_params:
query_class = index.DelayedMoreLikeThisQuery
else:
raise ValueError()
return query_class(
self.searcher,
self.request.query_params,
2022-02-27 15:26:41 +01:00
self.paginator.get_page_size(self.request),
)
else:
return super().filter_queryset(queryset)
def list(self, request, *args, **kwargs):
if self._is_search_request():
from documents import index
2022-02-27 15:26:41 +01:00
try:
with index.open_index_searcher() as s:
self.searcher = s
return super().list(request)
2021-04-03 21:50:23 +02:00
except NotFound:
raise
except Exception as e:
return HttpResponseBadRequest(str(e))
else:
return super().list(request)
2021-02-06 17:02:00 +01:00
class LogViewSet(ViewSet):
2020-11-02 01:24:56 +01:00
2016-03-01 18:57:12 +00:00
permission_classes = (IsAuthenticated,)
2021-02-06 17:02:00 +01:00
log_files = ["paperless", "mail"]
def retrieve(self, request, pk=None, *args, **kwargs):
2021-02-06 17:21:32 +01:00
if pk not in self.log_files:
2021-02-06 17:02:00 +01:00
raise Http404()
filename = os.path.join(settings.LOGGING_DIR, f"{pk}.log")
if not os.path.isfile(filename):
raise Http404()
with open(filename) as f:
2021-02-06 17:21:32 +01:00
lines = [line.rstrip() for line in f.readlines()]
2021-02-06 17:02:00 +01:00
return Response(lines)
def list(self, request, *args, **kwargs):
return Response(self.log_files)
2020-12-12 15:46:56 +01:00
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)
2021-03-16 20:47:45 +01:00
class BulkEditView(GenericAPIView):
2020-12-06 14:39:53 +01:00
permission_classes = (IsAuthenticated,)
serializer_class = BulkEditSerializer
parser_classes = (parsers.JSONParser,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
2020-12-11 14:30:18 +01:00
method = serializer.validated_data.get("method")
parameters = serializer.validated_data.get("parameters")
documents = serializer.validated_data.get("documents")
try:
# TODO: parameter validation
result = method(documents, **parameters)
return Response({"result": result})
except Exception as e:
return HttpResponseBadRequest(str(e))
2020-12-06 14:39:53 +01:00
2021-03-16 20:47:45 +01:00
class PostDocumentView(GenericAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = PostDocumentSerializer
parser_classes = (parsers.MultiPartParser,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
2022-02-27 15:26:41 +01:00
doc_name, doc_data = serializer.validated_data.get("document")
correspondent_id = serializer.validated_data.get("correspondent")
document_type_id = serializer.validated_data.get("document_type")
tag_ids = serializer.validated_data.get("tags")
title = serializer.validated_data.get("title")
t = int(mktime(datetime.now().timetuple()))
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
2022-02-27 15:26:41 +01:00
with tempfile.NamedTemporaryFile(
prefix="paperless-upload-",
dir=settings.SCRATCH_DIR,
delete=False,
2022-02-27 15:26:41 +01:00
) as f:
f.write(doc_data)
os.utime(f.name, times=(t, t))
temp_filename = f.name
task_id = str(uuid.uuid4())
2022-02-27 15:26:41 +01:00
async_task(
"documents.tasks.consume_file",
temp_filename,
override_filename=doc_name,
override_title=title,
override_correspondent_id=correspondent_id,
override_document_type_id=document_type_id,
override_tag_ids=tag_ids,
task_id=task_id,
task_name=os.path.basename(doc_name)[:100],
)
return Response("OK")
2021-03-16 20:47:45 +01:00
class SelectionDataView(GenericAPIView):
2020-12-27 12:43:05 +01:00
permission_classes = (IsAuthenticated,)
serializer_class = DocumentListSerializer
2020-12-27 12:43:05 +01:00
parser_classes = (parsers.MultiPartParser, parsers.JSONParser)
def post(self, request, format=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
2022-02-27 15:26:41 +01:00
ids = serializer.validated_data.get("documents")
2020-12-27 12:43:05 +01:00
2020-12-27 12:54:47 +01:00
correspondents = Correspondent.objects.annotate(
2022-02-27 15:26:41 +01:00
document_count=Count(
Case(When(documents__id__in=ids, then=1), output_field=IntegerField()),
),
2022-02-27 15:26:41 +01:00
)
tags = Tag.objects.annotate(
document_count=Count(
Case(When(documents__id__in=ids, then=1), output_field=IntegerField()),
),
2022-02-27 15:26:41 +01:00
)
types = DocumentType.objects.annotate(
document_count=Count(
Case(When(documents__id__in=ids, then=1), output_field=IntegerField()),
),
2022-02-27 15:26:41 +01:00
)
r = Response(
{
"selected_correspondents": [
{"id": t.id, "document_count": t.document_count}
for t in correspondents
],
"selected_tags": [
{"id": t.id, "document_count": t.document_count} for t in tags
],
"selected_document_types": [
{"id": t.id, "document_count": t.document_count} for t in types
],
},
2022-02-27 15:26:41 +01:00
)
2020-12-27 12:43:05 +01:00
return r
2020-10-27 17:07:13 +01:00
class SearchAutoCompleteView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
2022-02-27 15:26:41 +01:00
if "term" in request.query_params:
term = request.query_params["term"]
2020-10-27 17:07:13 +01:00
else:
2020-11-17 14:20:28 +01:00
return HttpResponseBadRequest("Term required")
2020-10-27 17:07:13 +01:00
2022-02-27 15:26:41 +01:00
if "limit" in request.query_params:
limit = int(request.query_params["limit"])
2020-11-17 14:20:28 +01:00
if limit <= 0:
return HttpResponseBadRequest("Invalid limit")
2020-10-27 17:07:13 +01:00
else:
limit = 10
2021-02-15 13:26:36 +01:00
from documents import index
ix = index.open_index()
return Response(index.autocomplete(ix, term, limit))
2020-10-31 00:56:20 +01:00
class StatisticsView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
documents_total = Document.objects.all().count()
if Tag.objects.filter(is_inbox_tag=True).exists():
2022-02-27 15:26:41 +01:00
documents_inbox = (
Document.objects.filter(tags__is_inbox_tag=True).distinct().count()
)
else:
documents_inbox = None
2022-02-27 15:26:41 +01:00
return Response(
{
"documents_total": documents_total,
"documents_inbox": documents_inbox,
},
2022-02-27 15:26:41 +01:00
)
2021-03-16 20:47:45 +01:00
class BulkDownloadView(GenericAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = BulkDownloadSerializer
parser_classes = (parsers.JSONParser,)
def post(self, request, format=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
2022-02-27 15:26:41 +01:00
ids = serializer.validated_data.get("documents")
compression = serializer.validated_data.get("compression")
content = serializer.validated_data.get("content")
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
2021-02-21 00:21:43 +01:00
temp = tempfile.NamedTemporaryFile(
dir=settings.SCRATCH_DIR,
suffix="-compressed-archive",
delete=False,
2022-02-27 15:26:41 +01:00
)
2022-02-27 15:26:41 +01:00
if content == "both":
strategy_class = OriginalAndArchiveStrategy
2022-02-27 15:26:41 +01:00
elif content == "originals":
strategy_class = OriginalsOnlyStrategy
else:
strategy_class = ArchiveOnlyStrategy
with zipfile.ZipFile(temp.name, "w", compression) as zipf:
strategy = strategy_class(zipf)
for id in ids:
doc = Document.objects.get(id=id)
strategy.add_document(doc)
with open(temp.name, "rb") as f:
response = HttpResponse(f, content_type="application/zip")
response["Content-Disposition"] = '{}; filename="{}"'.format(
"attachment",
"documents.zip",
2022-02-27 15:26:41 +01:00
)
return response
2022-04-01 01:48:05 -07:00
class RemoteVersionView(GenericAPIView):
def get(self, request, format=None):
2022-04-01 07:22:55 -07:00
remote_version = "0.0.0"
is_greater_than_current = False
current_version = packaging_version.parse(version.__full_version_str__)
# TODO: this can likely be removed when frontend settings are saved to DB
feature_is_set = settings.ENABLE_UPDATE_CHECK != "default"
if feature_is_set and settings.ENABLE_UPDATE_CHECK:
2022-04-01 07:22:55 -07:00
try:
req = urllib.request.Request(
"https://api.github.com/repos/paperless-ngx/"
"paperless-ngx/releases/latest",
)
# Ensure a JSON response
req.add_header("Accept", "application/json")
with urllib.request.urlopen(req) as response:
2022-04-01 07:22:55 -07:00
remote = response.read().decode("utf-8")
2022-04-01 15:31:10 -07:00
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-") :]
2022-04-01 15:31:10 -07:00
except ValueError:
logger.debug("An error occurred parsing remote version json")
2022-04-01 07:22:55 -07:00
except urllib.error.URLError:
logger.debug("An error occurred checking for available updates")
2022-04-01 07:22:55 -07:00
is_greater_than_current = (
packaging_version.parse(
remote_version,
)
> current_version
2022-04-01 07:22:55 -07:00
)
2022-04-01 01:48:05 -07:00
return Response(
{
"version": remote_version,
"update_available": is_greater_than_current,
"feature_is_set": feature_is_set,
2022-04-01 01:48:05 -07:00
},
)
2022-05-07 08:11:10 -07:00
class UiSettingsView(GenericAPIView):
permission_classes = (IsAuthenticated,)
2022-05-07 08:11:10 -07:00
serializer_class = UiSettingsViewSerializer
def get(self, request, format=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = User.objects.get(pk=request.user.id)
2022-05-09 11:08:28 -07:00
displayname = user.username
if user.first_name or user.last_name:
displayname = " ".join([user.first_name, user.last_name])
settings = []
2022-05-07 08:11:10 -07:00
if hasattr(user, "ui_settings"):
settings = user.ui_settings.settings
return Response(
{
"user_id": user.id,
2022-05-09 11:08:28 -07:00
"username": user.username,
"display_name": displayname,
"settings": settings,
},
)
def post(self, request, format=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=self.request.user)
return Response(
{
"success": True,
},
)