question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Factory for Django model linking to self with related_name

See original GitHub issue

Guys, Please advise how can I make factory with FactoryBoy 2.6.0 and python 2.7 from django model below . Each object may have ‘next’ object, and if some object is the ‘next’ for another object, then it knows this object by ‘previous’ attribute.

class MyModel(models.Model):
    name = models.CharField('Name', max_length=128)
    next = models.OneToOneField('self', related_name='previous', 
                                                     on_delete=models.SET_NULL, 
                                                      null=True, blank=True)

I tried following Factory:

class MyModelFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = 'app.MyModel'

    next = factory.SubFactory('pp.app.tests.factories.MyModelFactory', previous=None)
    previous = factory.RelatedFactory('pp.app.tests.factories.MyModelFactory', 'next')

But, got the “RuntimeError: maximum recursion depth exceeded in cmp” error.

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:1
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
rbarroiscommented, Oct 23, 2016

It is not possible natively.

You could write a custom ComplexParameter for this:


from factory import declarations

class RecursiveStopper(declarations.ComplexParameter):
    def __init__(self, default_depth, recursive_field, stopper_value=None):
        self.default_depth = default_depth
        self.recursive_field = recursive_field
        self.stopper_value = stopper_value

    def compute(self, field_name, declarations):
        depth = declarations.get(field_name) or self.default_depth
        # use depth + 1: if depth is 0, we must stop now => set FIELD_NAME=None
        override = '__'.join([self.recursive_field] * (depth + 1))
        return {override: self.stopper_value}

With this, your factory would look like this:

class MyFactory(factory.django.DjangoModelFactory):
    class Params:
        next_depth = RecursiveStopper(10, 'next')
        previous_depth = RecursiveStopper(5, 'previous')

Could you try this and let us know:

  • If it works
  • If the API is readable, or if better names should be used?

If it’s useful, we’ll add it to the core 😃

0reactions
leamingradcommented, Sep 11, 2018

The parameter method outlined above doesn’t seem to work with newer version of factoryboy (I am on 2.11.1). In particular, if you rename the compute method to as_declarations to fit in with the new way of declaring parameters the declaration dictionary will not contain field_name if you declare it as an argument.

In the example above, running MyFactory(next_depth=20) will still give you a next depth of 10.

I had a go at getting a toy version of this working another way:

import factory

class RecursiveClass:
    def __init__(self, child):
        self.child = child


class RecursiveFactory(factory.Factory):
    class Meta:
        model = RecursiveClass
        exclude = ('child_depth_temp', )

    class Params:
        child_depth = 10

    child_depth_temp=factory.LazyAttribute(lambda o: o.child_depth-1)
    child = factory.Maybe(
        factory.LazyAttribute(lambda o: o.child_depth > 0),
        yes_declaration=factory.SubFactory(
            'tests.test_factories.RecursiveStopperFactory',
            child_depth=factory.SelfAttribute('..child_depth_temp'),
        ),
        no_declaration=None,
    )

This seems to work - RecursiveFactory(20) will give you a 20-deep recursion rather than 10-deep.

@rbarrois: Is there still a way to do this with a parameter or similar? It would be nice to be able to separate the logic for handling recursion depth from the actual factory declaration.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using related_name in Django - Stack Overflow
CharField(max_length=100) manufacturer = models. ... CASCADE) def __str__(self): return f"{self.id}, {self.name}" class Job(models.
Read more >
Models - Django documentation
from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete= ...
Read more >
Common recipes — Factory Boy stable documentation
Building the adequate link between two models depends heavily on the use case; factory_boy doesn't provide a “all in one tools” as for...
Read more >
Testing Models with Django using Faker and Factory Boy
use the Meta class to tell your factory which model to use. We will create company factory in `core/factories/company.py`: import factoryclass CompanyFactory( ...
Read more >
related_name - Django Built-in Field Validation - GeeksforGeeks
The related_name attribute specifies the name of the reverse relation from the User model back to your model.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found