import json import uuid from django.core import exceptions, serializers from django.db import IntegrityError, connection, models from django.db.models import CharField, F, Value from django.db.models.functions import Concat, Repeat from django.test import ( SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature, ) from .models import ( NullableUUIDModel, PrimaryKeyUUIDModel, RelatedToUUIDModel, UUIDGrandchild, UUIDModel, ) class TestSaveLoad(TestCase): def test_uuid_instance(self): instance = UUIDModel.objects.create(field=uuid.uuid4()) loaded = UUIDModel.objects.get() self.assertEqual(loaded.field, instance.field) def test_str_instance_no_hyphens(self): UUIDModel.objects.create(field='550e8400e29b41d4a716446655440000') loaded = UUIDModel.objects.get() self.assertEqual(loaded.field, uuid.UUID('550e8400e29b41d4a716446655440000')) def test_str_instance_hyphens(self): UUIDModel.objects.create(field='550e8400-e29b-41d4-a716-446655440000') loaded = UUIDModel.objects.get() self.assertEqual(loaded.field, uuid.UUID('550e8400e29b41d4a716446655440000')) def test_str_instance_bad_hyphens(self): UUIDModel.objects.create(field='550e84-00-e29b-41d4-a716-4-466-55440000') loaded = UUIDModel.objects.get() self.assertEqual(loaded.field, uuid.UUID('550e8400e29b41d4a716446655440000')) def test_null_handling(self): NullableUUIDModel.objects.create(field=None) loaded = NullableUUIDModel.objects.get() self.assertIsNone(loaded.field) def test_pk_validated(self): with self.assertRaisesMessage(exceptions.ValidationError, 'is not a valid UUID'): PrimaryKeyUUIDModel.objects.get(pk={}) with self.assertRaisesMessage(exceptions.ValidationError, 'is not a valid UUID'): PrimaryKeyUUIDModel.objects.get(pk=[]) def test_wrong_value(self): with self.assertRaisesMessage(exceptions.ValidationError, 'is not a valid UUID'): UUIDModel.objects.get(field='not-a-uuid') with self.assertRaisesMessage(exceptions.ValidationError, 'is not a valid UUID'): UUIDModel.objects.create(field='not-a-uuid') class TestMethods(SimpleTestCase): def test_deconstruct(self): field = models.UUIDField() name, path, args, kwargs = field.deconstruct() self.assertEqual(kwargs, {}) def test_to_python(self): self.assertIsNone(models.UUIDField().to_python(None)) def test_to_python_int_values(self): self.assertEqual( models.UUIDField().to_python(0), uuid.UUID('00000000-0000-0000-0000-000000000000') ) # Works for integers less than 128 bits. self.assertEqual( models.UUIDField().to_python((2 ** 128) - 1), uuid.UUID('ffffffff-ffff-ffff-ffff-ffffffffffff') ) def test_to_python_int_too_large(self): # Fails for integers larger than 128 bits. with self.assertRaises(exceptions.ValidationError): models.UUIDField().to_python(2 ** 128) class TestQuerying(TestCase): @classmethod def setUpTestData(cls): cls.objs = [ NullableUUIDModel.objects.create( field=uuid.UUID('25d405be-4895-4d50-9b2e-d6695359ce47'), ), NullableUUIDModel.objects.create(field='550e8400e29b41d4a716446655440000'), NullableUUIDModel.objects.create(field=None), ] def assertSequenceEqualWithoutHyphens(self, qs, result): """ Backends with a native datatype for UUID don't support fragment lookups without hyphens because they store values with them. """ self.assertSequenceEqual( qs, [] if connection.features.has_native_uuid_field else result, ) def test_exact(self): self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__exact='550e8400e29b41d4a716446655440000'), [self.objs[1]] ) self.assertSequenceEqual( NullableUUIDModel.objects.filter( field__exact='550e8400-e29b-41d4-a716-446655440000' ), [self.objs[1]], ) def test_iexact(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter( field__iexact='550E8400E29B41D4A716446655440000' ), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter( field__iexact='550E8400-E29B-41D4-A716-446655440000' ), [self.objs[1]], ) def test_isnull(self): self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__isnull=True), [self.objs[2]] ) def test_contains(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__contains='8400e29b'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__contains='8400-e29b'), [self.objs[1]], ) def test_icontains(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__icontains='8400E29B'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__icontains='8400-E29B'), [self.objs[1]], ) def test_startswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__startswith='550e8400e29b4'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__startswith='550e8400-e29b-4'), [self.objs[1]], ) def test_istartswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__istartswith='550E8400E29B4'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__istartswith='550E8400-E29B-4'), [self.objs[1]], ) def test_endswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__endswith='a716446655440000'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__endswith='a716-446655440000'), [self.objs[1]], ) def test_iendswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__iendswith='A716446655440000'), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.filter(field__iendswith='A716-446655440000'), [self.objs[1]], ) def test_filter_with_expr(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.annotate( value=Concat(Value('8400'), Value('e29b'), output_field=CharField()), ).filter(field__contains=F('value')), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.annotate( value=Concat(Value('8400'), Value('-'), Value('e29b'), output_field=CharField()), ).filter(field__contains=F('value')), [self.objs[1]], ) self.assertSequenceEqual( NullableUUIDModel.objects.annotate( value=Repeat(Value('0'), 4, output_field=CharField()), ).filter(field__contains=F('value')), [self.objs[1]], ) class TestSerialization(SimpleTestCase): test_data = ( '[{"fields": {"field": "550e8400-e29b-41d4-a716-446655440000"}, ' '"model": "model_fields.uuidmodel", "pk": null}]' ) nullable_test_data = ( '[{"fields": {"field": null}, ' '"model": "model_fields.nullableuuidmodel", "pk": null}]' ) def test_dumping(self): instance = UUIDModel(field=uuid.UUID('550e8400e29b41d4a716446655440000')) data = serializers.serialize('json', [instance]) self.assertEqual(json.loads(data), json.loads(self.test_data)) def test_loading(self): instance = list(serializers.deserialize('json', self.test_data))[0].object self.assertEqual(instance.field, uuid.UUID('550e8400-e29b-41d4-a716-446655440000')) def test_nullable_loading(self): instance = list(serializers.deserialize('json', self.nullable_test_data))[0].object self.assertIsNone(instance.field) class TestValidation(SimpleTestCase): def test_invalid_uuid(self): field = models.UUIDField() with self.assertRaises(exceptions.ValidationError) as cm: field.clean('550e8400', None) self.assertEqual(cm.exception.code, 'invalid') self.assertEqual(cm.exception.message % cm.exception.params, '“550e8400” is not a valid UUID.') def test_uuid_instance_ok(self): field = models.UUIDField() field.clean(uuid.uuid4(), None) # no error class TestAsPrimaryKey(TestCase): def test_creation(self): PrimaryKeyUUIDModel.objects.create() loaded = PrimaryKeyUUIDModel.objects.get() self.assertIsInstance(loaded.pk, uuid.UUID) def test_uuid_pk_on_save(self): saved = PrimaryKeyUUIDModel.objects.create(id=None) loaded = PrimaryKeyUUIDModel.objects.get() self.assertIsNotNone(loaded.id, None) self.assertEqual(loaded.id, saved.id) def test_uuid_pk_on_bulk_create(self): u1 = PrimaryKeyUUIDModel() u2 = PrimaryKeyUUIDModel(id=None) PrimaryKeyUUIDModel.objects.bulk_create([u1, u2]) # The two objects were correctly created. u1_found = PrimaryKeyUUIDModel.objects.filter(id=u1.id).exists() u2_found = PrimaryKeyUUIDModel.objects.exclude(id=u1.id).exists() self.assertTrue(u1_found) self.assertTrue(u2_found) self.assertEqual(PrimaryKeyUUIDModel.objects.count(), 2) def test_underlying_field(self): pk_model = PrimaryKeyUUIDModel.objects.create() RelatedToUUIDModel.objects.create(uuid_fk=pk_model) related = RelatedToUUIDModel.objects.get() self.assertEqual(related.uuid_fk.pk, related.uuid_fk_id) def test_update_with_related_model_instance(self): # regression for #24611 u1 = PrimaryKeyUUIDModel.objects.create() u2 = PrimaryKeyUUIDModel.objects.create() r = RelatedToUUIDModel.objects.create(uuid_fk=u1) RelatedToUUIDModel.objects.update(uuid_fk=u2) r.refresh_from_db() self.assertEqual(r.uuid_fk, u2) def test_update_with_related_model_id(self): u1 = PrimaryKeyUUIDModel.objects.create() u2 = PrimaryKeyUUIDModel.objects.create() r = RelatedToUUIDModel.objects.create(uuid_fk=u1) RelatedToUUIDModel.objects.update(uuid_fk=u2.pk) r.refresh_from_db() self.assertEqual(r.uuid_fk, u2) def test_two_level_foreign_keys(self): gc = UUIDGrandchild() # exercises ForeignKey.get_db_prep_value() gc.save() self.assertIsInstance(gc.uuidchild_ptr_id, uuid.UUID) gc.refresh_from_db() self.assertIsInstance(gc.uuidchild_ptr_id, uuid.UUID) class TestAsPrimaryKeyTransactionTests(TransactionTestCase): # Need a TransactionTestCase to avoid deferring FK constraint checking. available_apps = ['model_fields'] @skipUnlessDBFeature('supports_foreign_keys') def test_unsaved_fk(self): u1 = PrimaryKeyUUIDModel() with self.assertRaises(IntegrityError): RelatedToUUIDModel.objects.create(uuid_fk=u1)