50

I have a custom manager. I want to use it for related objects. I found use_for_related_fields in docs. But it does not work the way I used it:

class RandomQueryset(models.query.QuerySet):

    def randomize(self):       
        count = self.count()
        random_index = random.randint(0, count - 1)
        return self.all()[random_index]


class RandomManager(models.Manager):

    use_for_related_fields = True

    def get_query_set(self):
        return RandomQueryset(self.model, using=self._db)

    def randomize(self):
        return self.get_query_set().randomize()

I used it for one model:

>>> post = PostPages.default_manager.filter(image_gallery__isnull=False).distinct().randomize()

And tried to do the same with m2m related object:

>>> post.image_gallery.randomize()

Got an error:

AttributeError: 'ManyRelatedManager' object has no attribute 'randomize'

Is it possible to use a custom manager in the way I did it? If so, how do you make it work?

Edit

My models:

class ShivaImage(models.Model, ImageResizing):
    image = models.ImageField(upload_to='img')
    slide_show = models.BooleanField() 
    title = models.CharField(max_length=100)
    text = models.TextField(max_length=400)
    ordering = models.IntegerField(blank=True, null=True)

    objects = RandomManager()


class PostPages(models.Model):
    image_gallery = models.ManyToManyField(ShivaImage, blank=True,
                                       related_name='gallery',)
    # all the other fields... 

    objects = RandomManager()
coffee-grinder
  • 26,940
  • 19
  • 56
  • 82
I159
  • 29,741
  • 31
  • 97
  • 132

4 Answers4

49

For completeness of the topic, Django 1.7 (finally) supports using a custom reverse manager, so you can do something like that (just copying from the django docs):

from django.db import models

class Entry(models.Model):
    objects = models.Manager()  # Default Manager
    entries = EntryManager()    # Custom Manager

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
damon
  • 14,485
  • 14
  • 56
  • 75
Serafeim
  • 14,962
  • 14
  • 91
  • 133
29

THIS IS ONLY SUGGESTED FOR Django 1.09 or older - Docs proof

Setting use_for_related_fields to True on the manager will make it available on all relations that point to the model on which you defined this manager as the default manager. This is documented here

class MyManager(models.Manager):
    use_for_related_fields = True
    # ...

I suppose you have it only enabled on your PostPages model, not on your Gallery model (or whatever the model is called that is referenced through post_image_gallery). If you want to have additionally functionality on this realtion manager you need to add a custom default manager with use_for_related_fields = True to your Gallery model!

chris Frisina
  • 19,086
  • 22
  • 87
  • 167
Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
  • I defined the manager in both models. If I right understood, `objects` is means default manager. See question edits, I added models in there. – I159 Sep 21 '11 at 08:07
  • 3
    The _first_ manager defined becomes the default manager, no matter if it's called `objects` or not... – Bernhard Vallant Sep 21 '11 at 08:41
  • Uoo hooo! It's works! Most likely there was some very stupid mistake. Because now it's ok. – I159 Sep 21 '11 at 08:45
  • 30
    Warning, as of Django 1.10 this feature is deprecated in favor of setting Meta.base_manager_name on the model. See release notes in https://docs.djangoproject.com/en/1.10/releases/1.10/#manager-use-for-related-fields-and-inheritance-changes. – migonzalvar Oct 05 '16 at 11:45
  • 8
    Other warning: the docs also says that managers aren't used when querying on related objects. https://docs.djangoproject.com/en/1.11/topics/db/managers/#django.db.models.Model._base_manager. This prevents from using that feature to filter out related objects implicitely. – David Guillot Mar 08 '18 at 17:32
  • @DavidGuillot: Thanks. Any idea how to work around that related object query restriction? Do you know what is used instead of managers? – djvg Dec 10 '18 at 17:38
20

In django 2.0 use_for_related_fields is deprecated https://docs.djangoproject.com/en/2.0/releases/1.10/#manager-use-for-related-fields-and-inheritance-changes

You should use base_manager_name: https://docs.djangoproject.com/en/2.0/ref/models/options/#django.db.models.Options.base_manager_name

Updated docs: https://docs.djangoproject.com/en/2.0/topics/db/managers/#using-managers-for-related-object-access

class MyModel(models.Model):
    field1 = ...
    field2 = ...
    special_manager = MyManager()

    class Meta:
        base_manager_name = 'special_manager'
daino3
  • 4,386
  • 37
  • 48
Sardorbek Imomaliev
  • 14,861
  • 2
  • 51
  • 63
  • 4
    `base_manager_name` expects the name of the manager in question as a a string, not the instance. See [this](https://github.com/django/django/blob/8dc675d90f14a84ef95f16c7cc8100d9a04459b3/tests/custom_managers/models.py#L158). – Nobilis Feb 28 '18 at 14:47
  • 1
    for me, setting `base_manager_name` was even necessary when overrding default `objects` attribute in my model (before, I thought this would only be needed if manager name differs from default `objects`)! – Henhuy Jun 09 '20 at 10:13
1

Also, in the custom manager, make sure to access the queryset via the self.get_query_set() proxy method when implementing custom filters to be called from related manager:

class EventManager(models.Manager):

    use_for_related_fields = True

    def visible_events(self):
        today = datetime.date.today()
        # don't do this !!! 
        # unsuitable for related managers as could retrieve extraneous objects
        # qs = super(EventManager, self).get_query_set()
        # Use queryset proxy method as follows, instead:
        qs = self.get_query_set()
        qs = qs.filter(visible_from__lte=today, visible_to__gte=today)
        return qs


class Event(models.Model):

    visible_from = models.DateField(_(u'visible from'), null=False, blank=False)
    visible_to = models.DateField(_(u'visible to'), null=False, blank=False)
    concepts = models.ManyToManyField(Concept, through='ConceptEventRegistration')

    objects = EventManager()

Sample usage:

my_concept = Concept.objects.get(id=1)
# retrieve all events related to the object
my_concept.event_set.all()
# retrieve all visible events related to the object
my_concept.event_set.visible_events()
Mario Orlandi
  • 5,629
  • 26
  • 29