I am trying to use factory.faker to randomly choose from a list of four companies and use them as a traffic source for a list of generated names. I am using the below code:
from django.db import models
import factory
import factory.django
from datetime import datetime
from django.core.validators import MinValueValidator, MaxValueValidator
from faker import Faker
from faker.providers import BaseProvider
import random
fake = Faker()
class User(models.Model):
name = models.CharField(max_length=64)
address = models.CharField(max_length=128)
phone_number = models.CharField(max_length=32)
login_date = models.DateTimeField(default=datetime.now(), blank=True)
session_duration = models.IntegerField(default = 0, validators= [
MinValueValidator(0),
MaxValueValidator(5)
])
traffic_source = models.CharField(max_length=32)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
name = factory.Faker('name')
address = factory.Faker('address')
phone_number = factory.Faker('phone_number')
login_date = factory.Faker('date')
session_duration = factory.Faker('random_int')
traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])
The issue is that for all 200 iterations I perform using the following in the python shell:
for _ in range(200):
UserFactory.create()
I get the same company for every name, i.e. 'XYZ' for all 200 names.
Am I missing something? I want to get a different company for each of the 200 iterations. Any help is much appreciated. Thank you!
This comes from Python's parsing rules.
When you write this:
class UserFactory(factory.django.DjangoModelFactory):
⋮
traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])
Python will execute the following steps:
Read the class declaration body;
Reach the line traffic_source = random.choice(['XYZ', 'ABC', '123', '456']);
Evaluate the call to random.choice, which might return 'ABC';
Once each line of the class body has been read (and its function calls evaluated), create the class:
UserFactory = type(
name='UserFactory',
bases=[factory.django.DjangoModelFactory],
{'traffic_source': 'ABC', … },
)
As you can see, the call to random.choice is performed only once, when parsing the class declaration.
This is, basically, the reason for all the factory.XXX declarations: they yield an object that will only execute its specific rules when building an instance from the factory.
Here, you should use:
factory.Faker using Faker's random_element provider;factory.fuzzy.FuzzyChoice:class UserFactory(factory.django.DjangoModelFactory):
⋮
traffic_source = factory.Faker('random_element', elements=['XYZ', 'ABC', '123', '456'])
alt_traffic_source = factory.fuzzy.FuzzyChoice(['XYZ', 'ABC', '123', '456'])
The main difference between factory.Faker('random_choices') and factory.fuzzy.FuzzyChoices is that factory.fuzzy.FuzzyChoices supports lazily evaluating generators; this is useful if you want to choose from a queryset:
factory.Faker('random_element', elements=Company.objects.all()) will perform a DB query at import time;factory.fuzzy.FuzzyChoice(Company.objects.all()) will only query the DB the first time UserFactory.create() is called.