Skip to content

Commit 41c4b08

Browse files
committed
more has_any has_all complex query tests
1 parent 8095a54 commit 41c4b08

File tree

5 files changed

+229
-7
lines changed

5 files changed

+229
-7
lines changed

django_enum/tests/djenum/migrations/0001_initial.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.4 on 2023-08-07 15:11
1+
# Generated by Django 4.2.4 on 2023-08-08 01:51
22

33
import datetime
44
from decimal import Decimal
@@ -52,6 +52,26 @@ class Migration(migrations.Migration):
5252
('big_neg', django_enum.fields.EnumBigIntegerField(blank=True, choices=[(-576460752303423488, 'ONE'), (-1152921504606846976, 'TWO'), (-2305843009213693952, 'THREE'), (-4611686018427387904, 'FOUR'), (-9223372036854775808, 'FIVE')], db_index=True, default=0)),
5353
('extra_big_neg', django_enum.fields.EnumExtraBigIntegerField(blank=True, choices=[(-1, 'ONE'), (-2, 'TWO'), (-18446744073709551616, 'THREE'), (-36893488147419103232, 'FOUR'), (-73786976294838206464, 'FIVE')], db_index=True, default=0)),
5454
],
55+
options={
56+
'abstract': False,
57+
},
58+
),
59+
migrations.CreateModel(
60+
name='EnumFlagTesterRelated',
61+
fields=[
62+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63+
('small_pos', django_enum.fields.SmallIntegerFlagField(blank=True, choices=[(1024, 'ONE'), (2048, 'TWO'), (4096, 'THREE'), (8192, 'FOUR'), (16384, 'FIVE')], db_index=True, default=None, null=True)),
64+
('pos', django_enum.fields.IntegerFlagField(blank=True, choices=[(67108864, 'ONE'), (134217728, 'TWO'), (268435456, 'THREE'), (536870912, 'FOUR'), (1073741824, 'FIVE')], db_index=True, default=0)),
65+
('big_pos', django_enum.fields.BigIntegerFlagField(blank=True, choices=[(288230376151711744, 'ONE'), (576460752303423488, 'TWO'), (1152921504606846976, 'THREE'), (2305843009213693952, 'FOUR'), (4611686018427387904, 'FIVE')], db_index=True, default=0)),
66+
('extra_big_pos', django_enum.fields.ExtraBigIntegerFlagField(blank=True, choices=[(1, 'ONE'), (2, 'TWO'), (9223372036854775808, 'THREE'), (18446744073709551616, 'FOUR'), (36893488147419103232, 'FIVE')], db_index=True, default=0)),
67+
('small_neg', django_enum.fields.EnumSmallIntegerField(blank=True, choices=[(-2048, 'ONE'), (-4096, 'TWO'), (-8192, 'THREE'), (-16384, 'FOUR'), (-32768, 'FIVE')], db_index=True, default=0)),
68+
('neg', django_enum.fields.EnumIntegerField(blank=True, choices=[(-134217728, 'ONE'), (-268435456, 'TWO'), (-536870912, 'THREE'), (-1073741824, 'FOUR'), (-2147483648, 'FIVE')], db_index=True, default=0)),
69+
('big_neg', django_enum.fields.EnumBigIntegerField(blank=True, choices=[(-576460752303423488, 'ONE'), (-1152921504606846976, 'TWO'), (-2305843009213693952, 'THREE'), (-4611686018427387904, 'FOUR'), (-9223372036854775808, 'FIVE')], db_index=True, default=0)),
70+
('extra_big_neg', django_enum.fields.EnumExtraBigIntegerField(blank=True, choices=[(-1, 'ONE'), (-2, 'TWO'), (-18446744073709551616, 'THREE'), (-36893488147419103232, 'FOUR'), (-73786976294838206464, 'FIVE')], db_index=True, default=0)),
71+
],
72+
options={
73+
'abstract': False,
74+
},
5575
),
5676
migrations.CreateModel(
5777
name='EnumTester',
@@ -173,6 +193,11 @@ class Migration(migrations.Migration):
173193
model_name='enumtester',
174194
constraint=models.CheckConstraint(check=models.Q(('decimal_enum__in', [Decimal('0.99'), Decimal('0.999'), Decimal('0.9999'), Decimal('99.9999'), Decimal('999')])), name='django_enum_tests_djenum_EnumTester_decimal_enum_DecimalEnum'),
175195
),
196+
migrations.AddField(
197+
model_name='enumflagtesterrelated',
198+
name='related_flags',
199+
field=models.ManyToManyField(related_name='related_flags', to='django_enum_tests_djenum.enumflagtester'),
200+
),
176201
migrations.AddConstraint(
177202
model_name='emptyenumvaluetester',
178203
constraint=models.CheckConstraint(check=models.Q(('blank_text_enum__in', ['', 'V22'])), name='_tests_djenum_EmptyEnumValueTester_blank_text_enum_BlankTextEnum'),

django_enum/tests/djenum/models.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class NoneIntEnum(enum.Enum):
214214
none_int_enum_non_null = EnumField(NoneIntEnum, null=False)
215215

216216

217-
class EnumFlagTester(models.Model):
217+
class EnumFlagTesterBase(models.Model):
218218

219219
small_pos = EnumField(
220220
SmallPositiveFlagEnum,
@@ -282,6 +282,83 @@ def __repr__(self):
282282
f'big_neg={repr(self.big_neg)}, ' \
283283
f'extra_big_neg={repr(self.extra_big_neg)})'
284284

285+
class Meta:
286+
abstract = True
287+
288+
289+
class EnumFlagTester(EnumFlagTesterBase):
290+
291+
small_pos = EnumField(
292+
SmallPositiveFlagEnum,
293+
default=None,
294+
null=True,
295+
db_index=True,
296+
blank=True
297+
)
298+
299+
pos = EnumField(
300+
PositiveFlagEnum,
301+
default=PositiveFlagEnum(0),
302+
db_index=True,
303+
blank=True
304+
)
305+
306+
big_pos = EnumField(
307+
BigPositiveFlagEnum,
308+
default=BigPositiveFlagEnum(0),
309+
db_index=True,
310+
blank=True
311+
)
312+
313+
extra_big_pos = EnumField(
314+
ExtraBigPositiveFlagEnum,
315+
default=ExtraBigPositiveFlagEnum(0),
316+
db_index=True,
317+
blank=True
318+
)
319+
320+
small_neg = EnumField(
321+
SmallNegativeFlagEnum,
322+
default=SmallNegativeFlagEnum(0),
323+
db_index=True,
324+
blank=True
325+
)
326+
327+
neg = EnumField(
328+
NegativeFlagEnum,
329+
default=NegativeFlagEnum(0),
330+
db_index=True,
331+
blank=True
332+
)
333+
334+
big_neg = EnumField(
335+
BigNegativeFlagEnum,
336+
default=BigNegativeFlagEnum(0),
337+
db_index=True,
338+
blank=True
339+
)
340+
341+
extra_big_neg = EnumField(
342+
ExtraBigNegativeFlagEnum,
343+
default=ExtraBigNegativeFlagEnum(0),
344+
db_index=True,
345+
blank=True
346+
)
347+
348+
def __repr__(self):
349+
return f'EnumFlagTester(small_pos={repr(self.small_pos)}, ' \
350+
f'pos={repr(self.pos)}, ' \
351+
f'big_pos={repr(self.big_pos)}, ' \
352+
f'extra_big_pos={repr(self.extra_big_pos)}, ' \
353+
f'small_neg={repr(self.small_neg)}, neg={repr(self.neg)}, ' \
354+
f'big_neg={repr(self.big_neg)}, ' \
355+
f'extra_big_neg={repr(self.extra_big_neg)})'
356+
357+
358+
class EnumFlagTesterRelated(EnumFlagTesterBase):
359+
360+
related_flags = models.ManyToManyField(EnumFlagTester, related_name='related_flags')
361+
285362

286363
class MultiPrimitiveTestModel(models.Model):
287364

django_enum/tests/enum_prop/migrations/0001_initial.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.4 on 2023-08-07 15:11
1+
# Generated by Django 4.2.4 on 2023-08-08 01:54
22

33
import datetime
44
from decimal import Decimal
@@ -38,6 +38,26 @@ class Migration(migrations.Migration):
3838
('big_neg', django_enum.fields.EnumBigIntegerField(blank=True, choices=[(-576460752303423488, 'One'), (-1152921504606846976, 'Two'), (-2305843009213693952, 'Three'), (-4611686018427387904, 'Four'), (-9223372036854775808, 'Five')], db_index=True, default=0)),
3939
('extra_big_neg', django_enum.fields.EnumExtraBigIntegerField(blank=True, choices=[(-4611686018427387904, 'One'), (-9223372036854775808, 'Two'), (-18446744073709551616, 'Three'), (-36893488147419103232, 'Four'), (-73786976294838206464, 'Five')], db_index=True, default=0)),
4040
],
41+
options={
42+
'abstract': False,
43+
},
44+
),
45+
migrations.CreateModel(
46+
name='EnumFlagPropTesterRelated',
47+
fields=[
48+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
49+
('small_pos', django_enum.fields.SmallIntegerFlagField(blank=True, choices=[(1024, 'One'), (2048, 'Two'), (4096, 'Three'), (8192, 'Four'), (16384, 'Five')], db_index=True, default=None, null=True)),
50+
('pos', django_enum.fields.IntegerFlagField(blank=True, choices=[(67108864, 'One'), (134217728, 'Two'), (268435456, 'Three'), (536870912, 'Four'), (1073741824, 'Five')], db_index=True, default=0)),
51+
('big_pos', django_enum.fields.BigIntegerFlagField(blank=True, choices=[(288230376151711744, 'One'), (576460752303423488, 'Two'), (1152921504606846976, 'Three'), (2305843009213693952, 'Four'), (4611686018427387904, 'Five')], db_index=True, default=0)),
52+
('extra_big_pos', django_enum.fields.ExtraBigIntegerFlagField(blank=True, choices=[(2305843009213693952, 'One'), (4611686018427387904, 'Two'), (9223372036854775808, 'Three'), (18446744073709551616, 'Four'), (36893488147419103232, 'Five')], db_index=True, default=0)),
53+
('small_neg', django_enum.fields.EnumSmallIntegerField(blank=True, choices=[(-2048, 'One'), (-4096, 'Two'), (-8192, 'Three'), (-16384, 'Four'), (-32768, 'Five')], db_index=True, default=0)),
54+
('neg', django_enum.fields.EnumIntegerField(blank=True, choices=[(-134217728, 'One'), (-268435456, 'Two'), (-536870912, 'Three'), (-1073741824, 'Four'), (-2147483648, 'Five')], db_index=True, default=0)),
55+
('big_neg', django_enum.fields.EnumBigIntegerField(blank=True, choices=[(-576460752303423488, 'One'), (-1152921504606846976, 'Two'), (-2305843009213693952, 'Three'), (-4611686018427387904, 'Four'), (-9223372036854775808, 'Five')], db_index=True, default=0)),
56+
('extra_big_neg', django_enum.fields.EnumExtraBigIntegerField(blank=True, choices=[(-4611686018427387904, 'One'), (-9223372036854775808, 'Two'), (-18446744073709551616, 'Three'), (-36893488147419103232, 'Four'), (-73786976294838206464, 'Five')], db_index=True, default=0)),
57+
],
58+
options={
59+
'abstract': False,
60+
},
4161
),
4262
migrations.CreateModel(
4363
name='EnumTester',
@@ -289,4 +309,9 @@ class Migration(migrations.Migration):
289309
model_name='enumtester',
290310
constraint=models.CheckConstraint(check=models.Q(('no_coerce__in', [0, 2, 32767]), ('no_coerce__isnull', True), _connector='OR'), name='django_enum_tests_enum_prop_EnumTester_no_coerce_SmallPosIntEnum'),
291311
),
312+
migrations.AddField(
313+
model_name='enumflagproptesterrelated',
314+
name='related_flags',
315+
field=models.ManyToManyField(related_name='related_flags', to='django_enum_tests_enum_prop.enumflagproptester'),
316+
),
292317
]

django_enum/tests/enum_prop/models.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ class BitFieldModel(models.Model):
287287
no_default = EnumField(LargeBitField)
288288

289289

290-
class EnumFlagPropTester(models.Model):
290+
class BaseEnumFlagPropTester(models.Model):
291291

292292
small_pos = EnumField(
293293
SmallPositiveFlagEnum,
@@ -355,6 +355,20 @@ def __repr__(self):
355355
f'big_neg={repr(self.big_neg)}, ' \
356356
f'extra_big_neg={repr(self.extra_big_neg)})'
357357

358+
class Meta:
359+
abstract = True
360+
361+
362+
class EnumFlagPropTester(BaseEnumFlagPropTester):
363+
pass
364+
365+
class EnumFlagPropTesterRelated(BaseEnumFlagPropTester):
366+
367+
related_flags = models.ManyToManyField(
368+
EnumFlagPropTester,
369+
related_name='related_flags'
370+
)
371+
358372

359373
except (ImportError, ModuleNotFoundError): # pragma: no cover
360374
pass

django_enum/tests/tests.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from django.core.exceptions import FieldError, ValidationError
1212
from django.core.management import call_command
1313
from django.db import connection, transaction
14-
from django.db.models import F, Func, OuterRef, Q, Subquery
14+
from django.db.models import Count, F, Func, OuterRef, Q, Subquery
1515
from django.db.utils import DatabaseError
1616
from django.http import QueryDict
1717
from django.test import Client, TestCase
@@ -42,9 +42,10 @@
4242
# ExternEnum
4343
# )
4444
from django_enum.tests.djenum.forms import EnumTesterForm
45-
from django_enum.tests.djenum.models import ( # EnumFlagTesterRelated
45+
from django_enum.tests.djenum.models import (
4646
BadDefault,
4747
EnumFlagTester,
48+
EnumFlagTesterRelated,
4849
EnumTester,
4950
)
5051
from django_enum.tests.oracle_patch import patch_oracle
@@ -2542,7 +2543,7 @@ def test_get_set_bits(self):
25422543
class FlagTests(TestCase):
25432544

25442545
MODEL_CLASS = EnumFlagTester
2545-
#RELATED_CLASS = EnumFlagTesterRelated
2546+
RELATED_CLASS = EnumFlagTesterRelated
25462547

25472548
def test_flag_filters(self):
25482549
fields = [
@@ -2753,6 +2754,7 @@ def update_empties(obj):
27532754
self.assertTrue(obj.small_pos is None and obj.pos & EnumClass.ONE)
27542755

27552756
def test_subquery(self):
2757+
"""test that has_any and has_all work with complex queries involving subqueries"""
27562758

27572759
for field in [
27582760
field for field in self.MODEL_CLASS._meta.fields
@@ -2822,6 +2824,83 @@ def test_subquery(self):
28222824
):
28232825
self.assertEqual(obj.any_matches, expected)
28242826

2827+
def test_joins(self):
2828+
"""test that has_any and has_all work with complex queries involving joins"""
2829+
2830+
for field in [
2831+
field for field in self.MODEL_CLASS._meta.fields
2832+
if isinstance(field, FlagField)
2833+
and not isinstance(field, ExtraBigIntegerFlagField)
2834+
]:
2835+
EnumClass = field.enum
2836+
self.MODEL_CLASS.objects.all().delete()
2837+
2838+
objects = [
2839+
self.MODEL_CLASS.objects.create(
2840+
**{field.name: EnumClass.TWO | EnumClass.FOUR | EnumClass.FIVE}
2841+
),
2842+
self.MODEL_CLASS.objects.create(
2843+
**{field.name: EnumClass.ONE | EnumClass.THREE}
2844+
),
2845+
self.MODEL_CLASS.objects.create(
2846+
**{field.name: EnumClass.TWO | EnumClass.FOUR}
2847+
),
2848+
self.MODEL_CLASS.objects.create(**{field.name: EnumClass.FIVE}),
2849+
self.MODEL_CLASS.objects.create(
2850+
**{
2851+
field.name: (
2852+
EnumClass.ONE | EnumClass.TWO | EnumClass.THREE |
2853+
EnumClass.FOUR | EnumClass.FIVE
2854+
)
2855+
}
2856+
)
2857+
]
2858+
related = []
2859+
for obj in objects:
2860+
related.append([
2861+
self.RELATED_CLASS.objects.create(
2862+
**{field.name: EnumClass.TWO | EnumClass.FOUR | EnumClass.FIVE}
2863+
),
2864+
self.RELATED_CLASS.objects.create(
2865+
**{field.name: EnumClass.ONE | EnumClass.THREE}
2866+
),
2867+
self.RELATED_CLASS.objects.create(
2868+
**{field.name: EnumClass.TWO | EnumClass.FOUR}
2869+
),
2870+
self.RELATED_CLASS.objects.create(**{field.name: EnumClass.FIVE}),
2871+
self.RELATED_CLASS.objects.create(
2872+
**{
2873+
field.name: (
2874+
EnumClass.ONE | EnumClass.TWO | EnumClass.THREE |
2875+
EnumClass.FOUR | EnumClass.FIVE
2876+
)
2877+
}
2878+
)
2879+
])
2880+
for rel in related[-1]:
2881+
rel.related_flags.add(obj)
2882+
2883+
for obj in self.MODEL_CLASS.objects.annotate(
2884+
exact_matches=Count('related_flags__id', filter=Q(**{f'related_flags__{field.name}__exact': F(field.name)}))
2885+
):
2886+
self.assertEqual(obj.exact_matches, 1)
2887+
2888+
for idx, (expected, obj) in enumerate(zip(
2889+
[2, 2, 3, 3, 1],
2890+
self.MODEL_CLASS.objects.annotate(
2891+
all_matches=Count('related_flags__id', filter=Q(**{f'related_flags__{field.name}__has_all': F(field.name)}))
2892+
).order_by('id')
2893+
)):
2894+
self.assertEqual(obj.all_matches, expected)
2895+
2896+
for idx, (expected, obj) in enumerate(zip(
2897+
[4, 2, 3, 3, 5],
2898+
self.MODEL_CLASS.objects.annotate(
2899+
any_matches=Count('related_flags__id', filter=Q(**{f'related_flags__{field.name}__has_any': F(field.name)}))
2900+
).order_by('id')
2901+
)):
2902+
self.assertEqual(obj.any_matches, expected)
2903+
28252904
def test_unsupported_flags(self):
28262905
obj = self.MODEL_CLASS.objects.create()
28272906
for field in [
@@ -2852,6 +2931,7 @@ def test_unsupported_flags(self):
28522931
from django_enum.tests.enum_prop.models import (
28532932
BitFieldModel,
28542933
EnumFlagPropTester,
2934+
EnumFlagPropTesterRelated,
28552935
EnumTester,
28562936
MyModel,
28572937
)
@@ -4724,6 +4804,7 @@ def test_validate(self):
47244804
class FlagTestsProp(FlagTests):
47254805

47264806
MODEL_CLASS = EnumFlagPropTester
4807+
RELATED_CLASS = EnumFlagPropTesterRelated
47274808

47284809
def test_prop_enum(self):
47294810

0 commit comments

Comments
 (0)