mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Enhancement: add barcode frontend config (#9742)
This commit is contained in:
		| @@ -15,13 +15,16 @@ from pikepdf import Pdf | ||||
|  | ||||
| from documents.converters import convert_from_tiff_to_pdf | ||||
| from documents.data_models import ConsumableDocument | ||||
| from documents.data_models import DocumentMetadataOverrides | ||||
| from documents.models import Tag | ||||
| from documents.plugins.base import ConsumeTaskPlugin | ||||
| from documents.plugins.base import StopConsumeTaskError | ||||
| from documents.plugins.helpers import ProgressManager | ||||
| from documents.plugins.helpers import ProgressStatusOptions | ||||
| from documents.utils import copy_basic_file_stats | ||||
| from documents.utils import copy_file_with_basic_stats | ||||
| from documents.utils import maybe_override_pixel_limit | ||||
| from paperless.config import BarcodeConfig | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from collections.abc import Callable | ||||
| @@ -39,6 +42,7 @@ class Barcode: | ||||
|  | ||||
|     page: int | ||||
|     value: str | ||||
|     settings: BarcodeConfig | ||||
|  | ||||
|     @property | ||||
|     def is_separator(self) -> bool: | ||||
| @@ -46,7 +50,7 @@ class Barcode: | ||||
|         Returns True if the barcode value equals the configured separation value, | ||||
|         False otherwise | ||||
|         """ | ||||
|         return self.value == settings.CONSUMER_BARCODE_STRING | ||||
|         return self.value == self.settings.barcode_string | ||||
|  | ||||
|     @property | ||||
|     def is_asn(self) -> bool: | ||||
| @@ -54,7 +58,7 @@ class Barcode: | ||||
|         Returns True if the barcode value matches the configured ASN prefix, | ||||
|         False otherwise | ||||
|         """ | ||||
|         return self.value.startswith(settings.CONSUMER_ASN_BARCODE_PREFIX) | ||||
|         return self.value.startswith(self.settings.barcode_asn_prefix) | ||||
|  | ||||
|  | ||||
| class BarcodePlugin(ConsumeTaskPlugin): | ||||
| @@ -67,17 +71,41 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|           - ASN from barcode detection is enabled or | ||||
|           - Barcode support is enabled and the mime type is supported | ||||
|         """ | ||||
|         if settings.CONSUMER_BARCODE_TIFF_SUPPORT: | ||||
|         if self.settings.barcode_enable_tiff_support: | ||||
|             supported_mimes: set[str] = {"application/pdf", "image/tiff"} | ||||
|         else: | ||||
|             supported_mimes = {"application/pdf"} | ||||
|  | ||||
|         return ( | ||||
|             settings.CONSUMER_ENABLE_ASN_BARCODE | ||||
|             or settings.CONSUMER_ENABLE_BARCODES | ||||
|             or settings.CONSUMER_ENABLE_TAG_BARCODE | ||||
|             self.settings.barcode_enable_asn | ||||
|             or self.settings.barcodes_enabled | ||||
|             or self.settings.barcode_enable_tag | ||||
|         ) and self.input_doc.mime_type in supported_mimes | ||||
|  | ||||
|     def get_settings(self) -> BarcodeConfig: | ||||
|         """ | ||||
|         Returns the settings for this plugin (Django settings or app config) | ||||
|         """ | ||||
|         return BarcodeConfig() | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         input_doc: ConsumableDocument, | ||||
|         metadata: DocumentMetadataOverrides, | ||||
|         status_mgr: ProgressManager, | ||||
|         base_tmp_dir: Path, | ||||
|         task_id: str, | ||||
|     ) -> None: | ||||
|         super().__init__( | ||||
|             input_doc, | ||||
|             metadata, | ||||
|             status_mgr, | ||||
|             base_tmp_dir, | ||||
|             task_id, | ||||
|         ) | ||||
|         # need these for able_to_run | ||||
|         self.settings = self.get_settings() | ||||
|  | ||||
|     def setup(self) -> None: | ||||
|         self.temp_dir = tempfile.TemporaryDirectory( | ||||
|             dir=self.base_tmp_dir, | ||||
| @@ -99,7 +127,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|  | ||||
|         # try reading tags from barcodes | ||||
|         if ( | ||||
|             settings.CONSUMER_ENABLE_TAG_BARCODE | ||||
|             self.settings.barcode_enable_tag | ||||
|             and (tags := self.tags) is not None | ||||
|             and len(tags) > 0 | ||||
|         ): | ||||
| @@ -110,7 +138,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|             logger.info(f"Found tags in barcode: {tags}") | ||||
|  | ||||
|         # Lastly attempt to split documents | ||||
|         if settings.CONSUMER_ENABLE_BARCODES and ( | ||||
|         if self.settings.barcodes_enabled and ( | ||||
|             separator_pages := self.get_separation_pages() | ||||
|         ): | ||||
|             # We have pages to split against | ||||
| @@ -155,10 +183,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|  | ||||
|         # Update/overwrite an ASN if possible | ||||
|         # After splitting, as otherwise each split document gets the same ASN | ||||
|         if ( | ||||
|             settings.CONSUMER_ENABLE_ASN_BARCODE | ||||
|             and (located_asn := self.asn) is not None | ||||
|         ): | ||||
|         if self.settings.barcode_enable_asn and (located_asn := self.asn) is not None: | ||||
|             logger.info(f"Found ASN in barcode: {located_asn}") | ||||
|             self.metadata.asn = located_asn | ||||
|  | ||||
| @@ -245,8 +270,8 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|             # Get limit from configuration | ||||
|             barcode_max_pages: int = ( | ||||
|                 num_of_pages | ||||
|                 if settings.CONSUMER_BARCODE_MAX_PAGES == 0 | ||||
|                 else settings.CONSUMER_BARCODE_MAX_PAGES | ||||
|                 if self.settings.barcode_max_pages == 0 | ||||
|                 else self.settings.barcode_max_pages | ||||
|             ) | ||||
|  | ||||
|             if barcode_max_pages < num_of_pages:  # pragma: no cover | ||||
| @@ -261,7 +286,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|                 # Convert page to image | ||||
|                 page = convert_from_path( | ||||
|                     self.pdf_file, | ||||
|                     dpi=settings.CONSUMER_BARCODE_DPI, | ||||
|                     dpi=self.settings.barcode_dpi, | ||||
|                     output_folder=self.temp_dir.name, | ||||
|                     first_page=current_page_number + 1, | ||||
|                     last_page=current_page_number + 1, | ||||
| @@ -272,7 +297,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|                 logger.debug(f"Image is at {page_filepath}") | ||||
|  | ||||
|                 # Upscale image if configured | ||||
|                 factor = settings.CONSUMER_BARCODE_UPSCALE | ||||
|                 factor = self.settings.barcode_upscale | ||||
|                 if factor > 1.0: | ||||
|                     logger.debug( | ||||
|                         f"Upscaling image by {factor} for better barcode detection", | ||||
| @@ -285,7 +310,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|                 # Detect barcodes | ||||
|                 for barcode_value in reader(page): | ||||
|                     self.barcodes.append( | ||||
|                         Barcode(current_page_number, barcode_value), | ||||
|                         Barcode(current_page_number, barcode_value, self.settings), | ||||
|                     ) | ||||
|  | ||||
|                 # Delete temporary image file | ||||
| @@ -308,7 +333,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|     def asn(self) -> int | None: | ||||
|         """ | ||||
|         Search the parsed barcodes for any ASNs. | ||||
|         The first barcode that starts with CONSUMER_ASN_BARCODE_PREFIX | ||||
|         The first barcode that starts with barcode_asn_prefix | ||||
|         is considered the ASN to be used. | ||||
|         Returns the detected ASN (or None) | ||||
|         """ | ||||
| @@ -317,7 +342,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|         # Ensure the barcodes have been read | ||||
|         self.detect() | ||||
|  | ||||
|         # get the first barcode that starts with CONSUMER_ASN_BARCODE_PREFIX | ||||
|         # get the first barcode that starts with barcode_asn_prefix | ||||
|         asn_text: str | None = next( | ||||
|             (x.value for x in self.barcodes if x.is_asn), | ||||
|             None, | ||||
| @@ -326,7 +351,7 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|         if asn_text: | ||||
|             logger.debug(f"Found ASN Barcode: {asn_text}") | ||||
|             # remove the prefix and remove whitespace | ||||
|             asn_text = asn_text[len(settings.CONSUMER_ASN_BARCODE_PREFIX) :].strip() | ||||
|             asn_text = asn_text[len(self.settings.barcode_asn_prefix) :].strip() | ||||
|  | ||||
|             # remove non-numeric parts of the remaining string | ||||
|             asn_text = re.sub(r"\D", "", asn_text) | ||||
| @@ -356,9 +381,9 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|             for raw in tag_texts.split(","): | ||||
|                 try: | ||||
|                     tag_str: str | None = None | ||||
|                     for regex in settings.CONSUMER_TAG_BARCODE_MAPPING: | ||||
|                     for regex in self.settings.barcode_tag_mapping: | ||||
|                         if re.match(regex, raw, flags=re.IGNORECASE): | ||||
|                             sub = settings.CONSUMER_TAG_BARCODE_MAPPING[regex] | ||||
|                             sub = self.settings.barcode_tag_mapping[regex] | ||||
|                             tag_str = ( | ||||
|                                 re.sub(regex, sub, raw, flags=re.IGNORECASE) | ||||
|                                 if sub | ||||
| @@ -394,13 +419,13 @@ class BarcodePlugin(ConsumeTaskPlugin): | ||||
|         """ | ||||
|         # filter all barcodes for the separator string | ||||
|         # get the page numbers of the separating barcodes | ||||
|         retain = settings.CONSUMER_BARCODE_RETAIN_SPLIT_PAGES | ||||
|         retain = self.settings.barcode_retain_split_pages | ||||
|         separator_pages = { | ||||
|             bc.page: retain | ||||
|             for bc in self.barcodes | ||||
|             if bc.is_separator and (not retain or (retain and bc.page > 0)) | ||||
|         }  # as below, dont include the first page if retain is enabled | ||||
|         if not settings.CONSUMER_ENABLE_ASN_BARCODE: | ||||
|         if not self.settings.barcode_enable_asn: | ||||
|             return separator_pages | ||||
|  | ||||
|         # add the page numbers of the ASN barcodes | ||||
|   | ||||
| @@ -32,28 +32,39 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): | ||||
|  | ||||
|         self.assertEqual(response.status_code, status.HTTP_200_OK) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             json.dumps(response.data[0]), | ||||
|             json.dumps( | ||||
|                 { | ||||
|                     "id": 1, | ||||
|                     "user_args": None, | ||||
|                     "output_type": None, | ||||
|                     "pages": None, | ||||
|                     "language": None, | ||||
|                     "mode": None, | ||||
|                     "skip_archive_file": None, | ||||
|                     "image_dpi": None, | ||||
|                     "unpaper_clean": None, | ||||
|                     "deskew": None, | ||||
|                     "rotate_pages": None, | ||||
|                     "rotate_pages_threshold": None, | ||||
|                     "max_image_pixels": None, | ||||
|                     "color_conversion_strategy": None, | ||||
|                     "app_title": None, | ||||
|                     "app_logo": None, | ||||
|                 }, | ||||
|             ), | ||||
|         self.maxDiff = None | ||||
|  | ||||
|         self.assertDictEqual( | ||||
|             response.data[0], | ||||
|             { | ||||
|                 "id": 1, | ||||
|                 "output_type": None, | ||||
|                 "pages": None, | ||||
|                 "language": None, | ||||
|                 "mode": None, | ||||
|                 "skip_archive_file": None, | ||||
|                 "image_dpi": None, | ||||
|                 "unpaper_clean": None, | ||||
|                 "deskew": None, | ||||
|                 "rotate_pages": None, | ||||
|                 "rotate_pages_threshold": None, | ||||
|                 "max_image_pixels": None, | ||||
|                 "color_conversion_strategy": None, | ||||
|                 "user_args": None, | ||||
|                 "app_title": None, | ||||
|                 "app_logo": None, | ||||
|                 "barcodes_enabled": None, | ||||
|                 "barcode_enable_tiff_support": None, | ||||
|                 "barcode_string": None, | ||||
|                 "barcode_retain_split_pages": None, | ||||
|                 "barcode_enable_asn": None, | ||||
|                 "barcode_asn_prefix": None, | ||||
|                 "barcode_upscale": None, | ||||
|                 "barcode_dpi": None, | ||||
|                 "barcode_max_pages": None, | ||||
|                 "barcode_enable_tag": None, | ||||
|                 "barcode_tag_mapping": None, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def test_api_get_ui_settings_with_config(self): | ||||
| @@ -118,6 +129,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): | ||||
|                 { | ||||
|                     "user_args": "", | ||||
|                     "language": "", | ||||
|                     "barcode_tag_mapping": "", | ||||
|                 }, | ||||
|             ), | ||||
|             content_type="application/json", | ||||
| @@ -126,6 +138,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): | ||||
|         config = ApplicationConfiguration.objects.first() | ||||
|         self.assertEqual(config.user_args, None) | ||||
|         self.assertEqual(config.language, None) | ||||
|         self.assertEqual(config.barcode_tag_mapping, None) | ||||
|  | ||||
|     def test_api_replace_app_logo(self): | ||||
|         """ | ||||
|   | ||||
| @@ -22,6 +22,7 @@ from documents.tests.utils import DocumentConsumeDelayMixin | ||||
| from documents.tests.utils import DummyProgressManager | ||||
| from documents.tests.utils import FileSystemAssertsMixin | ||||
| from documents.tests.utils import SampleDirMixin | ||||
| from paperless.models import ApplicationConfiguration | ||||
|  | ||||
| try: | ||||
|     import zxingcpp  # noqa: F401 | ||||
| @@ -547,6 +548,27 @@ class TestBarcode( | ||||
|                 }, | ||||
|             ) | ||||
|  | ||||
|     def test_barcode_config(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Barcode app config is set (settings are not) | ||||
|         WHEN: | ||||
|             - Document with barcode is processed | ||||
|         THEN: | ||||
|             - The barcode config is used | ||||
|         """ | ||||
|         app_config = ApplicationConfiguration.objects.first() | ||||
|         app_config.barcodes_enabled = True | ||||
|         app_config.barcode_string = "CUSTOM BARCODE" | ||||
|         app_config.save() | ||||
|         test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-custom.pdf" | ||||
|         with self.get_reader(test_file) as reader: | ||||
|             reader.detect() | ||||
|             separator_page_numbers = reader.get_separation_pages() | ||||
|  | ||||
|             self.assertEqual(reader.pdf_file, test_file) | ||||
|             self.assertDictEqual(separator_page_numbers, {0: False}) | ||||
|  | ||||
|  | ||||
| @override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR") | ||||
| class TestBarcodeNewConsume( | ||||
|   | ||||
| @@ -96,10 +96,65 @@ class OcrConfig(OutputTypeConfig): | ||||
|                 user_args = json.loads(settings.OCR_USER_ARGS) | ||||
|             except json.JSONDecodeError: | ||||
|                 user_args = {} | ||||
|  | ||||
|         self.user_args = user_args | ||||
|  | ||||
|  | ||||
| @dataclasses.dataclass | ||||
| class BarcodeConfig(BaseConfig): | ||||
|     """ | ||||
|     Barcodes settings | ||||
|     """ | ||||
|  | ||||
|     barcodes_enabled: bool = dataclasses.field(init=False) | ||||
|     barcode_enable_tiff_support: bool = dataclasses.field(init=False) | ||||
|     barcode_string: str = dataclasses.field(init=False) | ||||
|     barcode_retain_split_pages: bool = dataclasses.field(init=False) | ||||
|     barcode_enable_asn: bool = dataclasses.field(init=False) | ||||
|     barcode_asn_prefix: str = dataclasses.field(init=False) | ||||
|     barcode_upscale: float = dataclasses.field(init=False) | ||||
|     barcode_dpi: int = dataclasses.field(init=False) | ||||
|     barcode_max_pages: int = dataclasses.field(init=False) | ||||
|     barcode_enable_tag: bool = dataclasses.field(init=False) | ||||
|     barcode_tag_mapping: dict[str, str] = dataclasses.field(init=False) | ||||
|  | ||||
|     def __post_init__(self) -> None: | ||||
|         app_config = self._get_config_instance() | ||||
|  | ||||
|         self.barcodes_enabled = ( | ||||
|             app_config.barcodes_enabled or settings.CONSUMER_ENABLE_BARCODES | ||||
|         ) | ||||
|         self.barcode_enable_tiff_support = ( | ||||
|             app_config.barcode_enable_tiff_support | ||||
|             or settings.CONSUMER_BARCODE_TIFF_SUPPORT | ||||
|         ) | ||||
|         self.barcode_string = ( | ||||
|             app_config.barcode_string or settings.CONSUMER_BARCODE_STRING | ||||
|         ) | ||||
|         self.barcode_retain_split_pages = ( | ||||
|             app_config.barcode_retain_split_pages | ||||
|             or settings.CONSUMER_BARCODE_RETAIN_SPLIT_PAGES | ||||
|         ) | ||||
|         self.barcode_enable_asn = ( | ||||
|             app_config.barcode_enable_asn or settings.CONSUMER_ENABLE_ASN_BARCODE | ||||
|         ) | ||||
|         self.barcode_asn_prefix = ( | ||||
|             app_config.barcode_asn_prefix or settings.CONSUMER_ASN_BARCODE_PREFIX | ||||
|         ) | ||||
|         self.barcode_upscale = ( | ||||
|             app_config.barcode_upscale or settings.CONSUMER_BARCODE_UPSCALE | ||||
|         ) | ||||
|         self.barcode_dpi = app_config.barcode_dpi or settings.CONSUMER_BARCODE_DPI | ||||
|         self.barcode_max_pages = ( | ||||
|             app_config.barcode_max_pages or settings.CONSUMER_BARCODE_MAX_PAGES | ||||
|         ) | ||||
|         self.barcode_enable_tag = ( | ||||
|             app_config.barcode_enable_tag or settings.CONSUMER_ENABLE_TAG_BARCODE | ||||
|         ) | ||||
|         self.barcode_tag_mapping = ( | ||||
|             app_config.barcode_tag_mapping or settings.CONSUMER_TAG_BARCODE_MAPPING | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @dataclasses.dataclass | ||||
| class GeneralConfig(BaseConfig): | ||||
|     """ | ||||
|   | ||||
| @@ -0,0 +1,100 @@ | ||||
| # Generated by Django 5.1.7 on 2025-04-02 19:21 | ||||
|  | ||||
| import django.core.validators | ||||
| from django.db import migrations | ||||
| from django.db import models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("paperless", "0003_alter_applicationconfiguration_max_image_pixels"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_asn_prefix", | ||||
|             field=models.CharField( | ||||
|                 blank=True, | ||||
|                 max_length=32, | ||||
|                 null=True, | ||||
|                 verbose_name="Sets the ASN barcode prefix", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_dpi", | ||||
|             field=models.PositiveIntegerField( | ||||
|                 null=True, | ||||
|                 validators=[django.core.validators.MinValueValidator(1)], | ||||
|                 verbose_name="Sets the barcode DPI", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_enable_asn", | ||||
|             field=models.BooleanField(null=True, verbose_name="Enables ASN barcode"), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_enable_tag", | ||||
|             field=models.BooleanField(null=True, verbose_name="Enables tag barcode"), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_enable_tiff_support", | ||||
|             field=models.BooleanField( | ||||
|                 null=True, | ||||
|                 verbose_name="Enables barcode TIFF support", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_max_pages", | ||||
|             field=models.PositiveIntegerField( | ||||
|                 null=True, | ||||
|                 validators=[django.core.validators.MinValueValidator(1)], | ||||
|                 verbose_name="Sets the maximum pages for barcode", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_retain_split_pages", | ||||
|             field=models.BooleanField(null=True, verbose_name="Retains split pages"), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_string", | ||||
|             field=models.CharField( | ||||
|                 blank=True, | ||||
|                 max_length=32, | ||||
|                 null=True, | ||||
|                 verbose_name="Sets the barcode string", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_tag_mapping", | ||||
|             field=models.JSONField( | ||||
|                 null=True, | ||||
|                 verbose_name="Sets the tag barcode mapping", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcode_upscale", | ||||
|             field=models.FloatField( | ||||
|                 null=True, | ||||
|                 validators=[django.core.validators.MinValueValidator(1.0)], | ||||
|                 verbose_name="Sets the barcode upscale factor", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="applicationconfiguration", | ||||
|             name="barcodes_enabled", | ||||
|             field=models.BooleanField( | ||||
|                 null=True, | ||||
|                 verbose_name="Enables barcode scanning", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -167,6 +167,10 @@ class ApplicationConfiguration(AbstractSingletonModel): | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     """ | ||||
|     Settings for the Paperless application | ||||
|     """ | ||||
|  | ||||
|     app_title = models.CharField( | ||||
|         verbose_name=_("Application title"), | ||||
|         null=True, | ||||
| @@ -184,6 +188,83 @@ class ApplicationConfiguration(AbstractSingletonModel): | ||||
|         upload_to="logo/", | ||||
|     ) | ||||
|  | ||||
|     """ | ||||
|     Settings for the barcode scanner | ||||
|     """ | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_ENABLE_BARCODES | ||||
|     barcodes_enabled = models.BooleanField( | ||||
|         verbose_name=_("Enables barcode scanning"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT | ||||
|     barcode_enable_tiff_support = models.BooleanField( | ||||
|         verbose_name=_("Enables barcode TIFF support"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_STRING | ||||
|     barcode_string = models.CharField( | ||||
|         verbose_name=_("Sets the barcode string"), | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         max_length=32, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES | ||||
|     barcode_retain_split_pages = models.BooleanField( | ||||
|         verbose_name=_("Retains split pages"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE | ||||
|     barcode_enable_asn = models.BooleanField( | ||||
|         verbose_name=_("Enables ASN barcode"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX | ||||
|     barcode_asn_prefix = models.CharField( | ||||
|         verbose_name=_("Sets the ASN barcode prefix"), | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         max_length=32, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_UPSCALE | ||||
|     barcode_upscale = models.FloatField( | ||||
|         verbose_name=_("Sets the barcode upscale factor"), | ||||
|         null=True, | ||||
|         validators=[MinValueValidator(1.0)], | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_DPI | ||||
|     barcode_dpi = models.PositiveIntegerField( | ||||
|         verbose_name=_("Sets the barcode DPI"), | ||||
|         null=True, | ||||
|         validators=[MinValueValidator(1)], | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_BARCODE_MAX_PAGES | ||||
|     barcode_max_pages = models.PositiveIntegerField( | ||||
|         verbose_name=_("Sets the maximum pages for barcode"), | ||||
|         null=True, | ||||
|         validators=[MinValueValidator(1)], | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE | ||||
|     barcode_enable_tag = models.BooleanField( | ||||
|         verbose_name=_("Enables tag barcode"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     # PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING | ||||
|     barcode_tag_mapping = models.JSONField( | ||||
|         verbose_name=_("Sets the tag barcode mapping"), | ||||
|         null=True, | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("paperless application settings") | ||||
|  | ||||
|   | ||||
| @@ -185,11 +185,14 @@ class ProfileSerializer(serializers.ModelSerializer): | ||||
|  | ||||
| class ApplicationConfigurationSerializer(serializers.ModelSerializer): | ||||
|     user_args = serializers.JSONField(binary=True, allow_null=True) | ||||
|     barcode_tag_mapping = serializers.JSONField(binary=True, allow_null=True) | ||||
|  | ||||
|     def run_validation(self, data): | ||||
|         # Empty strings treated as None to avoid unexpected behavior | ||||
|         if "user_args" in data and data["user_args"] == "": | ||||
|             data["user_args"] = None | ||||
|         if "barcode_tag_mapping" in data and data["barcode_tag_mapping"] == "": | ||||
|             data["barcode_tag_mapping"] = None | ||||
|         if "language" in data and data["language"] == "": | ||||
|             data["language"] = None | ||||
|         return super().run_validation(data) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon