Mauro Baraldi
Mauro Baraldi

Reputation: 6575

Factory Boy with Django ManyToMany models

I have 3 models on the same module, app.models.py, as following. Some other models may appear in code but it isn't relevant.

Optionals

class Optional(models.Model):
    name = models.CharField(_('Nome'), max_length=255)
    type = models.CharField(_('Tipo'), max_length=50, null=True, blank=True)
    description = models.TextField(_('Descrição'), null=True, blank=True)
    provider = models.ForeignKey('providers.Provider', null=True, blank=True)
    charge = models.ForeignKey('Charge', null=True, blank=True)

    def __str__(self):
        return self.name

Coverage

class Coverage(models.Model):
    code = models.CharField(_('Código'), max_length=20, null=True, blank=True)
    vehicle_code = models.CharField(_('Código do veículo (ACRISS)'), max_length=4, null=True, blank=True)
    charge = models.ForeignKey('Charge', null=True, blank=True)

Vehicle

class Vehicle(models.Model):

    code = models.CharField(_('Código'), max_length=100)
    description = models.CharField(_('Descrição'), max_length=255, null=True, blank=True)
    model = models.CharField(_('Modelo'), max_length=100, null=True, blank=True)
    brand = models.CharField(_('Fabricante'), max_length=100, null=True, blank=True)
    group = models.ForeignKey('Group', null=True, blank=True)
    optionals = models.ManyToManyField('Optional', related_name='vehicle_optional')
    coverages = models.ManyToManyField('Coverage', related_name='vehicle_coverage')

    def __str__(self):
        return self.code

I'm trying create fixtures from this models using factory_boy.

class CoverageFactory(factory.Factory):
    class Meta:
        model = Coverage
    charge = factory.SubFactory(ChargeFactory)

class OptionalFactory(factory.Factory):
    class Meta:
        model = Optional
    provider = factory.SubFactory(ProviderFactory)
    charge = factory.SubFactory(ChargeFactory)

class VehicleFactory(factory.Factory):
    class Meta:
        model = Vehicle
    group = factory.SubFactory(GroupFactory)
    optionals = factory.SubFactory(OptionalFactory)
    coverages = factory.SubFactory(CoverageFactory)

On my tests it is instantiated this way:

optional = OptionalFactory(
    name="GPS",
    type="13",
    description="",
    charge=charge,
    provider=provider
)

coverage = CoverageFactory(
    code="ALI",
    vehicle_code="ABCD",
    charge=charge
)

vehicle = VehicleFactory(
    code="ECMM",
    description="GRUPO AX - MOVIDA ON",
    model="MOBI LIKE, OU SIMILAR",
    brand="",
    optionals=optional,
    coverages=coverage
)

And when I run tests, with pytest-django, I get this error.

ValueError: "<Vehicle: ECMM>" needs to have a value for field "id" before this many-to-many relationship can be used.

I've read the factory_boy docs about Simple Many-to-many relationship and Many-to-many relation with a ‘through’ but can't fix.

Upvotes: 1

Views: 1935

Answers (2)

Jonah T
Jonah T

Reputation: 83

You've already pointed to the correct place in the docs, which should work: https://factoryboy.readthedocs.io/en/latest/recipes.html#simple-many-to-many-relationship

class VehicleFactory(factory.Factory):
    ...
    # no optionals subfactory here
    ...

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

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

Then you'd call with something like:

VehicleFactory.create(optionals=[optional1, optional2])

Hopefully that answers the question? It's hard to be more helpful without more information about what didn't work when you tried the solution in the docs

Upvotes: 1

user2829759
user2829759

Reputation: 3512

When calling UserFactory() or UserFactory.build(), no group binding will be created.

But when UserFactory.create(groups=(group1, group2, group3)) is called, the groups declaration will add passed in groups to the set of groups for the user.

the id field will be generated when you

optional = OptionalFactory.create(  # note the .create()
    name="GPS",
    type="13",
    description="",
    charge=charge,
    provider=provider
)

Then when you

vehicle = VehicleFactory.create(
    ...
    optionals=(optional,),
)

The many-to-many optionals can be established. Notes also the arguments to optionals is (optionals,). The function expect an iterable

Upvotes: 1

Related Questions