diff --git a/.gitignore b/.gitignore index 17607d43..12800b06 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ pyvenv.cfg dist/ /.coverage /.coverage.* +.envrc diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac3734c..2ee179ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - [dev] Various improvements to CI Python/Django matrices +- The creation of generic foreign key fields now respects their `for_concrete_model` configuration ### Removed - Drop fallbacks made for Django < 4.2 diff --git a/docs/basic_usage.md b/docs/basic_usage.md index e15170c3..978461c9 100644 --- a/docs/basic_usage.md +++ b/docs/basic_usage.md @@ -106,7 +106,7 @@ class PurchaseHistoryTestModel(TestCase): It will also create the Customer, automagically. -**NOTE: ForeignKeys and OneToOneFields** - Since Django 1.8, ForeignKey and OneToOne fields don't accept unpersisted model instances anymore. This means that if you run: +**NOTE: ForeignKeys and OneToOneFields** - Since Django 1.8, ForeignKey and OneToOne fields don't accept non persisted model instances anymore. This means that if you run: ```python baker.prepare('shop.PurchaseHistory') @@ -114,6 +114,9 @@ baker.prepare('shop.PurchaseHistory') You'll end up with a persisted "Customer" instance. +**NOTE: GenericForeignKey** - `model-bakery` defines the content type for this relation based in how +the relation configures their [`for_concrete_model` flag](https://docs.djangoproject.com/en/5.2/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericForeignKey.for_concrete_model). + ## M2M Relationships By default, Model Bakery doesn't create related instances for many-to-many relationships. diff --git a/model_bakery/baker.py b/model_bakery/baker.py index 8b963ef8..0a76ed53 100644 --- a/model_bakery/baker.py +++ b/model_bakery/baker.py @@ -521,6 +521,7 @@ def instance( "value": attrs.pop(k), "content_type_field": field.ct_field, "object_id_field": field.fk_field, + "for_concrete_model": field.for_concrete_model, } instance = self.model(**attrs) @@ -719,7 +720,7 @@ def _handle_generic_foreign_keys(self, instance: Model, attrs: dict[str, Any]): instance, ct_field_name, contenttypes_models.ContentType.objects.get_for_model( - value, for_concrete_model=False + value, for_concrete_model=data["for_concrete_model"] ), ) setattr(instance, oid_field_name, value.pk) diff --git a/tests/generic/models.py b/tests/generic/models.py index 4c14c373..bcba9326 100755 --- a/tests/generic/models.py +++ b/tests/generic/models.py @@ -154,6 +154,11 @@ class Person(models.Model): geom_collection = models.GeometryCollectionField() +class ProxyToPersonModel(Person): + class Meta: + proxy = True + + class Dog(models.Model): class Meta: order_with_respect_to = "owner" @@ -302,6 +307,18 @@ class DummyGenericForeignKeyModel(models.Model): object_id = models.PositiveIntegerField() content_object = GenericForeignKey("content_type", "object_id") + proxy_content_type = models.ForeignKey( + contenttypes.ContentType, + on_delete=models.CASCADE, + blank=True, + null=True, + limit_choices_to={"model__in": ["person", "dog"]}, + ) + proxy_object_id = models.PositiveIntegerField() + proxy_content_object = GenericForeignKey( + "proxy_content_type", "proxy_object_id", for_concrete_model=False + ) + class DummyGenericRelationModel(models.Model): relation = GenericRelation(DummyGenericForeignKeyModel) diff --git a/tests/test_baker.py b/tests/test_baker.py index d9c52092..71786bf3 100644 --- a/tests/test_baker.py +++ b/tests/test_baker.py @@ -649,23 +649,25 @@ def get_dummy_key(): assert isinstance(dummy.content_type, ContentType) assert isinstance(dummy.content_object, models.Person) - def test_create_model_with_contenttype_field_and_proxy_model(self): + def test_create_model_with_contenttype_field_and_proxy_model_respecting_generic_fK_config( + self, + ): from django.contrib.contenttypes.models import ContentType - class ProxyPerson(models.Person): - class Meta: - proxy = True - app_label = "generic" - + proxy_person = baker.make(models.ProxyToPersonModel, name="John Doe") dummy = baker.make( models.DummyGenericForeignKeyModel, - content_object=baker.make(ProxyPerson, name="John Doe"), + content_object=proxy_person, + proxy_content_object=proxy_person, ) dummy.refresh_from_db() assert isinstance(dummy, models.DummyGenericForeignKeyModel) assert isinstance(dummy.content_type, ContentType) - assert isinstance(dummy.content_object, ProxyPerson) + assert isinstance(dummy.content_object, models.Person) assert dummy.content_object.name == "John Doe" + assert isinstance(dummy.proxy_content_type, ContentType) + assert isinstance(dummy.proxy_content_object, models.ProxyToPersonModel) + assert dummy.proxy_content_object.name == "John Doe" @pytest.mark.skipif(