mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-04 18:40:55 +00:00
Validate queryset annotation
- Add unit test with large dataset - Ensure number of queries is fixed - Fix for prefetching check
This commit is contained in:
@@ -454,11 +454,12 @@ class ReferenceIndexingMixin(models.Model):
|
|||||||
class ContentTypeMixin:
|
class ContentTypeMixin:
|
||||||
"""Mixin class which supports retrieval of the ContentType for a model instance."""
|
"""Mixin class which supports retrieval of the ContentType for a model instance."""
|
||||||
|
|
||||||
def get_content_type(self):
|
@classmethod
|
||||||
|
def get_content_type(cls):
|
||||||
"""Return the ContentType object associated with this model."""
|
"""Return the ContentType object associated with this model."""
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
return ContentType.objects.get_for_model(self.__class__)
|
return ContentType.objects.get_for_model(cls)
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeModel(ContentTypeMixin, PluginValidationMixin, models.Model):
|
class InvenTreeModel(ContentTypeMixin, PluginValidationMixin, models.Model):
|
||||||
@@ -532,14 +533,10 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
Annotated queryset
|
Annotated queryset
|
||||||
"""
|
"""
|
||||||
from common.models import Parameter
|
|
||||||
|
|
||||||
return queryset.prefetch_related(
|
return queryset.prefetch_related(
|
||||||
models.Prefetch(
|
'parameters_list',
|
||||||
'parameters_list',
|
'parameters_list__model_type',
|
||||||
queryset=Parameter.objects.all().select_related('template'),
|
'parameters_list__template',
|
||||||
to_attr='parameters_list_prefetched',
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -548,8 +545,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).
|
||||||
"""
|
"""
|
||||||
if hasattr(self, 'parameters_list_prefetched'):
|
# Check the query cache for pre-fetched parameters
|
||||||
return self.parameters_list_prefetched
|
if 'parameters_list' in getattr(self, '_prefetched_objects_cache', {}):
|
||||||
|
return self._prefetched_objects_cache['parameters_list']
|
||||||
|
|
||||||
return self.parameters_list.all()
|
return self.parameters_list.all()
|
||||||
|
|
||||||
|
|||||||
@@ -192,3 +192,81 @@ class ParameterAPITests(InvenTreeAPITestCase):
|
|||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
common.models.Parameter.objects.filter(pk=parameter.pk).exists()
|
common.models.Parameter.objects.filter(pk=parameter.pk).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_parameter_annotation(self):
|
||||||
|
"""Test that we can annotate parameters against a queryset."""
|
||||||
|
from company.models import Company
|
||||||
|
|
||||||
|
templates = []
|
||||||
|
parameters = []
|
||||||
|
companies = []
|
||||||
|
|
||||||
|
for ii in range(100):
|
||||||
|
company = Company(
|
||||||
|
name=f'Test Company {ii}',
|
||||||
|
description='A company for testing parameter annotations',
|
||||||
|
)
|
||||||
|
companies.append(company)
|
||||||
|
|
||||||
|
Company.objects.bulk_create(companies)
|
||||||
|
|
||||||
|
# Let's create a large number of parameters
|
||||||
|
for ii in range(25):
|
||||||
|
templates.append(
|
||||||
|
common.models.ParameterTemplate(
|
||||||
|
name=f'Test Parameter {ii}',
|
||||||
|
units='',
|
||||||
|
description='A parameter for testing annotations',
|
||||||
|
model_type=Company.get_content_type(),
|
||||||
|
enabled=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
common.models.ParameterTemplate.objects.bulk_create(templates)
|
||||||
|
|
||||||
|
# Create a parameter for every company against every template
|
||||||
|
for company in companies:
|
||||||
|
for template in templates:
|
||||||
|
parameters.append(
|
||||||
|
common.models.Parameter(
|
||||||
|
template=template,
|
||||||
|
model_type=company.get_content_type(),
|
||||||
|
model_id=company.pk,
|
||||||
|
data=f'Test data for {company.name} - {template.name}',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
common.models.Parameter.objects.bulk_create(parameters)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
common.models.Parameter.objects.count(), len(companies) * len(templates)
|
||||||
|
)
|
||||||
|
|
||||||
|
# We will fetch the companies, annotated with all parameters
|
||||||
|
url = reverse('api-company-list')
|
||||||
|
|
||||||
|
# By default, we do not expect any parameter annotations
|
||||||
|
response = self.get(url, data={'limit': 5})
|
||||||
|
|
||||||
|
self.assertEqual(response.data['count'], len(companies))
|
||||||
|
for company in response.data['results']:
|
||||||
|
self.assertNotIn('parameters', company)
|
||||||
|
|
||||||
|
# Fetch all companies, explicitly without parameters
|
||||||
|
with self.assertNumQueriesLessThan(20):
|
||||||
|
response = self.get(url, data={'parameters': False})
|
||||||
|
|
||||||
|
# Now, annotate with parameters
|
||||||
|
# This must be done efficiently, without an 1 + N query pattern
|
||||||
|
with self.assertNumQueriesLessThan(45):
|
||||||
|
response = self.get(url, data={'parameters': True})
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), len(companies))
|
||||||
|
|
||||||
|
for company in response.data:
|
||||||
|
self.assertIn('parameters', company)
|
||||||
|
self.assertEqual(
|
||||||
|
len(company['parameters']),
|
||||||
|
len(templates),
|
||||||
|
'Incorrect number of parameter annotations found',
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user