AuthenticFood
AuthenticFood

Reputation: 13

Deduced attributes of a LazyAttribute using Factory Boy

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

Answers (1)

Xelnor
Xelnor

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

Related Questions