mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-04-14 03:58:52 +00:00
Compare commits
1 Commits
dependabot
...
chore/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4c26fb204 |
1
.github/workflows/ci-backend.yml
vendored
1
.github/workflows/ci-backend.yml
vendored
@@ -165,7 +165,6 @@ jobs:
|
||||
contents: read
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
PAPERLESS_SECRET_KEY: "ci-typing-not-a-real-secret"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
2
.github/workflows/ci-release.yml
vendored
2
.github/workflows/ci-release.yml
vendored
@@ -88,7 +88,6 @@ jobs:
|
||||
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
|
||||
- name: Compile messages
|
||||
env:
|
||||
PAPERLESS_SECRET_KEY: "ci-release-not-a-real-secret"
|
||||
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
|
||||
run: |
|
||||
cd src/
|
||||
@@ -97,7 +96,6 @@ jobs:
|
||||
manage.py compilemessages
|
||||
- name: Collect static files
|
||||
env:
|
||||
PAPERLESS_SECRET_KEY: "ci-release-not-a-real-secret"
|
||||
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
|
||||
run: |
|
||||
cd src/
|
||||
|
||||
2
.github/workflows/translate-strings.yml
vendored
2
.github/workflows/translate-strings.yml
vendored
@@ -36,8 +36,6 @@ jobs:
|
||||
--group dev \
|
||||
--frozen
|
||||
- name: Generate backend translation strings
|
||||
env:
|
||||
PAPERLESS_SECRET_KEY: "ci-translate-not-a-real-secret"
|
||||
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -79,7 +79,6 @@ virtualenv
|
||||
/docker-compose.env
|
||||
/docker-compose.yml
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
|
||||
# Used for development
|
||||
scripts/import-for-development
|
||||
@@ -112,6 +111,4 @@ celerybeat-schedule*
|
||||
|
||||
# ignore pnpm package store folder created when setting up the devcontainer
|
||||
.pnpm-store/
|
||||
|
||||
# Git worktree local folder
|
||||
.worktrees
|
||||
|
||||
1958
.mypy-baseline.txt
1958
.mypy-baseline.txt
File diff suppressed because it is too large
Load Diff
12424
.pyrefly-baseline.json
12424
.pyrefly-baseline.json
File diff suppressed because one or more lines are too long
@@ -241,3 +241,66 @@ For example:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consume Script Positional Arguments Removed
|
||||
|
||||
Pre- and post-consumption scripts no longer receive positional arguments. All information is
|
||||
now passed exclusively via environment variables, which have been available since earlier versions.
|
||||
|
||||
### Pre-consumption script
|
||||
|
||||
Previously, the original file path was passed as `$1`. It is now only available as
|
||||
`DOCUMENT_SOURCE_PATH`.
|
||||
|
||||
**Before:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# $1 was the original file path
|
||||
process_document "$1"
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
process_document "${DOCUMENT_SOURCE_PATH}"
|
||||
```
|
||||
|
||||
### Post-consumption script
|
||||
|
||||
Previously, document metadata was passed as positional arguments `$1` through `$8`:
|
||||
|
||||
| Argument | Environment Variable Equivalent |
|
||||
| -------- | ------------------------------- |
|
||||
| `$1` | `DOCUMENT_ID` |
|
||||
| `$2` | `DOCUMENT_FILE_NAME` |
|
||||
| `$3` | `DOCUMENT_SOURCE_PATH` |
|
||||
| `$4` | `DOCUMENT_THUMBNAIL_PATH` |
|
||||
| `$5` | `DOCUMENT_DOWNLOAD_URL` |
|
||||
| `$6` | `DOCUMENT_THUMBNAIL_URL` |
|
||||
| `$7` | `DOCUMENT_CORRESPONDENT` |
|
||||
| `$8` | `DOCUMENT_TAGS` |
|
||||
|
||||
**Before:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
DOCUMENT_ID=$1
|
||||
CORRESPONDENT=$7
|
||||
TAGS=$8
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Use environment variables directly
|
||||
echo "Document ${DOCUMENT_ID} from ${DOCUMENT_CORRESPONDENT} tagged: ${DOCUMENT_TAGS}"
|
||||
```
|
||||
|
||||
### Action Required
|
||||
|
||||
Update any pre- or post-consumption scripts that read `$1`, `$2`, etc. to use the
|
||||
corresponding environment variables instead. Environment variables have been the preferred
|
||||
option since v1.8.0.
|
||||
|
||||
@@ -24,7 +24,7 @@ dependencies = [
|
||||
"dateparser~=1.2",
|
||||
# WARNING: django does not use semver.
|
||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||
"django~=5.2.13",
|
||||
"django~=5.2.10",
|
||||
"django-allauth[mfa,socialaccount]~=65.15.0",
|
||||
"django-auditlog~=3.4.1",
|
||||
"django-cachalot~=2.9.0",
|
||||
@@ -113,7 +113,7 @@ testing = [
|
||||
"factory-boy~=3.3.1",
|
||||
"faker~=40.12.0",
|
||||
"imagehash",
|
||||
"pytest~=9.0.3",
|
||||
"pytest~=9.0.0",
|
||||
"pytest-cov~=7.1.0",
|
||||
"pytest-django~=4.12.0",
|
||||
"pytest-env~=1.6.0",
|
||||
|
||||
@@ -313,7 +313,6 @@ class ConsumerPlugin(
|
||||
run_subprocess(
|
||||
[
|
||||
settings.PRE_CONSUME_SCRIPT,
|
||||
original_file_path,
|
||||
],
|
||||
script_env,
|
||||
self.log,
|
||||
@@ -383,14 +382,6 @@ class ConsumerPlugin(
|
||||
run_subprocess(
|
||||
[
|
||||
settings.POST_CONSUME_SCRIPT,
|
||||
str(document.pk),
|
||||
document.get_public_filename(),
|
||||
os.path.normpath(document.source_path),
|
||||
os.path.normpath(document.thumbnail_path),
|
||||
reverse("document-download", kwargs={"pk": document.pk}),
|
||||
reverse("document-thumb", kwargs={"pk": document.pk}),
|
||||
str(document.correspondent),
|
||||
str(",".join(document.tags.all().values_list("name", flat=True))),
|
||||
],
|
||||
script_env,
|
||||
self.log,
|
||||
@@ -650,10 +641,6 @@ class ConsumerPlugin(
|
||||
# If we get here, it was successful. Proceed with post-consume
|
||||
# hooks. If they fail, nothing will get changed.
|
||||
|
||||
document = Document.objects.prefetch_related("versions").get(
|
||||
pk=document.pk,
|
||||
)
|
||||
|
||||
document_consumption_finished.send(
|
||||
sender=self.__class__,
|
||||
document=document,
|
||||
|
||||
@@ -381,10 +381,7 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
||||
if isinstance(prefetched_cache, dict)
|
||||
else None
|
||||
)
|
||||
if prefetched_versions is not None:
|
||||
# Empty list means prefetch ran and found no versions — use own content.
|
||||
if not prefetched_versions:
|
||||
return self.content
|
||||
if prefetched_versions:
|
||||
latest_prefetched = max(prefetched_versions, key=lambda doc: doc.id)
|
||||
return latest_prefetched.content
|
||||
|
||||
|
||||
@@ -182,9 +182,8 @@ def _check_thumbnail(
|
||||
present_files: set[Path],
|
||||
) -> None:
|
||||
"""Verify the thumbnail exists and is readable."""
|
||||
# doc.thumbnail_path already returns a resolved Path; no need to re-resolve.
|
||||
thumbnail_path: Final[Path] = doc.thumbnail_path
|
||||
if not thumbnail_path.is_file():
|
||||
thumbnail_path: Final[Path] = Path(doc.thumbnail_path).resolve()
|
||||
if not thumbnail_path.exists() or not thumbnail_path.is_file():
|
||||
messages.error(doc.pk, "Thumbnail of document does not exist.")
|
||||
return
|
||||
|
||||
@@ -201,9 +200,8 @@ def _check_original(
|
||||
present_files: set[Path],
|
||||
) -> None:
|
||||
"""Verify the original file exists, is readable, and has matching checksum."""
|
||||
# doc.source_path already returns a resolved Path; no need to re-resolve.
|
||||
source_path: Final[Path] = doc.source_path
|
||||
if not source_path.is_file():
|
||||
source_path: Final[Path] = Path(doc.source_path).resolve()
|
||||
if not source_path.exists() or not source_path.is_file():
|
||||
messages.error(doc.pk, "Original of document does not exist.")
|
||||
return
|
||||
|
||||
@@ -239,9 +237,8 @@ def _check_archive(
|
||||
elif doc.has_archive_version:
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(doc.archive_path, Path)
|
||||
# doc.archive_path already returns a resolved Path; no need to re-resolve.
|
||||
archive_path: Final[Path] = doc.archive_path # type: ignore[assignment]
|
||||
if not archive_path.is_file():
|
||||
archive_path: Final[Path] = Path(doc.archive_path).resolve()
|
||||
if not archive_path.exists() or not archive_path.is_file():
|
||||
messages.error(doc.pk, "Archived version of document does not exist.")
|
||||
return
|
||||
|
||||
@@ -317,15 +314,7 @@ def check_sanity(
|
||||
messages = SanityCheckMessages()
|
||||
present_files = _build_present_files()
|
||||
|
||||
documents = Document.global_objects.only(
|
||||
"pk",
|
||||
"filename",
|
||||
"mime_type",
|
||||
"checksum",
|
||||
"archive_checksum",
|
||||
"archive_filename",
|
||||
"content",
|
||||
).iterator(chunk_size=500)
|
||||
documents = Document.global_objects.all()
|
||||
for doc in iter_wrapper(documents):
|
||||
_check_document(doc, messages, present_files)
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ logger = logging.getLogger("paperless.serializers")
|
||||
|
||||
|
||||
# https://www.django-rest-framework.org/api-guide/serializers/#example
|
||||
class DynamicFieldsModelSerializer(serializers.ModelSerializer[Any]):
|
||||
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
A ModelSerializer that takes an additional `fields` argument that
|
||||
controls which fields should be displayed.
|
||||
@@ -121,7 +121,7 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer[Any]):
|
||||
self.fields.pop(field_name)
|
||||
|
||||
|
||||
class MatchingModelSerializer(serializers.ModelSerializer[Any]):
|
||||
class MatchingModelSerializer(serializers.ModelSerializer):
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
def get_slug(self, obj) -> str:
|
||||
@@ -261,7 +261,7 @@ class SetPermissionsSerializer(serializers.DictField):
|
||||
|
||||
class OwnedObjectSerializer(
|
||||
SerializerWithPerms,
|
||||
serializers.ModelSerializer[Any],
|
||||
serializers.ModelSerializer,
|
||||
SetPermissionsMixin,
|
||||
):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
@@ -469,7 +469,7 @@ class OwnedObjectSerializer(
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class OwnedObjectListSerializer(serializers.ListSerializer[Any]):
|
||||
class OwnedObjectListSerializer(serializers.ListSerializer):
|
||||
def to_representation(self, documents):
|
||||
self.child.context["shared_object_pks"] = self.child.get_shared_object_pks(
|
||||
documents,
|
||||
@@ -682,27 +682,27 @@ class TagSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
||||
return super().validate(attrs)
|
||||
|
||||
|
||||
class CorrespondentField(serializers.PrimaryKeyRelatedField[Correspondent]):
|
||||
class CorrespondentField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return Correspondent.objects.all()
|
||||
|
||||
|
||||
class TagsField(serializers.PrimaryKeyRelatedField[Tag]):
|
||||
class TagsField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return Tag.objects.all()
|
||||
|
||||
|
||||
class DocumentTypeField(serializers.PrimaryKeyRelatedField[DocumentType]):
|
||||
class DocumentTypeField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return DocumentType.objects.all()
|
||||
|
||||
|
||||
class StoragePathField(serializers.PrimaryKeyRelatedField[StoragePath]):
|
||||
class StoragePathField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return StoragePath.objects.all()
|
||||
|
||||
|
||||
class CustomFieldSerializer(serializers.ModelSerializer[CustomField]):
|
||||
class CustomFieldSerializer(serializers.ModelSerializer):
|
||||
data_type = serializers.ChoiceField(
|
||||
choices=CustomField.FieldDataType,
|
||||
read_only=False,
|
||||
@@ -816,7 +816,7 @@ def validate_documentlink_targets(user, doc_ids):
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInstance]):
|
||||
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
|
||||
field = serializers.PrimaryKeyRelatedField(queryset=CustomField.objects.all())
|
||||
value = ReadWriteSerializerMethodField(allow_null=True)
|
||||
|
||||
@@ -922,14 +922,14 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInsta
|
||||
]
|
||||
|
||||
|
||||
class BasicUserSerializer(serializers.ModelSerializer[User]):
|
||||
class BasicUserSerializer(serializers.ModelSerializer):
|
||||
# Different than paperless.serializers.UserSerializer
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "username", "first_name", "last_name"]
|
||||
|
||||
|
||||
class NotesSerializer(serializers.ModelSerializer[Note]):
|
||||
class NotesSerializer(serializers.ModelSerializer):
|
||||
user = BasicUserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -1256,7 +1256,7 @@ class DocumentSerializer(
|
||||
list_serializer_class = OwnedObjectListSerializer
|
||||
|
||||
|
||||
class SearchResultListSerializer(serializers.ListSerializer[Document]):
|
||||
class SearchResultListSerializer(serializers.ListSerializer):
|
||||
def to_representation(self, hits):
|
||||
document_ids = [hit["id"] for hit in hits]
|
||||
# Fetch all Document objects in the list in one SQL query.
|
||||
@@ -1313,7 +1313,7 @@ class SearchResultSerializer(DocumentSerializer):
|
||||
list_serializer_class = SearchResultListSerializer
|
||||
|
||||
|
||||
class SavedViewFilterRuleSerializer(serializers.ModelSerializer[SavedViewFilterRule]):
|
||||
class SavedViewFilterRuleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SavedViewFilterRule
|
||||
fields = ["rule_type", "value"]
|
||||
@@ -2401,7 +2401,7 @@ class StoragePathSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class UiSettingsViewSerializer(serializers.ModelSerializer[UiSettings]):
|
||||
class UiSettingsViewSerializer(serializers.ModelSerializer):
|
||||
settings = serializers.DictField(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
@@ -2760,7 +2760,7 @@ class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
|
||||
return attrs
|
||||
|
||||
|
||||
class WorkflowTriggerSerializer(serializers.ModelSerializer[WorkflowTrigger]):
|
||||
class WorkflowTriggerSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(required=False, allow_null=True)
|
||||
sources = fields.MultipleChoiceField(
|
||||
choices=WorkflowTrigger.DocumentSourceChoices.choices,
|
||||
@@ -2870,7 +2870,7 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer[WorkflowTrigger]):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class WorkflowActionEmailSerializer(serializers.ModelSerializer[WorkflowActionEmail]):
|
||||
class WorkflowActionEmailSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(allow_null=True, required=False)
|
||||
|
||||
class Meta:
|
||||
@@ -2884,9 +2884,7 @@ class WorkflowActionEmailSerializer(serializers.ModelSerializer[WorkflowActionEm
|
||||
]
|
||||
|
||||
|
||||
class WorkflowActionWebhookSerializer(
|
||||
serializers.ModelSerializer[WorkflowActionWebhook],
|
||||
):
|
||||
class WorkflowActionWebhookSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(allow_null=True, required=False)
|
||||
|
||||
def validate_url(self, url):
|
||||
@@ -2907,7 +2905,7 @@ class WorkflowActionWebhookSerializer(
|
||||
]
|
||||
|
||||
|
||||
class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]):
|
||||
class WorkflowActionSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(required=False, allow_null=True)
|
||||
assign_correspondent = CorrespondentField(allow_null=True, required=False)
|
||||
assign_tags = TagsField(many=True, allow_null=True, required=False)
|
||||
@@ -3029,7 +3027,7 @@ class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]):
|
||||
return attrs
|
||||
|
||||
|
||||
class WorkflowSerializer(serializers.ModelSerializer[Workflow]):
|
||||
class WorkflowSerializer(serializers.ModelSerializer):
|
||||
order = serializers.IntegerField(required=False)
|
||||
|
||||
triggers = WorkflowTriggerSerializer(many=True)
|
||||
|
||||
@@ -1328,7 +1328,7 @@ class PreConsumeTestCase(DirectoriesMixin, GetConsumerMixin, TestCase):
|
||||
environment = args[1]
|
||||
|
||||
self.assertEqual(command[0], script.name)
|
||||
self.assertEqual(command[1], str(self.test_file))
|
||||
self.assertEqual(len(command), 1)
|
||||
|
||||
subset = {
|
||||
"DOCUMENT_SOURCE_PATH": str(c.input_doc.original_file),
|
||||
@@ -1478,11 +1478,7 @@ class PostConsumeTestCase(DirectoriesMixin, GetConsumerMixin, TestCase):
|
||||
environment = args[1]
|
||||
|
||||
self.assertEqual(command[0], script.name)
|
||||
self.assertEqual(command[1], str(doc.pk))
|
||||
self.assertEqual(command[5], f"/api/documents/{doc.pk}/download/")
|
||||
self.assertEqual(command[6], f"/api/documents/{doc.pk}/thumb/")
|
||||
self.assertEqual(command[7], "my_bank")
|
||||
self.assertCountEqual(command[8].split(","), ["a", "b"])
|
||||
self.assertEqual(len(command), 1)
|
||||
|
||||
subset = {
|
||||
"DOCUMENT_ID": str(doc.pk),
|
||||
|
||||
@@ -291,7 +291,7 @@ class IndexView(TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
class PassUserMixin(GenericAPIView[Any]):
|
||||
class PassUserMixin(GenericAPIView):
|
||||
"""
|
||||
Pass a user object to serializer
|
||||
"""
|
||||
@@ -457,10 +457,7 @@ class PermissionsAwareDocumentCountMixin(BulkPermissionMixin, PassUserMixin):
|
||||
|
||||
|
||||
@extend_schema_view(**generate_object_with_permissions_schema(CorrespondentSerializer))
|
||||
class CorrespondentViewSet(
|
||||
PermissionsAwareDocumentCountMixin,
|
||||
ModelViewSet[Correspondent],
|
||||
):
|
||||
class CorrespondentViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
|
||||
model = Correspondent
|
||||
|
||||
queryset = Correspondent.objects.select_related("owner").order_by(Lower("name"))
|
||||
@@ -497,7 +494,7 @@ class CorrespondentViewSet(
|
||||
|
||||
|
||||
@extend_schema_view(**generate_object_with_permissions_schema(TagSerializer))
|
||||
class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]):
|
||||
class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
|
||||
model = Tag
|
||||
serializer_class = TagSerializer
|
||||
document_count_through = Document.tags.through
|
||||
@@ -576,10 +573,7 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]):
|
||||
|
||||
|
||||
@extend_schema_view(**generate_object_with_permissions_schema(DocumentTypeSerializer))
|
||||
class DocumentTypeViewSet(
|
||||
PermissionsAwareDocumentCountMixin,
|
||||
ModelViewSet[DocumentType],
|
||||
):
|
||||
class DocumentTypeViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
|
||||
model = DocumentType
|
||||
|
||||
queryset = DocumentType.objects.select_related("owner").order_by(Lower("name"))
|
||||
@@ -814,7 +808,7 @@ class DocumentViewSet(
|
||||
UpdateModelMixin,
|
||||
DestroyModelMixin,
|
||||
ListModelMixin,
|
||||
GenericViewSet[Document],
|
||||
GenericViewSet,
|
||||
):
|
||||
model = Document
|
||||
queryset = Document.objects.all()
|
||||
@@ -1254,10 +1248,7 @@ class DocumentViewSet(
|
||||
),
|
||||
)
|
||||
def suggestions(self, request, pk=None):
|
||||
doc = get_object_or_404(
|
||||
Document.objects.select_related("owner").prefetch_related("versions"),
|
||||
pk=pk,
|
||||
)
|
||||
doc = get_object_or_404(Document.objects.select_related("owner"), pk=pk)
|
||||
if request.user is not None and not has_perms_owner_aware(
|
||||
request.user,
|
||||
"view_document",
|
||||
@@ -1961,7 +1952,7 @@ class ChatStreamingSerializer(serializers.Serializer):
|
||||
],
|
||||
name="dispatch",
|
||||
)
|
||||
class ChatStreamingView(GenericAPIView[Any]):
|
||||
class ChatStreamingView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = ChatStreamingSerializer
|
||||
|
||||
@@ -2287,7 +2278,7 @@ class LogViewSet(ViewSet):
|
||||
|
||||
|
||||
@extend_schema_view(**generate_object_with_permissions_schema(SavedViewSerializer))
|
||||
class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet[SavedView]):
|
||||
class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet):
|
||||
model = SavedView
|
||||
|
||||
queryset = SavedView.objects.select_related("owner").prefetch_related(
|
||||
@@ -2765,7 +2756,7 @@ class RemovePasswordDocumentsView(DocumentOperationPermissionMixin):
|
||||
},
|
||||
),
|
||||
)
|
||||
class PostDocumentView(GenericAPIView[Any]):
|
||||
class PostDocumentView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = PostDocumentSerializer
|
||||
parser_classes = (parsers.MultiPartParser,)
|
||||
@@ -2886,7 +2877,7 @@ class PostDocumentView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class SelectionDataView(GenericAPIView[Any]):
|
||||
class SelectionDataView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = DocumentListSerializer
|
||||
parser_classes = (parsers.MultiPartParser, parsers.JSONParser)
|
||||
@@ -2990,7 +2981,7 @@ class SelectionDataView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class SearchAutoCompleteView(GenericAPIView[Any]):
|
||||
class SearchAutoCompleteView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, format=None):
|
||||
@@ -3271,7 +3262,7 @@ class GlobalSearchView(PassUserMixin):
|
||||
},
|
||||
),
|
||||
)
|
||||
class StatisticsView(GenericAPIView[Any]):
|
||||
class StatisticsView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, format=None):
|
||||
@@ -3373,7 +3364,7 @@ class StatisticsView(GenericAPIView[Any]):
|
||||
)
|
||||
|
||||
|
||||
class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
|
||||
class BulkDownloadView(DocumentSelectionMixin, GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = BulkDownloadSerializer
|
||||
parser_classes = (parsers.JSONParser,)
|
||||
@@ -3426,7 +3417,7 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
|
||||
|
||||
|
||||
@extend_schema_view(**generate_object_with_permissions_schema(StoragePathSerializer))
|
||||
class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[StoragePath]):
|
||||
class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
|
||||
model = StoragePath
|
||||
|
||||
queryset = StoragePath.objects.select_related("owner").order_by(
|
||||
@@ -3490,7 +3481,7 @@ class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Storag
|
||||
return Response(result)
|
||||
|
||||
|
||||
class UiSettingsView(GenericAPIView[Any]):
|
||||
class UiSettingsView(GenericAPIView):
|
||||
queryset = UiSettings.objects.all()
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
serializer_class = UiSettingsViewSerializer
|
||||
@@ -3588,7 +3579,7 @@ class UiSettingsView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class RemoteVersionView(GenericAPIView[Any]):
|
||||
class RemoteVersionView(GenericAPIView):
|
||||
cache_key = "remote_version_view_latest_release"
|
||||
|
||||
def get(self, request, format=None):
|
||||
@@ -3665,7 +3656,7 @@ class RemoteVersionView(GenericAPIView[Any]):
|
||||
),
|
||||
],
|
||||
)
|
||||
class TasksViewSet(ReadOnlyModelViewSet[PaperlessTask]):
|
||||
class TasksViewSet(ReadOnlyModelViewSet):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
serializer_class = TasksViewSerializer
|
||||
filter_backends = (
|
||||
@@ -3739,7 +3730,7 @@ class TasksViewSet(ReadOnlyModelViewSet[PaperlessTask]):
|
||||
)
|
||||
|
||||
|
||||
class ShareLinkViewSet(PassUserMixin, ModelViewSet[ShareLink]):
|
||||
class ShareLinkViewSet(ModelViewSet, PassUserMixin):
|
||||
model = ShareLink
|
||||
|
||||
queryset = ShareLink.objects.all()
|
||||
@@ -3756,7 +3747,7 @@ class ShareLinkViewSet(PassUserMixin, ModelViewSet[ShareLink]):
|
||||
ordering_fields = ("created", "expiration", "document")
|
||||
|
||||
|
||||
class ShareLinkBundleViewSet(PassUserMixin, ModelViewSet[ShareLinkBundle]):
|
||||
class ShareLinkBundleViewSet(ModelViewSet, PassUserMixin):
|
||||
model = ShareLinkBundle
|
||||
|
||||
queryset = ShareLinkBundle.objects.all()
|
||||
@@ -4113,7 +4104,7 @@ class BulkEditObjectsView(PassUserMixin):
|
||||
return Response({"result": "OK"})
|
||||
|
||||
|
||||
class WorkflowTriggerViewSet(ModelViewSet[WorkflowTrigger]):
|
||||
class WorkflowTriggerViewSet(ModelViewSet):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
|
||||
serializer_class = WorkflowTriggerSerializer
|
||||
@@ -4131,7 +4122,7 @@ class WorkflowTriggerViewSet(ModelViewSet[WorkflowTrigger]):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WorkflowActionViewSet(ModelViewSet[WorkflowAction]):
|
||||
class WorkflowActionViewSet(ModelViewSet):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
|
||||
serializer_class = WorkflowActionSerializer
|
||||
@@ -4156,7 +4147,7 @@ class WorkflowActionViewSet(ModelViewSet[WorkflowAction]):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WorkflowViewSet(ModelViewSet[Workflow]):
|
||||
class WorkflowViewSet(ModelViewSet):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
|
||||
serializer_class = WorkflowSerializer
|
||||
@@ -4174,7 +4165,7 @@ class WorkflowViewSet(ModelViewSet[Workflow]):
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[CustomField]):
|
||||
class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
|
||||
serializer_class = CustomFieldSerializer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ class PaperlessAuthTokenSerializer(AuthTokenSerializer):
|
||||
return attrs
|
||||
|
||||
|
||||
class UserSerializer(PasswordValidationMixin, serializers.ModelSerializer[User]):
|
||||
class UserSerializer(PasswordValidationMixin, serializers.ModelSerializer):
|
||||
password = ObfuscatedPasswordField(required=False)
|
||||
user_permissions = serializers.SlugRelatedField(
|
||||
many=True,
|
||||
@@ -142,7 +142,7 @@ class UserSerializer(PasswordValidationMixin, serializers.ModelSerializer[User])
|
||||
return user
|
||||
|
||||
|
||||
class GroupSerializer(serializers.ModelSerializer[Group]):
|
||||
class GroupSerializer(serializers.ModelSerializer):
|
||||
permissions = serializers.SlugRelatedField(
|
||||
many=True,
|
||||
queryset=Permission.objects.exclude(content_type__app_label="admin"),
|
||||
@@ -158,7 +158,7 @@ class GroupSerializer(serializers.ModelSerializer[Group]):
|
||||
)
|
||||
|
||||
|
||||
class SocialAccountSerializer(serializers.ModelSerializer[SocialAccount]):
|
||||
class SocialAccountSerializer(serializers.ModelSerializer):
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -176,7 +176,7 @@ class SocialAccountSerializer(serializers.ModelSerializer[SocialAccount]):
|
||||
return "Unknown App"
|
||||
|
||||
|
||||
class ProfileSerializer(PasswordValidationMixin, serializers.ModelSerializer[User]):
|
||||
class ProfileSerializer(PasswordValidationMixin, serializers.ModelSerializer):
|
||||
email = serializers.EmailField(allow_blank=True, required=False)
|
||||
password = ObfuscatedPasswordField(required=False, allow_null=False)
|
||||
auth_token = serializers.SlugRelatedField(read_only=True, slug_field="key")
|
||||
@@ -209,9 +209,7 @@ class ProfileSerializer(PasswordValidationMixin, serializers.ModelSerializer[Use
|
||||
)
|
||||
|
||||
|
||||
class ApplicationConfigurationSerializer(
|
||||
serializers.ModelSerializer[ApplicationConfiguration],
|
||||
):
|
||||
class ApplicationConfigurationSerializer(serializers.ModelSerializer):
|
||||
user_args = serializers.JSONField(binary=True, allow_null=True)
|
||||
barcode_tag_mapping = serializers.JSONField(binary=True, allow_null=True)
|
||||
llm_api_key = ObfuscatedPasswordField(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from allauth.mfa import signals
|
||||
from allauth.mfa.adapter import get_adapter as get_mfa_adapter
|
||||
@@ -115,7 +114,7 @@ class FaviconView(View):
|
||||
return HttpResponseNotFound("favicon.ico not found")
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet[User]):
|
||||
class UserViewSet(ModelViewSet):
|
||||
_BOOL_NOT_PROVIDED = object()
|
||||
model = User
|
||||
|
||||
@@ -217,7 +216,7 @@ class UserViewSet(ModelViewSet[User]):
|
||||
return HttpResponseNotFound("TOTP not found")
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet[Group]):
|
||||
class GroupViewSet(ModelViewSet):
|
||||
model = Group
|
||||
|
||||
queryset = Group.objects.order_by(Lower("name"))
|
||||
@@ -230,7 +229,7 @@ class GroupViewSet(ModelViewSet[Group]):
|
||||
ordering_fields = ("name",)
|
||||
|
||||
|
||||
class ProfileView(GenericAPIView[Any]):
|
||||
class ProfileView(GenericAPIView):
|
||||
"""
|
||||
User profile view, only available when logged in
|
||||
"""
|
||||
@@ -289,7 +288,7 @@ class ProfileView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class TOTPView(GenericAPIView[Any]):
|
||||
class TOTPView(GenericAPIView):
|
||||
"""
|
||||
TOTP views
|
||||
"""
|
||||
@@ -369,7 +368,7 @@ class TOTPView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class GenerateAuthTokenView(GenericAPIView[Any]):
|
||||
class GenerateAuthTokenView(GenericAPIView):
|
||||
"""
|
||||
Generates (or re-generates) an auth token, requires a logged in user
|
||||
unlike the default DRF endpoint
|
||||
@@ -398,7 +397,7 @@ class GenerateAuthTokenView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class ApplicationConfigurationViewSet(ModelViewSet[ApplicationConfiguration]):
|
||||
class ApplicationConfigurationViewSet(ModelViewSet):
|
||||
model = ApplicationConfiguration
|
||||
|
||||
queryset = ApplicationConfiguration.objects
|
||||
@@ -451,7 +450,7 @@ class ApplicationConfigurationViewSet(ModelViewSet[ApplicationConfiguration]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class DisconnectSocialAccountView(GenericAPIView[Any]):
|
||||
class DisconnectSocialAccountView(GenericAPIView):
|
||||
"""
|
||||
Disconnects a social account provider from the user account
|
||||
"""
|
||||
@@ -477,7 +476,7 @@ class DisconnectSocialAccountView(GenericAPIView[Any]):
|
||||
},
|
||||
),
|
||||
)
|
||||
class SocialAccountProvidersView(GenericAPIView[Any]):
|
||||
class SocialAccountProvidersView(GenericAPIView):
|
||||
"""
|
||||
List of social account providers
|
||||
"""
|
||||
|
||||
@@ -57,7 +57,7 @@ class MailAccountSerializer(OwnedObjectSerializer):
|
||||
return instance
|
||||
|
||||
|
||||
class AccountField(serializers.PrimaryKeyRelatedField[MailAccount]):
|
||||
class AccountField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return MailAccount.objects.all().order_by("-id")
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import datetime
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.http import HttpResponseForbidden
|
||||
@@ -66,7 +65,7 @@ from paperless_mail.tasks import process_mail_accounts
|
||||
},
|
||||
),
|
||||
)
|
||||
class MailAccountViewSet(PassUserMixin, ModelViewSet[MailAccount]):
|
||||
class MailAccountViewSet(ModelViewSet, PassUserMixin):
|
||||
model = MailAccount
|
||||
|
||||
queryset = MailAccount.objects.all().order_by("pk")
|
||||
@@ -160,7 +159,7 @@ class MailAccountViewSet(PassUserMixin, ModelViewSet[MailAccount]):
|
||||
return Response({"result": "OK"})
|
||||
|
||||
|
||||
class ProcessedMailViewSet(PassUserMixin, ReadOnlyModelViewSet[ProcessedMail]):
|
||||
class ProcessedMailViewSet(ReadOnlyModelViewSet, PassUserMixin):
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
serializer_class = ProcessedMailSerializer
|
||||
pagination_class = StandardPagination
|
||||
@@ -188,7 +187,7 @@ class ProcessedMailViewSet(PassUserMixin, ReadOnlyModelViewSet[ProcessedMail]):
|
||||
return Response({"result": "OK", "deleted_mail_ids": mail_ids})
|
||||
|
||||
|
||||
class MailRuleViewSet(PassUserMixin, ModelViewSet[MailRule]):
|
||||
class MailRuleViewSet(ModelViewSet, PassUserMixin):
|
||||
model = MailRule
|
||||
|
||||
queryset = MailRule.objects.all().order_by("order")
|
||||
@@ -204,7 +203,7 @@ class MailRuleViewSet(PassUserMixin, ModelViewSet[MailRule]):
|
||||
responses={200: None},
|
||||
),
|
||||
)
|
||||
class OauthCallbackView(GenericAPIView[Any]):
|
||||
class OauthCallbackView(GenericAPIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, format=None):
|
||||
|
||||
18
uv.lock
generated
18
uv.lock
generated
@@ -875,15 +875,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.2.13"
|
||||
version = "5.2.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "sqlparse", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/c5/c69e338eb2959f641045802e5ea87ca4bf5ac90c5fd08953ca10742fad51/django-5.2.13.tar.gz", hash = "sha256:a31589db5188d074c63f0945c3888fad104627dfcc236fb2b97f71f89da33bc4", size = 10890368, upload-time = "2026-04-07T14:02:15.072Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b9445fc0695b03746f355c05b2eecc54c34e05198c686f4fc4406b722b52/django-5.2.12.tar.gz", hash = "sha256:6b809af7165c73eff5ce1c87fdae75d4da6520d6667f86401ecf55b681eb1eeb", size = 10860574, upload-time = "2026-03-03T13:56:05.509Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/b1/51ab36b2eefcf8cdb9338c7188668a157e29e30306bfc98a379704c9e10d/django-5.2.13-py3-none-any.whl", hash = "sha256:5788fce61da23788a8ce6f02583765ab060d396720924789f97fa42119d37f7a", size = 8310982, upload-time = "2026-04-07T14:02:08.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/32/4b144e125678efccf5d5b61581de1c4088d6b0286e46096e3b8de0d556c8/django-5.2.12-py3-none-any.whl", hash = "sha256:4853482f395c3a151937f6991272540fcbf531464f254a347bf7c89f53c8cff7", size = 8310245, upload-time = "2026-03-03T13:56:01.174Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3014,7 +3014,7 @@ requires-dist = [
|
||||
{ name = "channels-redis", specifier = "~=4.2" },
|
||||
{ name = "concurrent-log-handler", specifier = "~=0.9.25" },
|
||||
{ name = "dateparser", specifier = "~=1.2" },
|
||||
{ name = "django", specifier = "~=5.2.13" },
|
||||
{ name = "django", specifier = "~=5.2.10" },
|
||||
{ name = "django-allauth", extras = ["mfa", "socialaccount"], specifier = "~=65.15.0" },
|
||||
{ name = "django-auditlog", specifier = "~=3.4.1" },
|
||||
{ name = "django-cachalot", specifier = "~=2.9.0" },
|
||||
@@ -3087,7 +3087,7 @@ dev = [
|
||||
{ name = "faker", specifier = "~=40.12.0" },
|
||||
{ name = "imagehash" },
|
||||
{ name = "prek", specifier = "~=0.3.0" },
|
||||
{ name = "pytest", specifier = "~=9.0.3" },
|
||||
{ name = "pytest", specifier = "~=9.0.0" },
|
||||
{ name = "pytest-cov", specifier = "~=7.1.0" },
|
||||
{ name = "pytest-django", specifier = "~=4.12.0" },
|
||||
{ name = "pytest-env", specifier = "~=1.6.0" },
|
||||
@@ -3110,7 +3110,7 @@ testing = [
|
||||
{ name = "factory-boy", specifier = "~=3.3.1" },
|
||||
{ name = "faker", specifier = "~=40.12.0" },
|
||||
{ name = "imagehash" },
|
||||
{ name = "pytest", specifier = "~=9.0.3" },
|
||||
{ name = "pytest", specifier = "~=9.0.0" },
|
||||
{ name = "pytest-cov", specifier = "~=7.1.0" },
|
||||
{ name = "pytest-django", specifier = "~=4.12.0" },
|
||||
{ name = "pytest-env", specifier = "~=1.6.0" },
|
||||
@@ -3769,7 +3769,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
version = "9.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
@@ -3777,9 +3777,9 @@ dependencies = [
|
||||
{ name = "pluggy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user