25

How to define a field that depends on other field using factory-boy?

For instance, I'd like to define an email that depends on the first name and last name of an User.

I tried using the post_generation decorator. However, my system requires the definition of the email before creating the instance.

HaveNoDisplayName
  • 8,291
  • 106
  • 37
  • 47
jsmedmar
  • 1,025
  • 10
  • 21
  • maybe show your current logic / code you already have, so we can have a better idea what you have to give a direction you can go – davejal Nov 28 '15 at 00:28

2 Answers2

33

Use LazyAttribute:

from the docs:

The LazyAttribute is a simple yet extremely powerful building brick for extending a Factory.

It takes as argument a method to call (usually a lambda); that method should accept the object being built as sole argument, and return a value.

class UserFactory(factory.Factory):
    class Meta:
        model = User

    username = 'john'
    email = factory.LazyAttribute(lambda o: '%s@example.com' % o.username)

or the lazy_attribute decorator.

from the docs:

If a simple lambda isn’t enough, you may use the lazy_attribute() decorator instead.

This decorates an instance method that should take a single argument, self; the name of the method will be used as the name of the attribute to fill with the return value of the method:

class UserFactory(factory.Factory)
    class Meta:
        model = User

    name = u"Jean"

    @factory.lazy_attribute
    def email(self):
        # Convert to plain ascii text
        clean_name = (unicodedata.normalize('NFKD', self.name)
                        .encode('ascii', 'ignore')
                        .decode('utf8'))
        return u'%s@example.com' % clean_name
Community
  • 1
  • 1
jsmedmar
  • 1,025
  • 10
  • 21
2

You could also use factory.SelfAttribute for fields that depends on others fields.

In your case, LazyAttribute works fine, and it's quite clear, but if you need to do something a little more complex, SelfAttribute it's gonna be a better option.

For example, let's say we have an entity called Course with a start_date and end_date. Each course has a final test which must be attended after the course starts and before the course ends. Then, model.py should look like this:

class Course(models.Model):
    start_date = models.DateTimeField(auto_now_add=False, blank=False)
    end_date = models.DateTimeField(auto_now_add=False, blank=False)

class Test(models.Model):
    course = models.ForeignKey(
        to=Course, blank=False, null=False, on_delete=models.CASCADE
    )
    date = models.DateField()

Now, let's create our factory.py:

class CourseFactory(DjangoModelFactory):
    class Meta:
        model = Course

    start_date = factory.Faker(
        "date_time_this_month", before_now=True, after_now=False, tzinfo=pytz.UTC
    )
    end_date = factory.Faker(
        "date_time_this_month", before_now=False, after_now=True, tzinfo=pytz.UTC
    )

class TestFactory(DjangoModelFactory):
    class Meta:
        model = Test

    date = factory.Faker(
        "date_between_dates",
        date_start=factory.SelfAttribute('..course.start_date'),
        date_end=factory.SelfAttribute('..course.end_date')
    )
    course = factory.SubFactory(CourseFactory)

As you can see in TestFactory we can reference another field of the object being constructed, or an attribute thereof.

mathias.lantean
  • 541
  • 4
  • 10
  • Shouldn't `date = factory.Faker(` read `date = factory.Maybe(` – thoroc Mar 10 '23 at 09:33
  • 1
    Not really. I mean, for this particular scenario, we have courses with a start date and end date, and we want to have a test within that date range. So, "date_between_dates" fits well. On the other hand, "factory.Maybe" takes a boolean "decider" field and two declarations. Depending on the boolean value, one of those declarations will be executed to generate the value. – mathias.lantean Mar 23 '23 at 14:47