mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-11-12 09:36:41 -05:00
Backend initial stuff
This commit is contained in:
parent
cf3647d6ff
commit
1ff6857a60
@ -12,6 +12,7 @@ export interface ShareBundleSummary {
|
|||||||
slug: string
|
slug: string
|
||||||
created: string // Date
|
created: string // Date
|
||||||
expiration?: string // Date
|
expiration?: string // Date
|
||||||
|
documents: number[]
|
||||||
document_count: number
|
document_count: number
|
||||||
file_version: FileVersion
|
file_version: FileVersion
|
||||||
status: ShareBundleStatus
|
status: ShareBundleStatus
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from documents.models import Note
|
|||||||
from documents.models import PaperlessTask
|
from documents.models import PaperlessTask
|
||||||
from documents.models import SavedView
|
from documents.models import SavedView
|
||||||
from documents.models import SavedViewFilterRule
|
from documents.models import SavedViewFilterRule
|
||||||
|
from documents.models import ShareBundle
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
@ -185,6 +186,22 @@ class ShareLinksAdmin(GuardedModelAdmin):
|
|||||||
return super().get_queryset(request).select_related("document__correspondent")
|
return super().get_queryset(request).select_related("document__correspondent")
|
||||||
|
|
||||||
|
|
||||||
|
class ShareBundleAdmin(GuardedModelAdmin):
|
||||||
|
list_display = ("created", "status", "expiration", "owner", "slug")
|
||||||
|
list_filter = ("status", "created", "expiration", "owner")
|
||||||
|
search_fields = ("slug",)
|
||||||
|
|
||||||
|
def get_queryset(self, request): # pragma: no cover
|
||||||
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset(request)
|
||||||
|
.select_related("owner")
|
||||||
|
.prefetch_related(
|
||||||
|
"documents",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldsAdmin(GuardedModelAdmin):
|
class CustomFieldsAdmin(GuardedModelAdmin):
|
||||||
fields = ("name", "created", "data_type")
|
fields = ("name", "created", "data_type")
|
||||||
readonly_fields = ("created", "data_type")
|
readonly_fields = ("created", "data_type")
|
||||||
@ -216,6 +233,7 @@ admin.site.register(StoragePath, StoragePathAdmin)
|
|||||||
admin.site.register(PaperlessTask, TaskAdmin)
|
admin.site.register(PaperlessTask, TaskAdmin)
|
||||||
admin.site.register(Note, NotesAdmin)
|
admin.site.register(Note, NotesAdmin)
|
||||||
admin.site.register(ShareLink, ShareLinksAdmin)
|
admin.site.register(ShareLink, ShareLinksAdmin)
|
||||||
|
admin.site.register(ShareBundle, ShareBundleAdmin)
|
||||||
admin.site.register(CustomField, CustomFieldsAdmin)
|
admin.site.register(CustomField, CustomFieldsAdmin)
|
||||||
admin.site.register(CustomFieldInstance, CustomFieldInstancesAdmin)
|
admin.site.register(CustomFieldInstance, CustomFieldInstancesAdmin)
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ from documents.models import CustomFieldInstance
|
|||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import PaperlessTask
|
from documents.models import PaperlessTask
|
||||||
|
from documents.models import ShareBundle
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
@ -793,6 +794,29 @@ class ShareLinkFilterSet(FilterSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ShareBundleFilterSet(FilterSet):
|
||||||
|
documents = Filter(method="filter_documents")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ShareBundle
|
||||||
|
fields = {
|
||||||
|
"created": DATETIME_KWARGS,
|
||||||
|
"expiration": DATETIME_KWARGS,
|
||||||
|
"status": ["exact"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def filter_documents(self, queryset, name, value):
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
try:
|
||||||
|
ids = [int(item) for item in value.split(",") if item]
|
||||||
|
except ValueError:
|
||||||
|
return queryset.none()
|
||||||
|
if not ids:
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(documents__in=ids).distinct()
|
||||||
|
|
||||||
|
|
||||||
class PaperlessTaskFilterSet(FilterSet):
|
class PaperlessTaskFilterSet(FilterSet):
|
||||||
acknowledged = BooleanFilter(
|
acknowledged = BooleanFilter(
|
||||||
label="Acknowledged",
|
label="Acknowledged",
|
||||||
|
|||||||
174
src/documents/migrations/1075_sharebundle.py
Normal file
174
src/documents/migrations/1075_sharebundle.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-04 18:34
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.management import create_permissions
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
def grant_sharebundle_permissions(apps, schema_editor):
|
||||||
|
# Ensure newly introduced permissions are created for all apps
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
app_config.models_module = True
|
||||||
|
create_permissions(app_config, apps=apps, verbosity=0)
|
||||||
|
app_config.models_module = None
|
||||||
|
|
||||||
|
add_document_perm = Permission.objects.filter(codename="add_document").first()
|
||||||
|
if add_document_perm is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
sharebundle_permissions = Permission.objects.filter(
|
||||||
|
codename__contains="sharebundle",
|
||||||
|
)
|
||||||
|
|
||||||
|
users = User.objects.filter(user_permissions=add_document_perm).distinct()
|
||||||
|
for user in users:
|
||||||
|
user.user_permissions.add(*sharebundle_permissions)
|
||||||
|
|
||||||
|
groups = Group.objects.filter(permissions=add_document_perm).distinct()
|
||||||
|
for group in groups:
|
||||||
|
group.permissions.add(*sharebundle_permissions)
|
||||||
|
|
||||||
|
|
||||||
|
def revoke_sharebundle_permissions(apps, schema_editor):
|
||||||
|
sharebundle_permissions = Permission.objects.filter(
|
||||||
|
codename__contains="sharebundle",
|
||||||
|
)
|
||||||
|
for user in User.objects.all():
|
||||||
|
user.user_permissions.remove(*sharebundle_permissions)
|
||||||
|
for group in Group.objects.all():
|
||||||
|
group.permissions.remove(*sharebundle_permissions)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ShareBundle",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
editable=False,
|
||||||
|
verbose_name="created",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"expiration",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="expiration",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug",
|
||||||
|
models.SlugField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file_version",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("archive", "Archive"), ("original", "Original")],
|
||||||
|
default="archive",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "Pending"),
|
||||||
|
("processing", "Processing"),
|
||||||
|
("ready", "Ready"),
|
||||||
|
("failed", "Failed"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"size_bytes",
|
||||||
|
models.BigIntegerField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="size (bytes)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"last_error",
|
||||||
|
models.TextField(
|
||||||
|
blank=True,
|
||||||
|
verbose_name="last error",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="share_bundles",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="owner",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deleted_at",
|
||||||
|
models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"restored_at",
|
||||||
|
models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"transaction_id",
|
||||||
|
models.UUIDField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ("-created",),
|
||||||
|
"verbose_name": "share bundle",
|
||||||
|
"verbose_name_plural": "share bundles",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="sharebundle",
|
||||||
|
name="documents",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="share_bundles",
|
||||||
|
to="documents.document",
|
||||||
|
verbose_name="documents",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
grant_sharebundle_permissions,
|
||||||
|
reverse_code=revoke_sharebundle_permissions,
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -777,6 +777,83 @@ class ShareLink(SoftDeleteModel):
|
|||||||
return f"Share Link for {self.document.title}"
|
return f"Share Link for {self.document.title}"
|
||||||
|
|
||||||
|
|
||||||
|
class ShareBundle(SoftDeleteModel):
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = ("pending", _("Pending"))
|
||||||
|
PROCESSING = ("processing", _("Processing"))
|
||||||
|
READY = ("ready", _("Ready"))
|
||||||
|
FAILED = ("failed", _("Failed"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("-created",)
|
||||||
|
verbose_name = _("share bundle")
|
||||||
|
verbose_name_plural = _("share bundles")
|
||||||
|
|
||||||
|
created = models.DateTimeField(
|
||||||
|
_("created"),
|
||||||
|
default=timezone.now,
|
||||||
|
db_index=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
expiration = models.DateTimeField(
|
||||||
|
_("expiration"),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
slug = models.SlugField(
|
||||||
|
_("slug"),
|
||||||
|
db_index=True,
|
||||||
|
unique=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
related_name="share_bundles",
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_("owner"),
|
||||||
|
)
|
||||||
|
|
||||||
|
file_version = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=ShareLink.FileVersion.choices,
|
||||||
|
default=ShareLink.FileVersion.ARCHIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=Status.choices,
|
||||||
|
default=Status.PENDING,
|
||||||
|
)
|
||||||
|
|
||||||
|
size_bytes = models.BigIntegerField(
|
||||||
|
_("size (bytes)"),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
last_error = models.TextField(
|
||||||
|
_("last error"),
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
documents = models.ManyToManyField(
|
||||||
|
"documents.Document",
|
||||||
|
related_name="share_bundles",
|
||||||
|
verbose_name=_("documents"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Share bundle %(slug)s") % {"slug": self.slug}
|
||||||
|
|
||||||
|
|
||||||
class CustomField(models.Model):
|
class CustomField(models.Model):
|
||||||
"""
|
"""
|
||||||
Defines the name and type of a custom field
|
Defines the name and type of a custom field
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import logging
|
|||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
@ -21,6 +22,7 @@ from django.core.validators import MaxLengthValidator
|
|||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.core.validators import integer_validator
|
from django.core.validators import integer_validator
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.dateparse import parse_datetime
|
from django.utils.dateparse import parse_datetime
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
@ -56,6 +58,7 @@ from documents.models import Note
|
|||||||
from documents.models import PaperlessTask
|
from documents.models import PaperlessTask
|
||||||
from documents.models import SavedView
|
from documents.models import SavedView
|
||||||
from documents.models import SavedViewFilterRule
|
from documents.models import SavedViewFilterRule
|
||||||
|
from documents.models import ShareBundle
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
@ -2127,6 +2130,106 @@ class ShareLinkSerializer(OwnedObjectSerializer):
|
|||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class ShareBundleSerializer(OwnedObjectSerializer):
|
||||||
|
document_ids = serializers.ListField(
|
||||||
|
child=serializers.IntegerField(min_value=1),
|
||||||
|
allow_empty=False,
|
||||||
|
write_only=True,
|
||||||
|
)
|
||||||
|
expiration_days = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
allow_null=True,
|
||||||
|
min_value=1,
|
||||||
|
write_only=True,
|
||||||
|
)
|
||||||
|
documents = serializers.PrimaryKeyRelatedField(
|
||||||
|
many=True,
|
||||||
|
read_only=True,
|
||||||
|
)
|
||||||
|
document_count = SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ShareBundle
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"created",
|
||||||
|
"expiration",
|
||||||
|
"expiration_days",
|
||||||
|
"slug",
|
||||||
|
"file_version",
|
||||||
|
"status",
|
||||||
|
"size_bytes",
|
||||||
|
"last_error",
|
||||||
|
"documents",
|
||||||
|
"document_ids",
|
||||||
|
"document_count",
|
||||||
|
)
|
||||||
|
read_only_fields = (
|
||||||
|
"id",
|
||||||
|
"created",
|
||||||
|
"expiration",
|
||||||
|
"slug",
|
||||||
|
"status",
|
||||||
|
"size_bytes",
|
||||||
|
"last_error",
|
||||||
|
"documents",
|
||||||
|
"document_count",
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_document_ids(self, value):
|
||||||
|
unique_ids = set(value)
|
||||||
|
if len(unique_ids) != len(value):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_("Duplicate document identifiers are not allowed."),
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
document_ids = validated_data.pop("document_ids")
|
||||||
|
expiration_days = validated_data.pop("expiration_days", None)
|
||||||
|
documents = validated_data.pop("documents", None)
|
||||||
|
validated_data["slug"] = get_random_string(50)
|
||||||
|
if expiration_days:
|
||||||
|
validated_data["expiration"] = timezone.now() + timedelta(
|
||||||
|
days=expiration_days,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
validated_data["expiration"] = None
|
||||||
|
|
||||||
|
share_bundle = super().create(validated_data)
|
||||||
|
|
||||||
|
if documents is None:
|
||||||
|
documents = list(
|
||||||
|
Document.objects.filter(pk__in=document_ids).only(
|
||||||
|
"pk",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
documents = list(documents)
|
||||||
|
|
||||||
|
documents_by_id = {doc.pk: doc for doc in documents}
|
||||||
|
missing = [
|
||||||
|
str(doc_id) for doc_id in document_ids if doc_id not in documents_by_id
|
||||||
|
]
|
||||||
|
if missing:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
{
|
||||||
|
"document_ids": _(
|
||||||
|
"Documents not found: %(ids)s",
|
||||||
|
)
|
||||||
|
% {"ids": ", ".join(missing)},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ordered_documents = [documents_by_id[doc_id] for doc_id in document_ids]
|
||||||
|
share_bundle.documents.set(ordered_documents)
|
||||||
|
|
||||||
|
return share_bundle
|
||||||
|
|
||||||
|
def get_document_count(self, obj: ShareBundle) -> int:
|
||||||
|
return obj.documents.count()
|
||||||
|
|
||||||
|
|
||||||
class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
|
class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
|
||||||
objects = serializers.ListField(
|
objects = serializers.ListField(
|
||||||
required=True,
|
required=True,
|
||||||
|
|||||||
@ -50,6 +50,7 @@ from django.utils import timezone
|
|||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.cache import cache_control
|
from django.views.decorators.cache import cache_control
|
||||||
from django.views.decorators.http import condition
|
from django.views.decorators.http import condition
|
||||||
@ -69,6 +70,7 @@ from packaging import version as packaging_version
|
|||||||
from redis import Redis
|
from redis import Redis
|
||||||
from rest_framework import parsers
|
from rest_framework import parsers
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import NotFound
|
from rest_framework.exceptions import NotFound
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
@ -117,6 +119,7 @@ from documents.filters import DocumentTypeFilterSet
|
|||||||
from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
|
from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
|
||||||
from documents.filters import ObjectOwnedPermissionsFilter
|
from documents.filters import ObjectOwnedPermissionsFilter
|
||||||
from documents.filters import PaperlessTaskFilterSet
|
from documents.filters import PaperlessTaskFilterSet
|
||||||
|
from documents.filters import ShareBundleFilterSet
|
||||||
from documents.filters import ShareLinkFilterSet
|
from documents.filters import ShareLinkFilterSet
|
||||||
from documents.filters import StoragePathFilterSet
|
from documents.filters import StoragePathFilterSet
|
||||||
from documents.filters import TagFilterSet
|
from documents.filters import TagFilterSet
|
||||||
@ -132,6 +135,7 @@ from documents.models import DocumentType
|
|||||||
from documents.models import Note
|
from documents.models import Note
|
||||||
from documents.models import PaperlessTask
|
from documents.models import PaperlessTask
|
||||||
from documents.models import SavedView
|
from documents.models import SavedView
|
||||||
|
from documents.models import ShareBundle
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
@ -166,6 +170,7 @@ from documents.serialisers import PostDocumentSerializer
|
|||||||
from documents.serialisers import RunTaskViewSerializer
|
from documents.serialisers import RunTaskViewSerializer
|
||||||
from documents.serialisers import SavedViewSerializer
|
from documents.serialisers import SavedViewSerializer
|
||||||
from documents.serialisers import SearchResultSerializer
|
from documents.serialisers import SearchResultSerializer
|
||||||
|
from documents.serialisers import ShareBundleSerializer
|
||||||
from documents.serialisers import ShareLinkSerializer
|
from documents.serialisers import ShareLinkSerializer
|
||||||
from documents.serialisers import StoragePathSerializer
|
from documents.serialisers import StoragePathSerializer
|
||||||
from documents.serialisers import StoragePathTestSerializer
|
from documents.serialisers import StoragePathTestSerializer
|
||||||
@ -2251,7 +2256,7 @@ class BulkDownloadView(GenericAPIView):
|
|||||||
follow_filename_format = serializer.validated_data.get("follow_formatting")
|
follow_filename_format = serializer.validated_data.get("follow_formatting")
|
||||||
|
|
||||||
for document in documents:
|
for document in documents:
|
||||||
if not has_perms_owner_aware(request.user, "view_document", document):
|
if not has_perms_owner_aware(request.user, "change_document", document):
|
||||||
return HttpResponseForbidden("Insufficient permissions")
|
return HttpResponseForbidden("Insufficient permissions")
|
||||||
|
|
||||||
settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True)
|
settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
@ -2598,6 +2603,68 @@ class ShareLinkViewSet(ModelViewSet, PassUserMixin):
|
|||||||
ordering_fields = ("created", "expiration", "document")
|
ordering_fields = ("created", "expiration", "document")
|
||||||
|
|
||||||
|
|
||||||
|
class ShareBundleViewSet(ModelViewSet, PassUserMixin):
|
||||||
|
model = ShareBundle
|
||||||
|
|
||||||
|
queryset = ShareBundle.objects.all()
|
||||||
|
|
||||||
|
serializer_class = ShareBundleSerializer
|
||||||
|
pagination_class = StandardPagination
|
||||||
|
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||||
|
filter_backends = (
|
||||||
|
DjangoFilterBackend,
|
||||||
|
OrderingFilter,
|
||||||
|
ObjectOwnedOrGrantedPermissionsFilter,
|
||||||
|
)
|
||||||
|
filterset_class = ShareBundleFilterSet
|
||||||
|
ordering_fields = ("created", "expiration", "status")
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().prefetch_related("documents")
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
document_ids = serializer.validated_data["document_ids"]
|
||||||
|
documents_qs = Document.objects.filter(pk__in=document_ids).select_related(
|
||||||
|
"owner",
|
||||||
|
)
|
||||||
|
found_ids = set(documents_qs.values_list("pk", flat=True))
|
||||||
|
missing = sorted(set(document_ids) - found_ids)
|
||||||
|
if missing:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"document_ids": _(
|
||||||
|
"Documents not found: %(ids)s",
|
||||||
|
)
|
||||||
|
% {"ids": ", ".join(str(item) for item in missing)},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
documents = list(documents_qs)
|
||||||
|
for document in documents:
|
||||||
|
if not has_perms_owner_aware(request.user, "view_document", document):
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"document_ids": _(
|
||||||
|
"Insufficient permissions to share document %(id)s.",
|
||||||
|
)
|
||||||
|
% {"id": document.pk},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
serializer.save(
|
||||||
|
owner=request.user,
|
||||||
|
documents=documents,
|
||||||
|
)
|
||||||
|
headers = self.get_success_headers(serializer.data)
|
||||||
|
return Response(
|
||||||
|
serializer.data,
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SharedLinkView(View):
|
class SharedLinkView(View):
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from documents.views import RemoteVersionView
|
|||||||
from documents.views import SavedViewViewSet
|
from documents.views import SavedViewViewSet
|
||||||
from documents.views import SearchAutoCompleteView
|
from documents.views import SearchAutoCompleteView
|
||||||
from documents.views import SelectionDataView
|
from documents.views import SelectionDataView
|
||||||
|
from documents.views import ShareBundleViewSet
|
||||||
from documents.views import SharedLinkView
|
from documents.views import SharedLinkView
|
||||||
from documents.views import ShareLinkViewSet
|
from documents.views import ShareLinkViewSet
|
||||||
from documents.views import StatisticsView
|
from documents.views import StatisticsView
|
||||||
@ -72,6 +73,7 @@ api_router.register(r"users", UserViewSet, basename="users")
|
|||||||
api_router.register(r"groups", GroupViewSet, basename="groups")
|
api_router.register(r"groups", GroupViewSet, basename="groups")
|
||||||
api_router.register(r"mail_accounts", MailAccountViewSet)
|
api_router.register(r"mail_accounts", MailAccountViewSet)
|
||||||
api_router.register(r"mail_rules", MailRuleViewSet)
|
api_router.register(r"mail_rules", MailRuleViewSet)
|
||||||
|
api_router.register(r"share_bundles", ShareBundleViewSet)
|
||||||
api_router.register(r"share_links", ShareLinkViewSet)
|
api_router.register(r"share_links", ShareLinkViewSet)
|
||||||
api_router.register(r"workflow_triggers", WorkflowTriggerViewSet)
|
api_router.register(r"workflow_triggers", WorkflowTriggerViewSet)
|
||||||
api_router.register(r"workflow_actions", WorkflowActionViewSet)
|
api_router.register(r"workflow_actions", WorkflowActionViewSet)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user