This comes from Python's parsing rules.
Why?
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.
So, what should you do?
Here, you should use:
- Either
factory.Fakerusing Faker's random_element provider; - Or
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 timeUserFactory.create()is called.
This comes from Python's parsing rules.
Why?
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.
So, what should you do?
Here, you should use:
- Either
factory.Fakerusing Faker's random_element provider; - Or
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 timeUserFactory.create()is called.
While not truly random, the effect you're looking for when choosing from among a set of pre-existing records can also be achieved by using FactoryBoy's Iterator, which can also work with a QuerySet. For example, here I wanted every object to be created by someone different from the set of existing fake users:
from django.contrib.auth import get_user_model
...
# Then, within a factory class, for one of the fields:
created_by = factory.Iterator(get_user_model().objects.all())
Since Faker v6.3.0, the following 2 methods should do the trick:
faker.helpers.arrayElement(doc)faker.helpers.arrayElements(doc)
You can generate a number between 0 and the number of words you have and access the words using an array
const words = ["foo", "bar", "baz"]
const randomNumber = faker.datatype.number({
'min': 0,
'max': words.length - 1
});
console.log(words[randomNumber])
You'll not need a FuzzyAttribute.
You can either restrict the values possible and only give the int value of each product type to FuzzyChoice by doing something like this:
from factory import fuzzy
PRODUCT_IDS = [x[0] for x in IceCreamProduct.PRODUCT_TYPES]
class IceCreamProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = IceCreamProduct
type = fuzzy.FuzzyChoice(PRODUCT_IDS)
It should do the work.
Please be aware that fuzzy module has been deprecated recently, see Fuzzy Attributes Documentation, you may want to use a LazyFunction instead.
You can do as easy as this
class IceCreamProductFactory(factory.django.DjangoModelFactory):
icecream_flavour = factory.Faker(
'random_element', elements=[x[0] for x in IceCreamProduct.PRODUCT_TYPES]
)
class Meta:
model = IceCreamProduct
PS. Don't use type as attribute, it is a bad practice to use a built-in function name as an attribute