Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait drops SubFactory default value, at least with Django #542

Open
nbonnotte opened this issue Nov 23, 2018 · 3 comments
Open

Trait drops SubFactory default value, at least with Django #542

nbonnotte opened this issue Nov 23, 2018 · 3 comments

Comments

@nbonnotte
Copy link

Description

When using Trait to change the value of a dependant object that has been defined with a default value, this default value is dropped when building / creating.

Model / Factory code
import factory

import django
import django.db.models as models
from django.conf import settings
settings.configure()
django.setup()

class Foo(models.Model):
    value = models.IntegerField(default=0)
    class Meta:
        app_label = 'test'
        
class Bar(models.Model):
    foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
    class Meta:
        app_label = 'test'

class FooFactory(factory.DjangoModelFactory):
    value = 1
    class Meta:
        model = Foo
        
class BarFactory(factory.DjangoModelFactory):
    foo = factory.SubFactory(FooFactory, value=2)
    class Meta:
        model = Bar
    class Params:
        done = factory.Trait(
            foo__value=3,
        )
The issue

BarFactory.build().foo.value should be 2, but it ends up being 0.

If the value=2 in the BarFactory.foo SubFactory is removed, the value should be 1, but it still ends up being 0.

The behavior is the same with .create() instead of .build()

Notes

Factory version is 2.11.1

@rbarrois
Copy link
Member

Thanks for the report! This is indeed a nasty bug.

Under the hood, we end up with the following declaration:

>>> BarFactory._meta.declarations
{'foo': <factory.declarations.SubFactory object at 0x7f65070f8320>,
 'foo__value': Maybe(
      <SelfAttribute('done', default=False)>,
      yes=3,
      no=<factory.declarations.Skip object at 0x7f650afa71d0>
 )}

This means that:

  • The foo__value part of the trait is not configured properly into the SubFactory (doesn't look at the done flag in the right place);
  • It doesn't take into account the default value provided in the SubFactory nor in the default factory.

@roxt-underground
Copy link

I tried to fix this with my own custom Trait. May be you can use it to fix this bug

class CustomTrait(Trait):
    def as_declarations(self, field_name, declarations):
        overrides = {}
        for maybe_field, new_value in self.overrides.items():
            overrides[maybe_field] = Maybe(
                decider=SelfAttribute(
                    '%s.%s' % (
                        '.' * maybe_field.count(enums.SPLITTER),
                        field_name,
                    ),
                    default=False,
                ),
                yes_declaration=new_value,
                no_declaration=_get_maybe_field(declarations, maybe_field),
            )
        return overrides


def _get_maybe_field(declarations, maybe_field):
    field_chain = maybe_field.split('__')
    default_factory = declarations.get(field_chain[0])

    if not default_factory:
        return SKIP

    if type(default_factory) is SubFactory:
        sub_maybe_field = '__'.join(field_chain[1:])
        sub_declarations = default_factory.factory_wrapper.factory._meta.declarations
        return _get_maybe_field(sub_declarations, sub_maybe_field)

    if issubclass(type(default_factory), BaseDeclaration) and len(field_chain) == 1:
        return default_factory

    return SKIP

@Maushundb
Copy link

Getting the same issue, but just extending BaseDictFactory, no Django needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants