How to generate a random number for a model field using Factory Boy
Asked Answered
C

4

8

I need to create some fake data using factory boy. I have the following model:

class Fabric(models.Model):
    title = models.CharField(max_length=200, blank=True)
    description = models.CharField(max_length=200, blank=True)
    price = models.DecimalField(decimal_places=2, max_digits=10, null=True, blank=False)

I need to create a factory based on this model, and I would like the price to have a random value between 1 and 100.

class FabricFactory(DjangoModelFactory):
    class Meta:
        model = Fabric

    title = factory.Faker('name')
    description = factory.Faker('catch_phrase')
    price = random.randrange(MIN_PRICE, MAX_PRICE + 1)

The problem with this is that I am always getting the same price for every instance.

Cahn answered 30/4, 2019 at 1:40 Comment(5)
if I am understanding correctly (I don't have any experience with factory_boy) it is a fixture, that gets reused, it seems it's only being instantiated once and the scope of this fixture is your whole session. is there any way to modify the scope of your fixture within factory_boy?Oasis
Try a fuzzy attribute.Paganize
@KlausD., I thought of this option, but it seems using a lazy attribute is simpler.Cahn
@aws_appretice, thanks for your hint. It is definitely what using a lazy attribute does.Cahn
glad you figured it out :thumbsup:Oasis
C
9

I was able to figure out this problem by using a lazy attribute (factory.LazyAttribute). From the docs:

Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as fields whose value is computed from other elements) will need values assigned each time an instance is generated.

class FabricFactory(DjangoModelFactory):
    class Meta:
        model = Fabric

    title = factory.Faker('name')
    description = factory.Faker('catch_phrase')
    price = factory.LazyAttribute(random.randrange(MIN_PRICE, MAX_PRICE + 1))
Cahn answered 30/4, 2019 at 2:1 Comment(3)
In my case I needed to use lambda function number_field = factory.LazyAttribute(lambda x: random.randrange(0, 10000))Allogamy
Yeah this example will generate a single random number at import-time ...Krissykrista
this returns TypeError: 'int' object is not callableZetana
A
12

You can use python providers as well.

class MyModel(factory.django.DjangoModelFactory)
  number_field = factory.Faker('pyint', min_value=0, max_value=1000)
  class Meta:
    model = SomeModel

Documentation

Adur answered 5/7, 2021 at 19:4 Comment(3)
Did adding min_value or max_value worked for you? I'm trying to do the same with pydecimal but adding left_digits and right_digits still crashes my tests with numeric field overflowHughey
Sorry for the late response. Yes what I posted worked correctly for me. Try adjusting your left and right digits to a smaller number, also debugging to see which number is generating and why is giving you that error.Adur
I always forget faker has a python provider! There's also a pydecimal and a pyfloat provider (which is what I needed)Judge
C
9

I was able to figure out this problem by using a lazy attribute (factory.LazyAttribute). From the docs:

Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as fields whose value is computed from other elements) will need values assigned each time an instance is generated.

class FabricFactory(DjangoModelFactory):
    class Meta:
        model = Fabric

    title = factory.Faker('name')
    description = factory.Faker('catch_phrase')
    price = factory.LazyAttribute(random.randrange(MIN_PRICE, MAX_PRICE + 1))
Cahn answered 30/4, 2019 at 2:1 Comment(3)
In my case I needed to use lambda function number_field = factory.LazyAttribute(lambda x: random.randrange(0, 10000))Allogamy
Yeah this example will generate a single random number at import-time ...Krissykrista
this returns TypeError: 'int' object is not callableZetana
U
4

In my case, LazyAttribute didn't worked.

I found the best way to generate a 10 digit random number using factory boy was something like this:

import factory.fuzzy

class CustomUserFactory(DjangoModelFactory):
        phone_number = factory.fuzzy.FuzzyInteger(9000000000, 9999999999)

The parameters provided inside FuzzyInteger() are the lower limit and the upper limit between which the random numbers will be generated.

Umbles answered 13/5, 2021 at 11:26 Comment(1)
Fuzzy is deprecated in favour of faker, so although it works, I would not recommend this approachJudge
S
2

You can use FuzzyDecimal.

import factory.fuzzy 


class ExampleModel(DjangoModelFactory):
    price = factory.fuzzy.FuzzyDecimal(low=min_price, high=max_price, precision=numbers_of_decimals_you_need)

already used.

Salcido answered 8/7, 2022 at 9:54 Comment(1)
Fuzzy is deprecated in favour of faker, so although it works, I would not recommend this approachJudge

© 2022 - 2024 — McMap. All rights reserved.