Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
d225db4db1 Chore(deps): Bump django-guardian in the django group
Bumps the django group with 1 update: [django-guardian](https://github.com/django-guardian/django-guardian).


Updates `django-guardian` from 3.1.2 to 3.1.3
- [Release notes](https://github.com/django-guardian/django-guardian/releases)
- [Commits](https://github.com/django-guardian/django-guardian/compare/3.1.2...3.1.3)

---
updated-dependencies:
- dependency-name: django-guardian
  dependency-version: 3.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: django
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 20:23:36 +00:00
5 changed files with 16 additions and 147 deletions

View File

@@ -192,8 +192,8 @@ The endpoint supports the following optional form fields:
- `tags`: Similar to correspondent. Specify this multiple times to
have multiple tags added to the document.
- `archive_serial_number`: An optional archive serial number to set.
- `custom_fields`: Either an array of custom field ids to assign (with an empty
value) to the document or an object mapping field id -> value.
- `custom_fields`: An array of custom field ids to assign (with an empty
value) to the document.
The endpoint will immediately return HTTP 200 if the document consumption
process was started successfully, with the UUID of the consumption task

View File

@@ -1668,8 +1668,9 @@ class PostDocumentSerializer(serializers.Serializer):
max_value=Document.ARCHIVE_SERIAL_NUMBER_MAX,
)
# Accept either a list of custom field ids or a dict mapping id -> value
custom_fields = serializers.JSONField(
custom_fields = serializers.PrimaryKeyRelatedField(
many=True,
queryset=CustomField.objects.all(),
label="Custom fields",
write_only=True,
required=False,
@@ -1726,60 +1727,11 @@ class PostDocumentSerializer(serializers.Serializer):
return None
def validate_custom_fields(self, custom_fields):
if not custom_fields:
if custom_fields:
return [custom_field.id for custom_field in custom_fields]
else:
return None
# Normalize single values to a list
if isinstance(custom_fields, int):
custom_fields = [custom_fields]
if isinstance(custom_fields, dict):
custom_field_serializer = CustomFieldInstanceSerializer()
normalized = {}
for field_id, value in custom_fields.items():
try:
field_id_int = int(field_id)
except (TypeError, ValueError):
raise serializers.ValidationError(
_("Custom field id must be an integer: %(id)s")
% {"id": field_id},
)
try:
field = CustomField.objects.get(id=field_id_int)
except CustomField.DoesNotExist:
raise serializers.ValidationError(
_("Custom field with id %(id)s does not exist")
% {"id": field_id_int},
)
custom_field_serializer.validate(
{
"field": field,
"value": value,
},
)
normalized[field_id_int] = value
return normalized
elif isinstance(custom_fields, list):
try:
ids = [int(i) for i in custom_fields]
except (TypeError, ValueError):
raise serializers.ValidationError(
_(
"Custom fields must be a list of integers or an object mapping ids to values.",
),
)
if CustomField.objects.filter(id__in=ids).count() != len(set(ids)):
raise serializers.ValidationError(
_("Some custom fields don't exist or were specified twice."),
)
return ids
raise serializers.ValidationError(
_(
"Custom fields must be a list of integers or an object mapping ids to values.",
),
)
# custom_fields_w_values handled via validate_custom_fields
def validate_created(self, created):
# support datetime format for created for backwards compatibility
if isinstance(created, datetime):

View File

@@ -1,5 +1,4 @@
import datetime
import json
import shutil
import tempfile
import uuid
@@ -1538,86 +1537,6 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
overrides.update(new_overrides)
self.assertEqual(overrides.custom_fields, {cf.id: None, cf2.id: 123})
def test_upload_with_custom_field_values(self):
"""
GIVEN: A document with a source file
WHEN: Upload the document with custom fields and values
THEN: Metadata is set correctly
"""
self.consume_file_mock.return_value = celery.result.AsyncResult(
id=str(uuid.uuid4()),
)
cf_string = CustomField.objects.create(
name="stringfield",
data_type=CustomField.FieldDataType.STRING,
)
cf_int = CustomField.objects.create(
name="intfield",
data_type=CustomField.FieldDataType.INT,
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{
"document": f,
"custom_fields": json.dumps(
{
str(cf_string.id): "a string",
str(cf_int.id): 123,
},
),
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.consume_file_mock.assert_called_once()
input_doc, overrides = self.get_last_consume_delay_call_args()
self.assertEqual(input_doc.original_file.name, "simple.pdf")
self.assertEqual(overrides.filename, "simple.pdf")
self.assertEqual(
overrides.custom_fields,
{cf_string.id: "a string", cf_int.id: 123},
)
def test_upload_with_custom_fields_errors(self):
"""
GIVEN: A document with a source file
WHEN: Upload the document with invalid custom fields payloads
THEN: The upload is rejected
"""
self.consume_file_mock.return_value = celery.result.AsyncResult(
id=str(uuid.uuid4()),
)
error_payloads = [
# Non-integer key in mapping
{"custom_fields": json.dumps({"abc": "a string"})},
# List with non-integer entry
{"custom_fields": json.dumps(["abc"])},
# Nonexistent id in mapping
{"custom_fields": json.dumps({99999999: "a string"})},
# Nonexistent id in list
{"custom_fields": json.dumps([99999999])},
# Invalid type (JSON string, not list/dict/int)
{"custom_fields": json.dumps("not-a-supported-structure")},
]
for payload in error_payloads:
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
data = {"document": f, **payload}
response = self.client.post(
"/api/documents/post_document/",
data,
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.consume_file_mock.assert_not_called()
def test_upload_with_webui_source(self):
"""
GIVEN: A document with a source file

View File

@@ -1497,7 +1497,7 @@ class PostDocumentView(GenericAPIView):
title = serializer.validated_data.get("title")
created = serializer.validated_data.get("created")
archive_serial_number = serializer.validated_data.get("archive_serial_number")
cf = serializer.validated_data.get("custom_fields")
custom_field_ids = serializer.validated_data.get("custom_fields")
from_webui = serializer.validated_data.get("from_webui")
t = int(mktime(datetime.now().timetuple()))
@@ -1516,11 +1516,6 @@ class PostDocumentView(GenericAPIView):
source=DocumentSource.WebUI if from_webui else DocumentSource.ApiUpload,
original_file=temp_file_path,
)
custom_fields = None
if isinstance(cf, dict) and cf:
custom_fields = cf
elif isinstance(cf, list) and cf:
custom_fields = dict.fromkeys(cf, None)
input_doc_overrides = DocumentMetadataOverrides(
filename=doc_name,
title=title,
@@ -1531,7 +1526,10 @@ class PostDocumentView(GenericAPIView):
created=created,
asn=archive_serial_number,
owner_id=request.user.id,
custom_fields=custom_fields,
# TODO: set values
custom_fields={cf_id: None for cf_id in custom_field_ids}
if custom_field_ids
else None,
)
async_task = consume_file.delay(

6
uv.lock generated
View File

@@ -782,15 +782,15 @@ wheels = [
[[package]]
name = "django-guardian"
version = "3.1.2"
version = "3.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/ac/5a8f7301c0181ee9020e020a4fa519f9851726b8fd3c1177656c1f5a1be0/django_guardian-3.1.2.tar.gz", hash = "sha256:6fc93b55e5eacd1a062a959c5578b433d999a286742aa3e7e713c71046813538", size = 93422, upload-time = "2025-09-08T15:43:51.361Z" }
sdist = { url = "https://files.pythonhosted.org/packages/81/d3/436a44c7688fce1a978224c349ba66c95bf9103d548596b7a2694fd58c03/django_guardian-3.1.3.tar.gz", hash = "sha256:12b5e66c18c97088b0adfa033ab14be68c321c170fd3ec438898271f00a71699", size = 93571, upload-time = "2025-09-10T08:36:23.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/50/3a4a891f809c9865d30864f59f993f9535b18e3935dfad278c9682fc537f/django_guardian-3.1.2-py3-none-any.whl", hash = "sha256:6c10f88d0b7efd171ae65d7ac487a666f41eb373c6f94d80016b1a19bdfbf212", size = 127451, upload-time = "2025-09-08T15:43:49.987Z" },
{ url = "https://files.pythonhosted.org/packages/83/fc/6fd7b8bc7c52cbbfd1714673cfd28ff0b3fae32265c52d492ec0dee22cb8/django_guardian-3.1.3-py3-none-any.whl", hash = "sha256:90e28b40eea65c326a3a961908cc300f9e1cd69b74e88d38317a9befa167b71c", size = 127687, upload-time = "2025-09-10T08:36:22.533Z" },
]
[[package]]