#30355: Specifying custom manager doesn't work with prefetch
-------------------------------+----------------------------------------
Reporter: Kyle Mulka | Owner: Akshat verma
Type: Bug | Status: assigned
Component: Documentation | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 1 | Patch needs improvement: 1
Easy pickings: 1 | UI/UX: 1
-------------------------------+----------------------------------------
Changes (by Akshat verma):
* cc: Akshat verma (added)
* needs_better_patch: 0 => 1
* needs_tests: 0 => 1
* easy: 0 => 1
* has_patch: 0 => 1
* ui_ux: 0 => 1
Old description:
> When using prefetch and specifying a custom manager to use for a reverse
> relation, Django doesn't filter correctly. Here's an example:
>
> {{{
> class Business(models.Model):
> name = models.CharField(max_length=255)
>
> def approved_reviews(self):
> return self.review_set(manager='approved_reviews').all()
>
> class ApprovedReviewsManager(models.Manager):
> def get_queryset(self):
> return super().get_queryset().filter(status=Review.APPROVED)
>
> class Review(models.Model):
> NEW = 1
> APPROVED = 2
> STATUS_CHOICES = (
> (NEW, 'New'),
> (APPROVED, 'Approved'),
> )
> business = models.ForeignKey(Business)
> text = models.CharField(max_length=255)
> status = models.IntegerField(choices=STATUS_CHOICES, default=NEW)
>
> objects = models.Manager()
> approved_reviews = ApprovedReviewsManager()
>
> class ApprovedReviewsTest(TestCase):
> def test_with_prefetch(self):
> business = Business()
> business.save()
>
> review = Review()
> review.business = business
> review.save()
>
> businesses =
> Business.objects.prefetch_related('review_set').all()
>
> business = businesses[0]
> approved_reviews =
> business.review_set(manager='approved_reviews').all()
>
> self.assertEqual(len(approved_reviews), 0)
> }}}
>
> The full test project is available here:
> https://github.com/mulka/django_prefetch_manager_bug/blob/master/review_site/tests.py
New description:
When using prefetch and specifying a custom manager to use for a reverse
relation, Django doesn't filter correctly. Here's an example:
{{{
class Business(models.Model):
name = models.CharField(max_length=255)
def approved_reviews(self):
return self.review_set(manager='approved_reviews').all()
class ApprovedReviewsManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status=Review.APPROVED)
class Review(models.Model):
NEW = 1
APPROVED = 2
STATUS_CHOICES = (
(NEW, 'New'),
(APPROVED, 'Approved'),
)
business = models.ForeignKey(Business)
text = models.CharField(max_length=255)
status = models.IntegerField(choices=STATUS_CHOICES, default=NEW)
objects = models.Manager()
approved_reviews = ApprovedReviewsManager()
class ApprovedReviewsTest(TestCase):
def test_with_prefetch(self):
business = Business()
business.save()
review = Review()
review.business = business
review.save()
businesses = Business.objects.prefetch_related('review_set').all()
business = businesses[0]
approved_reviews =
business.review_set(manager='approved_reviews').all()
self.assertEqual(len(approved_reviews), 0)
}}}
The full test project is available here:
https://github.com/mulka/django_prefetch_manager_bug/blob/master/review_site/tests.py
#This implementation: by Akshat verma
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return self.filter(is_vegetarian=True)
#Looks non-standard. docs look like they do a safer method of modifying
the super-class method for this sort of lazy-eval stuff. #If I rewrite
your method in that style, it would look like:
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return super(ToppingManager,
self).get_queryset().filter(is_vegetarian=True)
#You wouldn't strictly need the super() here, but safer to use it because
you should know that you want to start with the #models.Manager
get_queryset method.
--
Comment:
#This implementation: by Akshat verma
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return self.filter(is_vegetarian=True)
#Looks non-standard. docs look like they do a safer method of modifying
the super-class method for this sort of lazy-eval stuff. #If I rewrite
your method in that style, it would look like:
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return super(ToppingManager,
self).get_queryset().filter(is_vegetarian=True)
#You wouldn't strictly need the super() here, but safer to use it because
you should know that you want to start with the #models.Manager
get_queryset method.
--
Ticket URL: <https://code.djangoproject.com/ticket/30355#comment:10>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/010701874695a99f-8f512c93-3107-4dbe-a9f8-abb7dc8ea9c9-000000%40eu-central-1.amazonses.com.