2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 17:28:11 +00:00

[refactor] Improve primary_address annotation for Company API (#11006)

* Refactor primary_address annotation

- Remove SerializerMethodField
- Better cache introspection

* Allow address detail to be optional

* Refactor address caching

* Fix primary_address annotation

* Remove "address_count" field

- Pointless annotation which is not used anywhere

* Update API version

* Tweak docs page

* Tweak unit tests
This commit is contained in:
Oliver
2025-12-14 21:54:07 +11:00
committed by GitHub
parent a727c4e2f9
commit 71c2f5ca73
5 changed files with 26 additions and 35 deletions

View File

@@ -1,11 +1,16 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 430 INVENTREE_API_VERSION = 431
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v431 -> 2025-12-14 : https://github.com/inventree/InvenTree/pull/11006
- Remove duplicate "address" field on the Company API endpoint
- Make "primary_address" field optional on the Company API endpoint
- Remove "address_count" field from the Company API endpoint
v430 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10699 v430 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10699
- Removed the "PartParameter" and "PartParameterTemplate" API endpoints - Removed the "PartParameter" and "PartParameterTemplate" API endpoints
- Removed the "ManufacturerPartParameter" API endpoint - Removed the "ManufacturerPartParameter" API endpoint

View File

@@ -537,7 +537,9 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
return queryset.prefetch_related( return queryset.prefetch_related(
'parameters_list', 'parameters_list',
'parameters_list__model_type', 'parameters_list__model_type',
'parameters_list__updated_by',
'parameters_list__template', 'parameters_list__template',
'parameters_list__template__model_type',
) )
@property @property
@@ -547,8 +549,9 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
This will return pre-fetched data if available (i.e. in a serializer context). This will return pre-fetched data if available (i.e. in a serializer context).
""" """
# Check the query cache for pre-fetched parameters # Check the query cache for pre-fetched parameters
if 'parameters_list' in getattr(self, '_prefetched_objects_cache', {}): if cache := getattr(self, '_prefetched_objects_cache', None):
return self._prefetched_objects_cache['parameters_list'] if 'parameters_list' in cache:
return cache['parameters_list']
return self.parameters_list.all() return self.parameters_list.all()

View File

@@ -238,8 +238,14 @@ class Company(
@property @property
def primary_address(self): def primary_address(self):
"""Returns address object of primary address. Parsed by serializer.""" """Returns address object of primary address for this Company."""
return Address.objects.filter(company=self.id).filter(primary=True).first() # We may have a pre-fetched primary address list
if hasattr(self, 'primary_address_list'):
addresses = self.primary_address_list
return addresses[0] if len(addresses) > 0 else None
# Otherwise, query the database
return self.addresses.filter(primary=True).first()
@property @property
def currency_code(self): def currency_code(self):

View File

@@ -6,7 +6,6 @@ from django.core.files.base import ContentFile
from django.db.models import Prefetch from django.db.models import Prefetch
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from sql_util.utils import SubqueryCount from sql_util.utils import SubqueryCount
from taggit.serializers import TagListSerializerField from taggit.serializers import TagListSerializerField
@@ -136,7 +135,6 @@ class CompanySerializer(
'website', 'website',
'name', 'name',
'phone', 'phone',
'address',
'email', 'email',
'currency', 'currency',
'contact', 'contact',
@@ -150,7 +148,6 @@ class CompanySerializer(
'parts_supplied', 'parts_supplied',
'parts_manufactured', 'parts_manufactured',
'remote_image', 'remote_image',
'address_count',
'primary_address', 'primary_address',
'tax_id', 'tax_id',
'parameters', 'parameters',
@@ -166,8 +163,6 @@ class CompanySerializer(
queryset = queryset.annotate(parts_supplied=SubqueryCount('supplied_parts')) queryset = queryset.annotate(parts_supplied=SubqueryCount('supplied_parts'))
queryset = queryset.annotate(address_count=SubqueryCount('addresses'))
queryset = queryset.prefetch_related( queryset = queryset.prefetch_related(
Prefetch( Prefetch(
'addresses', 'addresses',
@@ -180,30 +175,12 @@ class CompanySerializer(
return queryset return queryset
address = serializers.SerializerMethodField( primary_address = enable_filter(
label=_('Primary Address'), AddressBriefSerializer(read_only=True, allow_null=True),
help_text=_( True,
'Return the string representation for the primary address. This property exists for backwards compatibility.' filter_name='address_detail',
),
allow_null=True,
) )
primary_address = serializers.SerializerMethodField(allow_null=True)
@extend_schema_field(serializers.CharField())
def get_address(self, obj):
"""Return string version of primary address (for backwards compatibility)."""
if hasattr(obj, 'primary_address_list') and obj.primary_address_list:
return str(obj.primary_address_list[0])
return None
@extend_schema_field(AddressSerializer())
def get_primary_address(self, obj):
"""Return full address object for primary address using prefetch data."""
if hasattr(obj, 'primary_address_list') and obj.primary_address_list:
return AddressSerializer(obj.primary_address_list[0]).data
return None
image = InvenTreeImageSerializerField(required=False, allow_null=True) image = InvenTreeImageSerializerField(required=False, allow_null=True)
email = serializers.EmailField( email = serializers.EmailField(
@@ -211,8 +188,8 @@ class CompanySerializer(
) )
parts_supplied = serializers.IntegerField(read_only=True) parts_supplied = serializers.IntegerField(read_only=True)
parts_manufactured = serializers.IntegerField(read_only=True) parts_manufactured = serializers.IntegerField(read_only=True)
address_count = serializers.IntegerField(read_only=True)
currency = InvenTreeCurrencySerializer( currency = InvenTreeCurrencySerializer(
help_text=_('Default currency used for this supplier'), required=True help_text=_('Default currency used for this supplier'), required=True

View File

@@ -2065,8 +2065,8 @@ class PartListTests(PartAPITestBase):
if b and result['category'] is not None: if b and result['category'] is not None:
self.assertIn('category_detail', result) self.assertIn('category_detail', result)
# No more than 22 DB queries # No more than 25 DB queries
self.assertLessEqual(len(ctx), 22) self.assertLessEqual(len(ctx), 25)
def test_price_breaks(self): def test_price_breaks(self):
"""Test that price_breaks parameter works correctly and efficiently.""" """Test that price_breaks parameter works correctly and efficiently."""