Mr T.
Mr T.

Reputation: 4518

Creating a django manager with a parameter

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

Upvotes: 8

Views: 3840

Answers (2)

odedfos
odedfos

Reputation: 4619

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.

Upvotes: 9

Daniel Roseman
Daniel Roseman

Reputation: 600059

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()

Upvotes: 4

Related Questions