Compare commits

...

4 Commits

6 changed files with 141 additions and 3 deletions

View File

@@ -967,6 +967,8 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
getattr(request, "user", None) if request is not None else None,
doc_ids,
)
elif field.data_type == CustomField.FieldDataType.DATE:
data["value"] = serializers.DateField().to_internal_value(data["value"])
return data

View File

@@ -1425,3 +1425,41 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.data["results"]
self.assertEqual(results[0]["document_count"], 0)
def test_patch_document_invalid_date_custom_field_returns_validation_error(self):
"""
GIVEN:
- A date custom field
- A document
WHEN:
- Patching the document with a date string in the wrong format
THEN:
- HTTP 400 is returned instead of an internal server error
- No custom field instance is created
"""
cf_date = CustomField.objects.create(
name="datefield",
data_type=CustomField.FieldDataType.DATE,
)
doc = Document.objects.create(
title="Doc",
checksum="123",
mime_type="application/pdf",
)
response = self.client.patch(
f"/api/documents/{doc.pk}/",
{
"custom_fields": [
{
"field": cf_date.pk,
"value": "10.03.2026",
},
],
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("custom_fields", response.data)
self.assertEqual(CustomFieldInstance.objects.count(), 0)

View File

@@ -1087,6 +1087,43 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(len(response.data["all"]), 50)
self.assertCountEqual(response.data["all"], [d.id for d in docs])
def test_default_ordering_uses_id_as_tiebreaker(self):
"""
GIVEN:
- Documents sharing the same created date
WHEN:
- API request for documents without an explicit ordering
THEN:
- Results are correctly ordered by created > id
"""
older_doc = Document.objects.create(
checksum="older",
content="older",
created=date(2024, 1, 1),
)
first_same_date_doc = Document.objects.create(
checksum="same-date-1",
content="same-date-1",
created=date(2024, 1, 2),
)
second_same_date_doc = Document.objects.create(
checksum="same-date-2",
content="same-date-2",
created=date(2024, 1, 2),
)
response = self.client.get("/api/documents/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
[result["id"] for result in response.data["results"]],
[
second_same_date_doc.id,
first_same_date_doc.id,
older_doc.id,
],
)
def test_statistics(self):
doc1 = Document.objects.create(
title="none1",
@@ -2953,6 +2990,58 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED)
self.assertEqual(create_resp.data["document"], doc.pk)
def test_share_link_update_methods_not_allowed(self):
"""
GIVEN:
- An existing share link
WHEN:
- PUT and PATCH requests are made to its detail endpoint
THEN:
- The API rejects them with 405 and the link is unchanged
"""
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="share link content",
)
expiration = timezone.now() + timedelta(days=7)
create_resp = self.client.post(
"/api/share_links/",
data={
"document": doc.pk,
"expiration": expiration.isoformat(),
"file_version": ShareLink.FileVersion.ORIGINAL,
},
format="json",
)
self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED)
share_link_id = create_resp.data["id"]
patch_resp = self.client.patch(
f"/api/share_links/{share_link_id}/",
data={
"expiration": None,
"file_version": ShareLink.FileVersion.ARCHIVE,
},
format="json",
)
self.assertEqual(patch_resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
put_resp = self.client.put(
f"/api/share_links/{share_link_id}/",
data={
"document": doc.pk,
"expiration": None,
"file_version": ShareLink.FileVersion.ARCHIVE,
},
format="json",
)
self.assertEqual(put_resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
share_link = ShareLink.objects.get(pk=share_link_id)
self.assertEqual(share_link.file_version, ShareLink.FileVersion.ORIGINAL)
self.assertIsNotNone(share_link.expiration)
def test_next_asn(self):
"""
GIVEN:

View File

@@ -76,6 +76,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from rest_framework.mixins import DestroyModelMixin
from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import RetrieveModelMixin
@@ -785,7 +786,7 @@ class DocumentViewSet(
def get_queryset(self):
return (
Document.objects.distinct()
.order_by("-created")
.order_by("-created", "-id")
.annotate(num_notes=Count("notes"))
.select_related("correspondent", "storage_path", "document_type", "owner")
.prefetch_related("tags", "custom_fields", "notes")
@@ -2702,7 +2703,14 @@ class TasksViewSet(ReadOnlyModelViewSet):
)
class ShareLinkViewSet(ModelViewSet, PassUserMixin):
class ShareLinkViewSet(
PassUserMixin,
CreateModelMixin,
RetrieveModelMixin,
DestroyModelMixin,
ListModelMixin,
GenericViewSet,
):
model = ShareLink
queryset = ShareLink.objects.all()

View File

@@ -472,6 +472,7 @@ class MailAccountHandler(LoggingMixin):
name=name,
defaults={
"match": name,
"matching_algorithm": Correspondent.MATCH_LITERAL,
},
)[0]
except DatabaseError as e:

View File

@@ -448,7 +448,7 @@ class TestMail(
c = handler._get_correspondent(message, rule)
self.assertIsNotNone(c)
self.assertEqual(c.name, "someone@somewhere.com")
self.assertEqual(c.matching_algorithm, MatchingModel.MATCH_ANY)
self.assertEqual(c.matching_algorithm, MatchingModel.MATCH_LITERAL)
self.assertEqual(c.match, "someone@somewhere.com")
c = handler._get_correspondent(message2, rule)
self.assertIsNotNone(c)