Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django override behavior of double underscore relationship lookup in queries

My main ask: Is there any way to change the behavior of a related lookup such as MyModel.objects.filter(relationship__field="value")?

Consider this setup. I've got a one-to-many relationship with a custom Manager that filters out Books with active=False

from django.db import models


class ActiveOnly(models.Manager):

    use_for_related_fields = True

    def get_queryset(self):
        return super(ActiveOnly, self).get_queryset().filter(active=True)

class Author(models.Model):
    name = models.TextField()

class Book(models.Model):
    active = models.BooleanField()
    author = models.ForeignKey(Author, related_name="books")
    title = models.TextField()
    objects = ActiveOnly()

And let's create some data:

jim = Author.objects.create(name="Jim")
ulysses = Book.objects.create(title="Ulysses", author=jim, active=True)
finnegans = Book.objects.create(title="Finnegan's Wake", author=jim, active=False)

bill = Author.objects.create(name="Bill")
hamlet = Book.objects.create(title="Hamlet", author=bill, active=False)

Essentially, I never want to have to deal with inactive Books. Here are some queries to test various scenarios.

>>> Book.objects.all().count()  # expecting the 1 active book: good
1  
>>> jim.books.all()  # also expecting only 1: good
1
>>> Author.objects.filter(books__title="Hamlet").first().name
u'Bill'  
# ^ this is what I don't want to see, because bill's only book has active=False.
# I want the queryset to return no results.

Is there any way to change the behavior of the books__* lookup to include the additional filter on active?

like image 852
maackle Avatar asked Oct 18 '22 05:10

maackle


1 Answers

In Django 1.10 the Manager.use_for_related_fields is deprecated in favor of setting Meta.base_manager_name on the model. See the updated documentation for details:

Model._base_manager

Using managers for related object access

By default, Django uses an instance of the Model._base_manager manager class when accessing related objects (i.e. choice.poll), not the _default_manager on the related object. This is because Django needs to be able to retrieve the related object, even if it would otherwise be filtered out (and hence be inaccessible) by the default manager.

If the normal base manager class (django.db.models.Manager) isn’t appropriate for your circumstances, you can tell Django which class to use by setting Meta.base_manager_name.

The warning, not do exclude objects in the BaseManager still stands!

like image 119
jnns Avatar answered Oct 22 '22 09:10

jnns