Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ pyvenv.cfg
dist/
/.coverage
/.coverage.*
.envrc
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion docs/basic_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ 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:

**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).

```python
baker.prepare('shop.PurchaseHistory')
Expand Down
3 changes: 2 additions & 1 deletion model_bakery/baker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions tests/generic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
18 changes: 10 additions & 8 deletions tests/test_baker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down