I have the following ModelFactory:

class ProjectDataModelFactory(DjangoModelFactory):
    project = factory.SubFactory("projects.factories.ProjectModelFactory")
    version = "1"
    content = {
        "id": {"value": fuzzy.FuzzyText(length=7, chars=string.ascii_letters).fuzz().upper(), "type": "string"},
        "win_probability": {"value": fuzzy.FuzzyInteger(0, 100).fuzz(), "type": "number"},
        "bid_value": {
            "value": fuzzy.FuzzyInteger(0, 10000000).fuzz(),
            "type": "number",
        },
        "status": {"value": random.choice(["Won", "Lost"]), "type": "string"},
        "sector": {
            "value": random.choice(
                ["Construction", "Technology", "Finance", "Consulting", "Agriculture", "Engineering"]
            ),
            "type": "string",
        },
    } 

In content["id"], I would like to have value be project.external_id instead of fuzzy.FuzzyText(length=7, chars=string.ascii_letters).fuzz().upper(). external_idis part of the ProjectModel. But when I put project.external_id in the place of the fuzzer, I get this error:

AttributeError: 'SubFactory' object has no attribute 'external_id

This only seems to be a problem with dicts. We have not had issues using it in other contexts.

I do not have a lot of experience using FactoryBoy, so it could easily be something trivial. Any ideas?

1

There are 1 answers

0
Xelnor On BEST ANSWER

You have a first issue: all the calls to random or .fuzz() that you put in the content dict are evaluated when the class is imported: each instance will use the exact same values.

In order to perform lazy evaluation, you MUST wrap them in some factory_boy-provided declaration:

class ProjectDataModelFactory(DjangoModelFactory):
    project = factory.SubFactory("projects.factories.ProjectModelFactory")
    version = "1"
    content = factory.Dict(
        id=factory.Dict(
            value=fuzzy.FuzzyText(length=7, chars=string.ascii_uppercase),
            type="string",
        ),
        win_probability=factory.Dict(
            value=fuzzy.FuzzyInteger(0, 100),
            type="number",
        ),
        bid_value=factory.Dict(
            value=fuzzy.FuzzyInteger(0, 10_000_000),
            type="number",
        ),
        status=factory.Dict(
            value=fuzzy.FuzzyChoice(["Won", "Lost"]),
            type="string",
        ),
        sector=factory.Dict(
            value=fuzzy.FuzzyChoice(
                ["Construction", "Technology", "Finance", "Consulting", "Agriculture", "Engineering"],
            ),
            type="string",
        ),
    )

factory.Dict(**kwargs) is basically an alias for factory.SubFactory(DictFactory, **kwargs); you can then use any declaration as if using a simple SubFactory.

For your initial goal, you'll now be able to use all features easily:

class ProjectDataModelFactory(DjangoModelFactory):
    project = factory.SubFactory("projects.factories.ProjectModelFactory")
    version = "1"
    content = factory.Dict(
        id=factory.Dict(
            # . is the {"value": x, "type": "string"} dict;
            # .. is {"id": {"value": x, ...}} dict;
            # ... is the ProjectData(content={"id": {...}}) object.
            value=factory.SelfAttribute('...project.external_id'),
            type="string",
        ),
        # Other fields here
    )