Automatically prune removed options from existing instances

This commit is contained in:
shamoon 2024-11-16 09:39:20 -08:00
parent 233212d198
commit e08fd79265
3 changed files with 93 additions and 17 deletions

View File

@ -34,7 +34,7 @@ from documents.settings import EXPORTER_ARCHIVE_NAME
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
from documents.settings import EXPORTER_FILE_NAME
from documents.settings import EXPORTER_THUMBNAIL_NAME
from documents.signals.handlers import update_cf_instance_documents
from documents.signals.handlers import check_paths_and_prune_custom_fields
from documents.signals.handlers import update_filename_and_move_files
from documents.utils import copy_file_with_basic_stats
from paperless import version
@ -262,7 +262,7 @@ class Command(CryptMixin, BaseCommand):
),
disable_signal(
post_save,
receiver=update_cf_instance_documents,
receiver=check_paths_and_prune_custom_fields,
sender=CustomField,
),
):

View File

@ -367,21 +367,6 @@ class CannotMoveFilesException(Exception):
pass
# should be disabled in /src/documents/management/commands/document_importer.py handle
@receiver(models.signals.post_save, sender=CustomField)
def update_cf_instance_documents(sender, instance: CustomField, **kwargs):
"""
'Select' custom field instances get their end-user value (e.g. in file names) from the select_options in extra_data,
which is contained in the custom field itself. So when the field is changed, we (may) need to update the file names
of all documents that have this custom field.
"""
if (
instance.data_type == CustomField.FieldDataType.SELECT
): # Only select fields, for now
for cf_instance in instance.fields.all():
update_filename_and_move_files(sender, cf_instance)
# should be disabled in /src/documents/management/commands/document_importer.py handle
@receiver(models.signals.post_save, sender=CustomFieldInstance)
@receiver(models.signals.m2m_changed, sender=Document.tags.through)
@ -520,6 +505,34 @@ def update_filename_and_move_files(
)
# should be disabled in /src/documents/management/commands/document_importer.py handle
@receiver(models.signals.post_save, sender=CustomField)
def check_paths_and_prune_custom_fields(sender, instance: CustomField, **kwargs):
"""
When a custom field is updated:
1. 'Select' custom field instances get their end-user value (e.g. in file names) from the select_options in extra_data,
which is contained in the custom field itself. So when the field is changed, we (may) need to update the file names
of all documents that have this custom field.
2. If a 'Select' field option was removed, we need to nullify the custom field instances that have the option.
"""
if (
instance.data_type == CustomField.FieldDataType.SELECT
): # Only select fields, for now
for cf_instance in instance.fields.all():
options = instance.extra_data.get("select_options", [])
try:
next(
option["label"]
for option in options
if option["id"] == cf_instance.value
)
except StopIteration:
# The value of this custom field instance is not in the select options anymore
cf_instance.value_select = None
cf_instance.save()
update_filename_and_move_files(sender, cf_instance)
def set_log_entry(sender, document: Document, logging_group=None, **kwargs):
ct = ContentType.objects.get(model="document")
user = User.objects.get(username="consumer")

View File

@ -209,6 +209,69 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
],
)
def test_custom_field_select_options_pruned(self):
"""
GIVEN:
- Select custom field exists and document instance with one of the options
WHEN:
- API request to remove an option from the select field
THEN:
- The option is removed from the field
- The option is removed from the document instance
"""
custom_field_select = CustomField.objects.create(
name="Select Field",
data_type=CustomField.FieldDataType.SELECT,
extra_data={
"select_options": [
{"label": "Option 1", "id": "abc-123"},
{"label": "Option 2", "id": "def-456"},
{"label": "Option 3", "id": "ghi-789"},
],
},
)
doc = Document.objects.create(
title="WOW",
content="the content",
checksum="123",
mime_type="application/pdf",
)
CustomFieldInstance.objects.create(
document=doc,
field=custom_field_select,
value_text="abc-123",
)
resp = self.client.patch(
f"{self.ENDPOINT}{custom_field_select.id}/",
json.dumps(
{
"extra_data": {
"select_options": [
{"label": "Option 1", "id": "abc-123"},
{"label": "Option 3", "id": "ghi-789"},
],
},
},
),
content_type="application/json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertCountEqual(
data["extra_data"]["select_options"],
[
{"label": "Option 1", "id": "abc-123"},
{"label": "Option 3", "id": "ghi-789"},
],
)
doc.refresh_from_db()
self.assertEqual(doc.custom_fields.first().value, None)
def test_create_custom_field_monetary_validation(self):
"""
GIVEN: