8

I have the following situation
I have a manager class that filters a queryset according to a field. The problem is that the field name is different according to the class but the value to which it filters comes from the same place (so i thought i don't need several managers). This is what i did so far.

class MyManager(models.Manager):
    def __init__(self, field_name):
        super(MyManager, self).__init__()
        self.field_name = field_name

    def get_queryset(self):
        # getting some value
        kwargs = { self.field_name: some_value }
        return super(MyManager, self).get_queryset().filter(**kwargs)

class A:
    # some datamembers

    @property
    def children(self):
        return MyUtils.prepare(self.b_set.all())

class B:
    objects = MyManager(field_name='my_field_name')
    a = models.ForeignKey(A, null=False, blank=False)

When i run tests i that retrieve from the DB a B object, and try to read the children property i get the following error:

self = <django.db.models.fields.related_descriptors.RelatedManager object at 0x7f384d199290>, instance = <A: A object>

    def __init__(self, instance):
>       super(RelatedManager, self).__init__()
E       TypeError: __init__() takes exactly 2 arguments (1 given)

I know its because of the constructor parameter because when i remove it (or give it a default value) all of the tests work.
How can i overcome this? Is this the right way of achieving this?
Tech stuff:

  1. Django 1.9.5
  2. test framework py.test 2.9.1

Thanks

Mr T.
  • 4,278
  • 9
  • 44
  • 61

2 Answers2

9

Another option would be to generate the Manager class dynamically, such as:

def manager_factory(custom_field):

    class MyManager(models.Manager):
        my_field = custom_field

        def get_queryset(self):
            # getting some value
            kwargs = {self.my_field: 'some-value'}
            return super(MyManager, self).get_queryset().filter(**kwargs)

    return MyManager()


class MyModel(models.Model):
    objects = manager_factory('custom_field')

This way you can decouple the Manager from the Model class.

odedfos
  • 4,491
  • 3
  • 30
  • 42
  • how would one use the `__init__` method? – dopatraman Apr 03 '19 at 00:49
  • That's a great answer for what you know the value at start time. What if you want to generate a manager dynamically at run-time? I tried and it didn't work because Manager.model wasn't getting set by the "contribute_to_class" mechanism that I still haven't grokked. – odigity Sep 11 '22 at 15:33
4

As you can see, that error is happening because Django instantiates a new Manager whenever you make a related objects call; that instantiation wouldn't get the extra parameter.

Rather than getting the value this way, you could try making it an attribute of the model and then referencing it via self.model.

class MyManager(models.Manager):
    def get_queryset(self):
        # getting some value
        kwargs = { self.model.manager_field_name: some_value }

class B:
    manager_field_name = 'my_field_name'
    objects = MyManager()
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895