diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 2ca179bb40..0f8350f84f 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -4,6 +4,7 @@ Generic models which provide extra functionality over base Django model types. from __future__ import unicode_literals +import re import os import logging @@ -43,6 +44,48 @@ def rename_attachment(instance, filename): return os.path.join(instance.getSubdir(), filename) +class ReferenceIndexingMixin(models.Model): + """ + A mixin for keeping track of numerical copies of the "reference" field. + + Here, we attempt to convert a "reference" field value (char) to an integer, + for performing fast natural sorting. + + This requires extra database space (due to the extra table column), + but is required as not all supported database backends provide equivalent casting. + + This mixin adds a field named 'reference_int'. + + - If the 'reference' field can be cast to an integer, it is stored here + - If the 'reference' field *starts* with an integer, it is stored here + - Otherwise, we store zero + """ + + class Meta: + abstract = True + + def rebuild_reference_field(self): + + reference = getattr(self, 'reference', '') + + # Default value if we cannot convert to an integer + ref_int = 0 + + # Look at the start of the string - can it be "integerized"? + result = re.match(r"^(\d+)", reference) + + if result and len(result.groups()) == 1: + ref = result.groups()[0] + try: + ref_int = int(ref) + except: + ref_int = 0 + + self.reference_int = ref_int + + reference_int = models.IntegerField(default=0) + + class InvenTreeAttachment(models.Model): """ Provides an abstracted class for managing file attachments. diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index ea177c1570..cf4d44a03e 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -85,7 +85,7 @@ class BuildList(generics.ListCreateAPIView): ] ordering_field_aliases = { - 'reference': ['integer_ref', 'reference'], + 'reference': ['reference_int', 'reference'], } search_fields = [ diff --git a/InvenTree/build/migrations/0031_build_reference_int.py b/InvenTree/build/migrations/0031_build_reference_int.py new file mode 100644 index 0000000000..c7fc2c16cc --- /dev/null +++ b/InvenTree/build/migrations/0031_build_reference_int.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-10-14 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0030_alter_build_reference'), + ] + + operations = [ + migrations.AddField( + model_name='build', + name='reference_int', + field=models.IntegerField(default=0), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 449776579e..c477794e8c 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -28,7 +28,7 @@ from mptt.exceptions import InvalidMove from InvenTree.status_codes import BuildStatus, StockStatus, StockHistoryCode from InvenTree.helpers import increment, getSetting, normalize, MakeBarcode from InvenTree.validators import validate_build_order_reference -from InvenTree.models import InvenTreeAttachment +from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin import common.models @@ -69,7 +69,7 @@ def get_next_build_number(): return reference -class Build(MPTTModel): +class Build(MPTTModel, ReferenceIndexingMixin): """ A Build object organises the creation of new StockItem objects from other existing StockItem objects. Attributes: @@ -108,6 +108,8 @@ class Build(MPTTModel): def save(self, *args, **kwargs): + self.rebuild_reference_field() + try: super().save(*args, **kwargs) except InvalidMove: diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 5a64bda0a0..547f565905 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -10,8 +10,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.utils.translation import ugettext_lazy as _ from django.db.models import Case, When, Value -from django.db.models import BooleanField, IntegerField -from django.db.models.functions import Cast +from django.db.models import BooleanField from rest_framework import serializers from rest_framework.serializers import ValidationError @@ -72,11 +71,6 @@ class BuildSerializer(InvenTreeModelSerializer): ) ) - # Annotate with an "integer" version of the reference field, to be used for natural sorting - queryset = queryset.annotate( - integer_ref=Cast('reference', output_field=IntegerField()) - ) - return queryset def __init__(self, *args, **kwargs): diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index a451f05ac8..df0ec1a5de 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -156,7 +156,7 @@ class POList(generics.ListCreateAPIView): ] ordering_field_aliases = { - 'reference': ['integer_ref', 'reference'], + 'reference': ['reference_int', 'reference'], } filter_fields = [ @@ -512,7 +512,7 @@ class SOList(generics.ListCreateAPIView): ] ordering_field_aliases = { - 'reference': ['integer_ref', 'reference'], + 'reference': ['reference_int', 'reference'], } filter_fields = [ diff --git a/InvenTree/order/migrations/0051_auto_20211014_0623.py b/InvenTree/order/migrations/0051_auto_20211014_0623.py new file mode 100644 index 0000000000..20cc893dd2 --- /dev/null +++ b/InvenTree/order/migrations/0051_auto_20211014_0623.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.5 on 2021-10-14 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0050_alter_purchaseorderlineitem_destination'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorder', + name='reference_int', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='salesorder', + name='reference_int', + field=models.IntegerField(default=0), + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 4ac8925259..1b15e74663 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -28,7 +28,7 @@ from company.models import Company, SupplierPart from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField from InvenTree.helpers import decimal2string, increment, getSetting from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus, StockHistoryCode -from InvenTree.models import InvenTreeAttachment +from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin def get_next_po_number(): @@ -89,7 +89,7 @@ def get_next_so_number(): return reference -class Order(models.Model): +class Order(ReferenceIndexingMixin): """ Abstract model for an order. Instances of this class: @@ -147,6 +147,9 @@ class Order(models.Model): return new_ref def save(self, *args, **kwargs): + + self.rebuild_reference_field() + if not self.creation_date: self.creation_date = datetime.now().date() diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index bcc1791db4..40cd2def58 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -4,7 +4,6 @@ JSON serializers for the Order API # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db.models.fields import IntegerField from django.utils.translation import ugettext_lazy as _ @@ -12,7 +11,6 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.db import models, transaction from django.db.models import Case, When, Value from django.db.models import BooleanField, ExpressionWrapper, F -from django.db.models.functions import Cast from rest_framework import serializers from rest_framework.serializers import ValidationError @@ -75,11 +73,6 @@ class POSerializer(InvenTreeModelSerializer): ) ) - # Annotate with an "integer" version of the reference field, to be used for natural sorting - queryset = queryset.annotate( - integer_ref=Cast('reference', output_field=IntegerField()) - ) - return queryset supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) @@ -435,11 +428,6 @@ class SalesOrderSerializer(InvenTreeModelSerializer): ) ) - # Annotate with an "integer" version of the reference field, to be used for natural sorting - queryset = queryset.annotate( - integer_ref=Cast('reference', output_field=IntegerField()) - ) - return queryset customer_detail = CompanyBriefSerializer(source='customer', many=False, read_only=True)