From dc25d53af49402ee516badefcf22b9f56ee3c5da Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:44:05 -0700 Subject: [PATCH] Use a custom filter to work around access to fields with spaces --- docs/advanced_usage.md | 10 +++++ src/documents/file_handling.py | 9 ++++- src/documents/serialisers.py | 2 +- src/documents/templatetags/get_cf_value.py | 12 ++++++ src/documents/tests/test_file_handling.py | 45 ++++++++++++---------- 5 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 src/documents/templatetags/get_cf_value.py diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 2fe5c540f..a717bd0ee 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -438,6 +438,10 @@ with more complex logic. - `{{ tag_name_list }}`: A list of tag names applied to the document, ordered by the tag name. Note this is a list, not a single string - `{{ custom_fields }}`: A mapping of custom field names to their type and value. A user can access the mapping by field name or check if a field is applied by checking its existence in the variable. +!!! tip + + To access a custom field which has a space in the name, use the `get_cf_value` filter. See the examples below. + #### Examples This example will construct a path based on the archive serial number range: @@ -486,6 +490,12 @@ To use custom fields: If the document has a custom field named "Invoice" with a value of 123, it would be filed into the `invoices/123.pdf`, but a document without the custom field would be filed to `not-invoices/Title.pdf` +If the custom field is named "Invoice Number", you would access the value of it via the `get_cf_value` filter due to quirks of the Django Template Language: + +```django +"invoices/{{ custom_fields|get_cf_value:'Invoice Number' }}" +``` + ## Automatic recovery of invalid PDFs {#pdf-recovery} Paperless will attempt to "clean" certain invalid PDFs with `qpdf` before processing if, for example, the mime_type diff --git a/src/documents/file_handling.py b/src/documents/file_handling.py index 1b0baf0d4..1c3bbe69d 100644 --- a/src/documents/file_handling.py +++ b/src/documents/file_handling.py @@ -26,7 +26,10 @@ INVALID_VARIABLE_STR = "InvalidVarError" filepath_engine = Engine( autoescape=False, string_if_invalid=f"{INVALID_VARIABLE_STR}: %s", - libraries={"filepath": "documents.templatetags.filepath"}, + libraries={ + "filepath": "documents.templatetags.filepath", + "get_cf_value": "documents.templatetags.get_cf_value", + }, ) @@ -330,7 +333,9 @@ def validate_template_and_render( try: # We load the custom tag used to remove spaces and newlines from the final string around the user string template = filepath_engine.from_string( - "{% load filepath %}{% filepath %}" + template_string + "{% endfilepath %}", + "{% load filepath %}{% load get_cf_value %}{% filepath %}" + + template_string + + "{% endfilepath %}", ) rendered_template = template.render(Context(context)) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index fe7c7500e..bb7028e76 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -54,7 +54,7 @@ from documents.models import WorkflowTrigger from documents.parsers import is_mime_type_supported from documents.permissions import get_groups_with_only_permission from documents.permissions import set_permissions_for_object -from documents.templatetags import convert_to_django_template_format +from documents.templatetags.filepath import convert_to_django_template_format from documents.validators import uri_validator logger = logging.getLogger("paperless.serializers") diff --git a/src/documents/templatetags/get_cf_value.py b/src/documents/templatetags/get_cf_value.py new file mode 100644 index 000000000..2fe2b72dc --- /dev/null +++ b/src/documents/templatetags/get_cf_value.py @@ -0,0 +1,12 @@ +from django import template + +register = template.Library() + + +@register.filter("get_cf_value") +def get_cf_value(custom_field_data: dict[str, dict[str, str]], name: str): + """ + See https://stackoverflow.com/questions/2970244/django-templates-value-of-dictionary-key-with-a-space-in-it/2970337#2970337 + """ + data = custom_field_data[name]["value"] + return data diff --git a/src/documents/tests/test_file_handling.py b/src/documents/tests/test_file_handling.py index 126592f88..5bb593c0e 100644 --- a/src/documents/tests/test_file_handling.py +++ b/src/documents/tests/test_file_handling.py @@ -1245,17 +1245,6 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase): ) def test_template_with_custom_fields(self): - sp = StoragePath.objects.create( - name="sp1", - path=""" - {% if "Invoice" in custom_fields %} - invoices/{{ custom_fields.Invoice.value }} - {% else %} - not-invoices/{{ title }} - {% endif %} - """, - ) - doc_a = Document.objects.create( title="Some Title", created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)), @@ -1264,7 +1253,6 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase): pk=2, checksum="2", archive_serial_number=25, - storage_path=sp, ) cf = CustomField.objects.create( @@ -1278,17 +1266,32 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase): value_int=1234, ) - self.assertEqual( - generate_filename(doc_a), - "invoices/1234.pdf", - ) + with override_settings( + FILENAME_FORMAT=""" + {% if "Invoice" in custom_fields %} + invoices/{{ custom_fields|get_cf_value:'Invoice' }} + {% else %} + not-invoices/{{ title }} + {% endif %} + """, + ): + self.assertEqual( + generate_filename(doc_a), + "invoices/1234.pdf", + ) - cfi.delete() + cf.name = "Invoice Number" + cfi.value_int = 4567 + cfi.save() + cf.save() - self.assertEqual( - generate_filename(doc_a), - "not-invoices/Some Title.pdf", - ) + with override_settings( + FILENAME_FORMAT="invoices/{{ custom_fields|get_cf_value:'Invoice Number' }}", + ): + self.assertEqual( + generate_filename(doc_a), + "invoices/4567.pdf", + ) def test_using_other_filters(self): doc_a = Document.objects.create(