mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-03 11:29:28 -05:00
Automatically prune removed options from existing instances
This commit is contained in:
parent
233212d198
commit
e08fd79265
@ -34,7 +34,7 @@ from documents.settings import EXPORTER_ARCHIVE_NAME
|
|||||||
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
||||||
from documents.settings import EXPORTER_FILE_NAME
|
from documents.settings import EXPORTER_FILE_NAME
|
||||||
from documents.settings import EXPORTER_THUMBNAIL_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.signals.handlers import update_filename_and_move_files
|
||||||
from documents.utils import copy_file_with_basic_stats
|
from documents.utils import copy_file_with_basic_stats
|
||||||
from paperless import version
|
from paperless import version
|
||||||
@ -262,7 +262,7 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
),
|
),
|
||||||
disable_signal(
|
disable_signal(
|
||||||
post_save,
|
post_save,
|
||||||
receiver=update_cf_instance_documents,
|
receiver=check_paths_and_prune_custom_fields,
|
||||||
sender=CustomField,
|
sender=CustomField,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
|
@ -367,21 +367,6 @@ class CannotMoveFilesException(Exception):
|
|||||||
pass
|
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
|
# should be disabled in /src/documents/management/commands/document_importer.py handle
|
||||||
@receiver(models.signals.post_save, sender=CustomFieldInstance)
|
@receiver(models.signals.post_save, sender=CustomFieldInstance)
|
||||||
@receiver(models.signals.m2m_changed, sender=Document.tags.through)
|
@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):
|
def set_log_entry(sender, document: Document, logging_group=None, **kwargs):
|
||||||
ct = ContentType.objects.get(model="document")
|
ct = ContentType.objects.get(model="document")
|
||||||
user = User.objects.get(username="consumer")
|
user = User.objects.get(username="consumer")
|
||||||
|
@ -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):
|
def test_create_custom_field_monetary_validation(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user