mirror of
https://github.com/django/django.git
synced 2025-11-19 03:08:59 +00:00
Allow in_bulk() for fields with UniqueConstraint
Previously, in_bulk() required fields to have unique=True. Now, fields with an unconditional UniqueConstraint are also supported, enabling lookups like in_bulk(field_name="slug") for such fields. Fixes issue where fields unique via UniqueConstraint could not be used with in_bulk().
This commit is contained in:
parent
67f9d076cf
commit
1efb80ab0f
3 changed files with 63 additions and 3 deletions
|
|
@ -17,6 +17,7 @@ from django.db import (
|
||||||
)
|
)
|
||||||
from django.db.models import AutoField, DateField, DateTimeField, sql
|
from django.db.models import AutoField, DateField, DateTimeField, sql
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
|
from django.db.models.constraints import UniqueConstraint
|
||||||
from django.db.models.deletion import Collector
|
from django.db.models.deletion import Collector
|
||||||
from django.db.models.expressions import Case, Expression, F, Value, When
|
from django.db.models.expressions import Case, Expression, F, Value, When
|
||||||
from django.db.models.functions import Cast, Trunc
|
from django.db.models.functions import Cast, Trunc
|
||||||
|
|
@ -689,8 +690,19 @@ class QuerySet:
|
||||||
"""
|
"""
|
||||||
assert not self.query.is_sliced, \
|
assert not self.query.is_sliced, \
|
||||||
"Cannot use 'limit' or 'offset' with in_bulk"
|
"Cannot use 'limit' or 'offset' with in_bulk"
|
||||||
if field_name != 'pk' and not self.model._meta.get_field(field_name).unique:
|
if field_name != 'pk':
|
||||||
raise ValueError("in_bulk()'s field_name must be a unique field but %r isn't." % field_name)
|
# Check if field is unique via field.unique or UniqueConstraint
|
||||||
|
field = self.model._meta.get_field(field_name)
|
||||||
|
if not field.unique:
|
||||||
|
# Check if field has a total UniqueConstraint (single field, no condition)
|
||||||
|
is_unique_constraint = any(
|
||||||
|
isinstance(constraint, UniqueConstraint) and
|
||||||
|
constraint.fields == (field_name,) and
|
||||||
|
constraint.condition is None
|
||||||
|
for constraint in self.model._meta.constraints
|
||||||
|
)
|
||||||
|
if not is_unique_constraint:
|
||||||
|
raise ValueError("in_bulk()'s field_name must be a unique field but %r isn't." % field_name)
|
||||||
if id_list is not None:
|
if id_list is not None:
|
||||||
if not id_list:
|
if not id_list:
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,13 @@ class AbstractModel(models.Model):
|
||||||
|
|
||||||
class ChildModel(AbstractModel):
|
class ChildModel(AbstractModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UniqueConstraintInBulkProduct(models.Model):
|
||||||
|
"""Model for testing in_bulk() with UniqueConstraint on a single field."""
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=['name'], name='unique_name'),
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ChildModel, Product, UniqueConstraintConditionProduct,
|
ChildModel, Product, UniqueConstraintConditionProduct,
|
||||||
UniqueConstraintProduct,
|
UniqueConstraintInBulkProduct, UniqueConstraintProduct,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -238,3 +238,41 @@ class UniqueConstraintTests(TestCase):
|
||||||
def test_condition_must_be_q(self):
|
def test_condition_must_be_q(self):
|
||||||
with self.assertRaisesMessage(ValueError, 'UniqueConstraint.condition must be a Q instance.'):
|
with self.assertRaisesMessage(ValueError, 'UniqueConstraint.condition must be a Q instance.'):
|
||||||
models.UniqueConstraint(name='uniq', fields=['name'], condition='invalid')
|
models.UniqueConstraint(name='uniq', fields=['name'], condition='invalid')
|
||||||
|
|
||||||
|
def test_in_bulk_with_unique_constraint(self):
|
||||||
|
"""Test that in_bulk() works with UniqueConstraint on a single field."""
|
||||||
|
# Create test objects
|
||||||
|
obj1 = UniqueConstraintInBulkProduct.objects.create(name='product1')
|
||||||
|
obj2 = UniqueConstraintInBulkProduct.objects.create(name='product2')
|
||||||
|
obj3 = UniqueConstraintInBulkProduct.objects.create(name='product3')
|
||||||
|
|
||||||
|
# Test in_bulk with field_name that has UniqueConstraint
|
||||||
|
result = UniqueConstraintInBulkProduct.objects.in_bulk(
|
||||||
|
['product1', 'product2', 'product3'],
|
||||||
|
field_name='name'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(result), 3)
|
||||||
|
self.assertEqual(result['product1'], obj1)
|
||||||
|
self.assertEqual(result['product2'], obj2)
|
||||||
|
self.assertEqual(result['product3'], obj3)
|
||||||
|
|
||||||
|
def test_in_bulk_without_id_list_with_unique_constraint(self):
|
||||||
|
"""Test that in_bulk() works without id_list for UniqueConstraint fields."""
|
||||||
|
obj1 = UniqueConstraintInBulkProduct.objects.create(name='product_a')
|
||||||
|
obj2 = UniqueConstraintInBulkProduct.objects.create(name='product_b')
|
||||||
|
|
||||||
|
result = UniqueConstraintInBulkProduct.objects.in_bulk(field_name='name')
|
||||||
|
|
||||||
|
self.assertEqual(len(result), 2)
|
||||||
|
self.assertEqual(result['product_a'], obj1)
|
||||||
|
self.assertEqual(result['product_b'], obj2)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_partial_indexes')
|
||||||
|
def test_in_bulk_with_partial_unique_constraint_fails(self):
|
||||||
|
"""Test that in_bulk() fails for fields with conditional UniqueConstraint."""
|
||||||
|
UniqueConstraintConditionProduct.objects.create(name='p1')
|
||||||
|
|
||||||
|
msg = "in_bulk()'s field_name must be a unique field but 'name' isn't."
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
UniqueConstraintConditionProduct.objects.in_bulk(['p1'], field_name='name')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue