I'm trying to write up a Factory for a model with a GFK for testing but I can't seem to get it working. I've referred to the common recipes in the docs, but my models don't match up exactly, and I'm also running into an error. Here are my models
class Artwork(models.Model):
...
region = models.ForeignKey("Region", on_delete=models.SET_NULL, null=True, blank=True)
class Region(models.Model):
# Could be either BeaconRegion or SpaceRegion
region_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
region_object_id = models.PositiveIntegerField()
region = GenericForeignKey("region_content_type", "region_object_id")
class SpaceRegion(models.Model):
label = models.CharField(max_length=255)
regions = GenericRelation(
Region,
content_type_field="region_content_type",
object_id_field="region_object_id",
related_query_name="space_region",
)
class BeaconRegion(models.Model):
label = models.CharField(max_length=255)
regions = GenericRelation(
Region,
content_type_field="region_content_type",
object_id_field="region_object_id",
related_query_name="beacon_region",
)
Essentially, an Artwork can be placed in one of two Regions; a SpaceRegion or BeaconRegion.
I've created the following Factorys for the corresponding models
class RegionFactory(factory.django.DjangoModelFactory):
region_object_id = factory.SelfAttribute("region.id")
region_content_type = factory.LazyAttribute(
lambda o: ContentType.objects.get_for_model(o.region)
)
class Meta:
exclude = ["region"]
abstract = True
class BeaconRegionFactory(RegionFactory):
label = factory.Faker("sentence", nb_words=2)
region = factory.SubFactory(RegionFactory)
class Meta:
model = Region
class SpaceRegionFactory(RegionFactory):
label = factory.Faker("sentence", nb_words=2)
region = factory.SubFactory(RegionFactory)
class Meta:
model = Region
class ArtworkFactory(factory.django.DjangoModelFactory):
...
region = factory.SubFactory(SpaceRegionFactory)
In my test, I try to create an Artwork using ArtworkFactory(), but it errors with
AttributeError: The parameter 'region' is unknown. Evaluated attributes are {}, definitions are <DeclarationSet: {'region_object_id': <SelfAttribute('region.id', default=<class 'factory.declarations._UNSPECIFIED'>)>, 'region_content_type': <factory.declarations.LazyAttribute object at 0x1068cf430>, 'label': <factory.faker.Faker object at 0x1068cf880>}>
What am I doing wrong here?
The issue comes when resolving
ArtworkFactory.region.region, i.eSpaceRegionFactory.region.From your models, it seems that:
Regionis a table which points to eitherSpaceRegionorBeaconRegionSpaceRegionandBeaconRegionare simple tables, with a helper to retrieve the relatedRegionobject.The first step in those complex relation chains is to write the code without factories:
This tells us that the
Regionis always created after theSpaceRegionorBeaconRegion, giving the following factories:With this, you should be able to get your code working. Note how we're simply setting the
regionfield onRegion: Django's internals will extract the object content type / content ID automatically.Additional options
You could tune the
RegionFactoryto let callers decide whether they want aSpaceRegionor aBeaconRegion: