Rahul Sarma
Rahul Sarma

Reputation: 427

Can you override a model function for factory_boy?

I'm using Factory boy for my models. Another model has a ForeignKey to it, and the first obj of that model is used for string representation of the model. Example structure:

class A(models.Model):
    ...

    def __str__(self):
    return self.reverseForeignKey_set.first().name

class AFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = models.A

    some_attribute = factory.RelatedFactory(
        ReverseModelFactory,
        'a',
        )

But when I use factory_boy to create, it gives me this error:

Traceback (most recent call last):
  File "/home/rahul/Documents/projects/healbot_current/healbot/functional_tests/tests.py", line 21, in setUp
    factories.AFactory.create()
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 623, in create
    return cls._generate(True, attrs)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 554, in _generate
    results[name] = decl.call(obj, create, extraction_context)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/declarations.py", line 624, in call
    return factory.simple_generate(create, **passed_kwargs)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 709, in simple_generate
    return cls.generate(strategy, **kwargs)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 676, in generate
    return action(**kwargs)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 622, in create
    attrs = cls.attributes(create=True, extra=kwargs)
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/base.py", line 449, in attributes
    utils.log_repr(extra),
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/factory/utils.py", line 142, in log_repr
    return compat.force_text(repr(obj))
  File "/home/rahul/Envs/healbot/lib/python3.6/site-packages/django/db/models/base.py", line 588, in __repr__
    u = six.text_type(self)
  File "/home/rahul/Documents/projects/healbot_current/healbot/diagnosis/models.py", line 57, in __str__
    return self.reverseForeignKey_set.first().name
AttributeError: 'NoneType' object has no attribute 'name'

Is there something I can do to get around this? Like override the str method on model A just for factory_boy?

Upvotes: 1

Views: 3230

Answers (1)

A. Grinenko
A. Grinenko

Reputation: 331

You have such a problem because A model instance is creating earlier than a ReverseModel instance.

As a factory_boy documentation says:

RelatedFactory: this builds or creates a given factory after building/creating the first Factory.

See http://factoryboy.readthedocs.io/en/latest/reference.html#post-generation-hooks

First of all you should create your ReverseModel and then your A model instance. As the factory_boy documentation says in recepies:

When one attribute is actually a complex field (e.g a ForeignKey to another Model), use the SubFactory declaration

So you can define a factory for your FK model and define it as a SubFactory in an AFactory.

# models.py
class A(models.Model):
    ...

    def __str__(self):
        return self.reverseForeignKey_set.first().name

# factories.py
import factory
from . import models

class ReverseModelFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.ReverseModel

        # Define your model fields
        attr = factory.Faker('text', max_nb_chars=255)

class AFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.A

    some_attribute = factory.SubFactory(ReverseModelFactory)

In this case your ReverseModelFactory object will be created at first that should solve your problem. See http://factoryboy.readthedocs.io/en/latest/recipes.html for more information.

I should also note that if you want to handle ManyToMany field, you shall use the post_generation hook:

# models.py
class Group(models.Model):
    name = models.CharField()

class User(models.Model):
    name = models.CharField()
    groups = models.ManyToManyField(Group)


# factories.py
class GroupFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Group

    name = factory.Sequence(lambda n: "Group #%s" % n)

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.User

    name = "John Doe"

    @factory.post_generation
    def groups(self, create, extracted, **kwargs):
        if not create:
            # Simple build, do nothing.
            return

        if extracted:
            # A list of groups were passed in, use them
            for group in extracted:
                self.groups.add(group)

See http://factoryboy.readthedocs.io/en/latest/recipes.html#simple-many-to-many-relationship for more information.

Upvotes: 1

Related Questions