Model changes and Barcode consumer logic

This commit is contained in:
Florian Huber 2023-11-02 15:31:34 +00:00
parent 55e799b833
commit 28b70e288a
3 changed files with 98 additions and 20 deletions

View File

@ -17,9 +17,19 @@ from documents.converters import convert_from_tiff_to_pdf
from documents.data_models import DocumentSource
from documents.utils import copy_basic_file_stats
from documents.utils import copy_file_with_basic_stats
from documents.models import ASNPrefix
from documents.models import ASN
logger = logging.getLogger("paperless.barcodes")
ASNPrefixes = list(ASNPrefix.objects.all())
@receiver(post_save, sender=ASNPrefix)
@receiver(post_delete, sender=ASNPrefix)
def update_asn_prefix_list(sender, instance, **kwargs):
global ASNPrefixes
ASNPrefixes = list(ASNPrefix.objects.all())
@dataclass(frozen=True)
class Barcode:
@ -44,7 +54,10 @@ class Barcode:
Returns True if the barcode value matches the configured ASN prefix,
False otherwise
"""
return self.value.startswith(settings.CONSUMER_ASN_BARCODE_PREFIX)
for ASNPrefix in ASNPrefixes:
if self.value.startswith(ASNPrefix.prefix):
return True
return False
class BarcodeReader:
@ -78,7 +91,7 @@ class BarcodeReader:
return self.mime in self.SUPPORTED_FILE_MIMES
@property
def asn(self) -> Optional[int]:
def asn(self) -> Optional[ASN]:
"""
Search the parsed barcodes for any ASNs.
The first barcode that starts with CONSUMER_ASN_BARCODE_PREFIX
@ -99,18 +112,32 @@ class BarcodeReader:
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_prefix = None
asn_prefix_len = 0
for prefix in ASNPrefixes:
if asn_text.startswith(prefix.prefix) and len(prefix.prefix) > asn_prefix_len:
asn_prefix = prefix
asn_prefix_len = len(prefix.prefix)
if asn_prefix == None:
logger.warning("Failed to parse ASN Prefix")
return None
asn_value = asn_text[asn_prefix_len :].strip()
# remove non-numeric parts of the remaining string
asn_text = re.sub("[^0-9]", "", asn_text)
asn_value = re.sub("[^0-9]", "", asn_value)
# now, try parsing the ASN number
try:
asn = int(asn_text)
asn_number = int(asn_value)
except ValueError as e:
logger.warning(f"Failed to parse ASN number because: {e}")
return None
return asn
return ASN(prefix=asn_prefix,number=asn_number)
@staticmethod
def read_barcodes_zxing(image: Image) -> list[str]:

View File

@ -34,6 +34,7 @@ from documents.models import DocumentType
from documents.models import FileInfo
from documents.models import StoragePath
from documents.models import Tag
from documents.models import ASN
from documents.parsers import DocumentParser
from documents.parsers import ParseError
from documents.parsers import get_parser_class_for_mime_type
@ -174,8 +175,8 @@ class Consumer(LoggingMixin):
# Validate the range is above zero and less than uint32_t max
# otherwise, Whoosh can't handle it in the index
if (
self.override_asn < Document.ARCHIVE_SERIAL_NUMBER_MIN
or self.override_asn > Document.ARCHIVE_SERIAL_NUMBER_MAX
self.override_asn.number < Document.ARCHIVE_SERIAL_NUMBER_MIN
or self.override_asn.number > Document.ARCHIVE_SERIAL_NUMBER_MAX
):
self._fail(
ConsumerStatusShortMessage.ASN_RANGE,
@ -184,10 +185,11 @@ class Consumer(LoggingMixin):
f"[{Document.ARCHIVE_SERIAL_NUMBER_MIN:,}, "
f"{Document.ARCHIVE_SERIAL_NUMBER_MAX:,}]",
)
if Document.objects.filter(archive_serial_number=self.override_asn).exists():
if ASN.objects.filter(prefix=self.override_asn.prefix, number=self.override_asn.number).exists():
self._fail(
ConsumerStatusShortMessage.ASN_ALREADY_EXISTS,
f"Not consuming {self.filename}: Given ASN already exists!",
f"Not consuming {self.filename}: Given ASN already exists for the Prefix {self.override_asn.prefix.prefix}!",
)
def run_pre_consume_script(self):

View File

@ -15,6 +15,7 @@ from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator
from django.core.validators import MinValueValidator
from django.core.validators import MinLengthValidator
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@ -129,6 +130,58 @@ class StoragePath(MatchingModel):
verbose_name = _("storage path")
verbose_name_plural = _("storage paths")
class ASNPrefix(models.Model):
prefix = models.CharField(_("ASN-Prefix"), max_length=16, blank=False, primary_key=True, validators=[MinLengthValidator(2)])
description = models.CharField(_("Description", max_length=512, blank=True))
def __str__(self):
return f"{self.prefix.prefix}"
class ASN(models.Model):
prefix = models.ForeignKey(
ASNPrefix,
blank = False,
on_delete = models.CASCADE # TODO: Not shure about that one
)
ARCHIVE_SERIAL_NUMBER_MIN: Final[int] = 0
ARCHIVE_SERIAL_NUMBER_MAX: Final[int] = 0xFF_FF_FF_FF
number = models.PositiveIntegerField(
_("archive serial number"),
blank=True,
null=True,
unique=False,
db_index=True,
validators=[
MaxValueValidator(ARCHIVE_SERIAL_NUMBER_MAX),
MinValueValidator(ARCHIVE_SERIAL_NUMBER_MIN),
],
help_text=_(
"The position of this document in your physical document archive.",
),
)
class Meta:
unique_together = ('prefix', 'number')
def __str__(self):
return f"{self.prefix.prefix}-{self.number:06d}"
@staticmethod
def get_max_number_for_prefix(prefix):
try:
max_number = ASN.objects.filter(prefix=prefix).aggregate(models.Max('number'))['number__max']
return max_number if max_number is not None else 0
except ASN.DoesNotExist:
return 0
@staticmethod
def get_next_ASN_for_prefix(prefix):
return ASN(prefix, ASN.get_max_number_for_prefix(prefix)+1)
class Document(ModelWithOwner):
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
@ -256,19 +309,14 @@ class Document(ModelWithOwner):
help_text=_("The original name of the file when it was uploaded"),
)
ARCHIVE_SERIAL_NUMBER_MIN: Final[int] = 0
ARCHIVE_SERIAL_NUMBER_MAX: Final[int] = 0xFF_FF_FF_FF
archive_serial_number = models.PositiveIntegerField(
_("archive serial number"),
archive_serial_number = models.OneToOneField(
to=ASN,
on_delete=models.CASCADE,
verbose_name=_("archive serial number"),
blank=True,
null=True,
unique=True,
db_index=True,
validators=[
MaxValueValidator(ARCHIVE_SERIAL_NUMBER_MAX),
MinValueValidator(ARCHIVE_SERIAL_NUMBER_MIN),
],
help_text=_(
"The position of this document in your physical document archive.",
),
@ -574,6 +622,7 @@ class UiSettings(models.Model):
return self.user.username
class PaperlessTask(models.Model):
ALL_STATES = sorted(states.ALL_STATES)
TASK_STATE_CHOICES = sorted(zip(ALL_STATES, ALL_STATES))