Reputation: 13
Using Factory Boy and Faker in a Django project, i would like to create deduced attributes, meaning a method from Faker
returns a set of values that are dependent on each other. These values will be utilized for other attributes of the Factory Class.
Example:
The location_on_land
method from faker.providers.geo
returns a tuple of latitude, longitude, place name, two-letter country code and timezone (e.g. ('38.70734', '-77.02303', 'Fort Washington', 'US', 'America/New_York'). All values depend on each other, since the coordinates define the place, country, timezone, etc. Therefor i cannot use separated functions for the (lazy) attributes like faker.providers.latitude
, faker.providers.longitude
.
Is it possible to somehow access the return values from the lazy attribute to use them for other deduced attributes? Is there a better way to access the return values from location_on_land
to use them on the other depending attributes
My Factory-Class looks like this:
# factory_models.py
class OriginFactory(factory.django.DjangoModelFactory):
"""Factory for Origin."""
class Meta:
model = models.Origin
exclude = [
"geo_data",
]
# not working since not a lazy attribute
# lat, lon, place, iso_3166_alpha_2, _ = fake.location_on_land()
# desired outcome (not working)
geo_data = factory.Faker("location_on_land")
# attributes using the return values of `location_on_land` from lazy attribute (pseudo-code, desired)
latitude = geo_data[...][0]
longitude = geo_data[...][1]
# foreign key
iso_3166_alpha_2 = models.Country.objects.get(iso_3166_alpha_2=geo_data[...][2])
...
# models.py
class Origin(models.Model):
origin_id = models.AutoField(
primary_key=True,
db_column="origin_id",
)
latitude = models.FloatField(
null=True,
db_column="latitude",
)
longitude = models.FloatField(
null=True,
db_column="longitude",
)
region = models.CharField(
max_length=100,
blank=True,
db_column="region",
)
iso_3166_alpha_2 = models.ForeignKey(
to=Country,
on_delete=models.PROTECT,
db_column="iso_3166_alpha_2",
...
)
Upvotes: 1
Views: 2571
Reputation: 3589
When using factory.Params
, the resulting attribute is available on the first parameter on factory.LazyAttribute
: the passed-in instance is a stub used to build the parameters for the actual model call.
In your case, I'd go with the following factory.
Note that the geo_data
field is declared as a parameter (i.e not passed to the models.Origin
constructor); but it is still available to other declarations on the factory.
class OriginFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Origin
class Params:
geo_data = factory.Faker('location_on_land')
latitude = factory.LazyAttribute(lambda o: o.geo_data[0])
longitude = factory.LazyAttribute(lambda o: o.geo_data[1])
iso_3166_alpha_2 = factory.LazyAttribute(
lambda o: models.Country.objects.get(iso_3166_alpha_2=o.geo_data[3])
)
Upvotes: 3