3

Consider the following case:

class MyModelManager(models.Manager):
    def my_filter(self):
     return [some code here].filter(field_A__gt=3)

class MyModel(models.Model):
    # some fields
    objects = MyModelManager()


# The way I'd like to use it:
qs = MyModel.objects.filter(field_B__lt=1)
# some code
qs = qs.my_filter()

Notice that I'd like to use the custom my_filter() function on the already filtered queryset. What code should I place in the [some code here] above? Is the structure as a whole correct?

Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359
  • See also question #2163151: http://stackoverflow.com/questions/2163151/custom-queryset-and-manager-without-breaking-dry – akaihola Jun 21 '12 at 12:15

3 Answers3

2

I guess there is no way to do it exactly this way. MyModel.objects.filter(field_B__lt=1) will return QuerySet object, not MyModelManager. You may either pass it to my_filter method as Ignacio Vazquez-Abrams mentioned or first apply my_filter to your manager, that will return QuerySet object, that you can filter further

Dima Bildin
  • 1,533
  • 10
  • 14
  • How come then that MyModel.objects.filter(...).filter(...) works? – Jonathan Livni Apr 28 '11 at 09:16
  • so both Manager and QuerySet have filter()? – Jonathan Livni Apr 28 '11 at 09:28
  • From django source Manager class methods: def get_query_set(self): """Returns a new QuerySet object. Subclasses can override this method to easily customize the behavior of the Manager. """ return QuerySet(self.model, using=self._db) def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) So Manager creates new QuerySet and apply filter to it – Dima Bildin Apr 28 '11 at 09:36
1

Your approach is wrong because you can only use Model.Manager while you retreive data from database, so using two managers or trying to use a manager ona a queryset causes error.

data = SomeModel.objects.my_manager.all()

or

data = SomeModel.objects.all()
data = data.my_manager.filter(...)

is wrong since you can not use two managers...

One possible waw is defining a function that gets a queryset as a parameter and returns a filtered queryset...

def extra_filter(queryset):
    queryset = queryset.filter(...)

but i am not sure if this helps.

Best approach is defining another model Manager and use it instread of objects if required...

class SomeManager(models.Manager):
    def get_query_set(self):
        return super(SomeManager, self).get_query_set().filter(<filteer criteria>)

class MyModel(models.Model):
    # some fields
    objects = models.Manager()
    mymanager = SomeManager()

data = MyModel.mymanager.all() 

And use just one...

UPDATE: In django, querysets are lazy. That means, you can apply as many filters as you wish and data will not be retreived from the database unliess you try to get a specific record from the filtered or you try to slice it...Documentation is here

So, there is no diffrence between

qs = MyModel.objects.filter(field_B__lt=1)
qs = qs.filter(field_A__gt=3)

and

qs = MyModel.objects.filter(field_A__gt=3)
qs = qs.filter(field_B__lt=1)

So defining a manager to apply a specific filtering is the main reason for using managers...

You define your manager :

class SomeManager(models.Manager):
    def get_query_set(self):
        return super(SomeManager, self).get_query_set().filter(field_A__gt=3)

then you call it with other filtering criteria:

Somemodel.mymodelmanager.filter(field_B__lt=1, ....)

And django will add all filters and evluate the query when you want it to do.

So if it is a filter that you use oftenly, manager i the best choice...

As for an example, i have valid_ field in my most database models and a val manager that filters invalid entries. So When i wish to filter all valid data (90% of the time) i use

Somemodel.val.filter(...)

And in the situation that i need all the data i just use basic django manager:

Somemodel.objects.filter(...)
Mp0int
  • 18,172
  • 15
  • 83
  • 114
  • I'm trying to use a custom filtering function on an already regularly filtered queryset, don't see how defining another manager would achieve that – Jonathan Livni Apr 28 '11 at 21:21
0
class MyModelManager(models.Manager):
    def my_filter(self, qs=None):
        if qs is None:
            qs = [however you got it before]
        return qs.filter(field_A__gt=3)

Then just pass your queryset to it.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358