mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 09:18:10 +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:
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
Reference in New Issue
Block a user