Django: Custom model manager problem - django

I am trying a simple custom manager, but I can't concatenate custom queries:
class MyManager(models.Manager):
def some_filter(self):
qs = self.get_query_set()
return qs.filter(score__gt = 10).order_by("-score")
class Game(models.Model):
score = models.IntegerField(blank=True, default=0)
objects = MyManager()
games = Game.objects.filter(any_filter).some_filter()[:5]
But I get QuerySet' object has no attribute 'some_filter'
Edit: It appears that the question is how to concatenate custom filter functions together: seomthing like games = Game.objects.some_filter1().some_filter2()[:5] just won't work for me.

You can apply some_filter() to MyManager object before QuerySet method filter()
games = Game.objects.some_filter().filter(any_filter)[:5]
Otherwise you should add some_filter method to QuerySet

I found a way to do it. A new QuerySet class need to be defined too
class GameQS(QuerySet):
def some_filter1(self):
return self.filter(score__gt = 10).order_by("-score")
def some_filter2(self):
return self.filter(score__gt = 50).order_by("-score")
class GameManager(models.Manager):
def get_query_set(self):
return GameQS(self.model, using=self._db)
def some_filer1(self):
return self.get_query_set().some_filter1()
def some_filter2(self):
return self.get_query_set().some_filter2()

If you are new in using model managers, first see GabiMe's answer
From django 1.7 onward, use queryset as manager class method to keep code DRY
MyQueryset.as_manager()
If you are using older verssions refer this question for more ways to optimize the code.

Related

get_queryset vs manager in Django

As far as I know, we bring the database by model managers right?
e.g. queryset = Model.objects.all()
But sometimes, I see some code that seems almost same thing but is a bit different,
post = self.get_queryset()
which also fetches database but not by manager.
What's the difference between fetching database by manager and get_queryset() and their usage?
The below example helps you to understand what ModelManager and get_queryset are:
class PersonQuerySet(models.QuerySet):
def authors(self):
return self.filter(role='A')
def editors(self):
return self.filter(role='E')
class PersonManager(models.Manager):
def get_queryset(self):
return PersonQuerySet(self.model, using=self._db)
def authors(self):
return self.get_queryset().authors()
def editors(self):
return self.get_queryset().editors()
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
people = PersonManager()
Every model have at least one model manager and get_queryset is the model manager base QuerySet.
When you use Person.objects.all() it will return all results from Person model, not filters or anything else.
In the above example, we use a custom model manager named PersonManager, where we override get_queryset.
Firstly, we apply authors filter with role='A'.
Secondly, we apply editors filter with role='E'.
So, now if we use Person.people.all() it will return only authors. Look here we use all() but it returns only authors. Because we override the Default model manager queryset.

Django Beginner. How do I update all objects and set a certain field to a value that is a function of another field?

EDIT:
I needed a student_count field in course because I'm supposed to use this model for REST API. If you can tell me how to add fields in serializer without adding to model, I'd take that too.
This is my model:
class Course(models.Model):
student_count = models.PositiveIntegerField(default=0)
students = models.ManyToManyField()
What I try to do is the following but it doesn't work.
Course.objects.update(student_count=F('students__count'))
The following works but it's not ideal
courses = Course.objects.all()
for course in courses:
course.student_count = course.students.count()
course.save()
return Course.objects.all()
Try to add/update your serializer as below,
from django.db.models import Count
class CourseSerializer(serializers.ModelSerializer):
count = serializers.SerializerMethodField(read_only=True)
def get_count(self, model):
return model.students.aggregate(count=Count('id'))['count']
class Meta:
model = Course
fields = ('count',)
You can use aggregation Count() method instead of queryset.count() its more faster. Read this SO post, aggregate(Count()) vs queryset.count()

How to access custom QuerySet methods from the Manager of a ForeignKey

I'm using Django Managers to make a higher API to interact with my database and keeping my code cleaner and more readable. But in case I have a Foreignkey relationship, I can't use the manager of the ForeignKey model. The queries are more complex as below, but I just simplified the example so, It can be easier to read and get the idea of the question:
models.py:
class Community(models.Model):
objects = CommunityManager()
...
class Inscription(models.Model):
objects = InscriptionManager()
...
community = models.ForeignKey("Community", related_name="inscriptions")
created_at = models.DateTimeField()
managers.py:
from datetime import date
from django.db import models
class InscriptionQuerySet(models.query.QuerySet):
def by_day(self, day=date.today()):
return self.filter(created_at__day=day)
... # more queries
class InscriptionManager(models.Manager):
def get_query_set(self):
return InscriptionQuerySet(self.model, using=self._db)
def today(self):
return self.get_query_set().by_day()
... # more queries
class CommunityQuerySet(models.query.QuerySet):
def by_type(self, type):
return self.filter(type=type)
... # more queries
class CommunityManager(models.Manager):
def get_query_set(self):
return OrganistaionQuerySet(self.model, using=self._db)
def by_type(self, type):
return self.get_query_set().by_type(type)
... # more queries
Usage:
Inscription.objects.by_day() # return correctly all the inscriptions made today
Community.objects.by_type('type1') # return correctly all Communities that match
Problem: but here lies the problem
community_b = Community.objects.get(id=12)
community_b.inscriptions.by_day()
>>> AttributeError: 'ForeignRelatedObjectsDescriptor' object has no attribute 'by_day'
How can I fixe this. How to customize the manager to take in consideration the models relation.
I don't see the need of both Manager and QuerySet in your approach. You could just do away with a QuerySet. This is how I generally implement custom managers, and I tried accessing custom manager method on reverse relationship in one of my projects, and it works fine:
class InscriptionQuerySet(models.QuerySet):
def by_day(self, day=date.today()):
return self.filter(created_at__day=day)
def today(self):
return self.by_day()
class CommunityQuerySet(models.QuerySet):
def by_type(self, type):
return self.filter(type=type)
And then in your models, change your objects like this:
class Community(models.Model):
objects = CommunityQuerySet.as_manager()
...
class Inscription(models.Model):
objects = InscriptionQuerySet.as_manager()
I think you would be able to access custom queryset method with this setup.

django related objects filtering with cusom manager

I write custom manager for using it in template for showing related pics at side panel.
My manager work wrong and i cant understand why.
class RelatedPicsManager(models.Manager):
use_for_related_fields = True
def sidepics(self):
return super(RelatedPicsManager, self).get_queryset().filter(side = True)
class News (models.Model):
...
pass
class ExtPhoto(models.Model):
news = models.ForeignKey(News, related_name = 'extphotos')
side = models.BooleanField(...)
...
objects = RelatedPicsManager()
When i get the news and try look all its side pics
>>> onnews.extphotos.sidepics()
my manager return all ExtPhoto objects with side = True.
But i need only related with the "onenews".
I think you can solve this by using a custom QuerySet:
class ExtPhotoQuerySet(models.query.QuerySet):
def sidepics(self):
return self.filter(side=True)
class ExtPhotoManager(models.Manager):
use_for_related_fields = True
def get_queryset(self):
return ExtPhotoQuerySet(self.model)
class ExtPhoto(models.Model):
objects = ExtPhotoManager()
That way, onnews.extphotos.sidepics() only filters the photos relating to onnews. If you also want to use ExtPhoto.objects.sidepics(), I suggest inheriting from PassThroughManager from django-model-utils. This makes all queryset methods available on your manager.

Custom QuerySet and Manager without breaking DRY?

I'm trying to find a way to implement both a custom QuerySet and a custom Manager without breaking DRY. This is what I have so far:
class MyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
This works fine, until I do something like this:
inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()
This promptly breaks everything because the QuerySet doesn't have the same methods as the Manager. I've tried creating a custom QuerySet class, and implementing it in MyInquiryManager, but I end up replicating all of my method definitions.
I also found this snippet which works, but I need to pass in the extra argument to for_user so it breaks down because it relies heavily on redefining get_query_set.
Is there a way to do this without redefining all of my methods in both the QuerySet and the Manager subclasses?
The Django 1.7 released a new and simple way to create combined queryset and model manager:
class InquiryQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
objects = InqueryQuerySet.as_manager()
See Creating Manager with QuerySet methods for more details.
Django has changed! Before using the code in this answer, which was written in 2009, be sure to check out the rest of the answers and the Django documentation to see if there is a more appropriate solution.
The way I've implemented this is by adding the actual get_active_for_account as a method of a custom QuerySet. Then, to make it work off the manager, you can simply trap the __getattr__ and return it accordingly
To make this pattern re-usable, I've extracted out the Manager bits to a separate model manager:
custom_queryset/models.py
from django.db import models
from django.db.models.query import QuerySet
class CustomQuerySetManager(models.Manager):
"""A re-usable Manager to access a custom QuerySet"""
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
# don't delegate internal methods to the queryset
if attr.startswith('__') and attr.endswith('__'):
raise
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model, using=self._db)
Once you've got that, on your models all you need to do is define a QuerySet as a custom inner class and set the manager to your custom manager:
your_app/models.py
from custom_queryset.models import CustomQuerySetManager
from django.db.models.query import QuerySet
class Inquiry(models.Model):
objects = CustomQuerySetManager()
class QuerySet(QuerySet):
def active_for_account(self, account, *args, **kwargs):
return self.filter(account=account, deleted=False, *args, **kwargs)
With this pattern, any of these will work:
>>> Inquiry.objects.active_for_account(user)
>>> Inquiry.objects.all().active_for_account(user)
>>> Inquiry.objects.filter(first_name='John').active_for_account(user)
UPD if you are using it with custom user(AbstractUser), you need to change
from
class CustomQuerySetManager(models.Manager):
to
from django.contrib.auth.models import UserManager
class CustomQuerySetManager(UserManager):
***
You can provide the methods on the manager and queryset using a mixin.
This also avoids the use of a __getattr__() approach.
from django.db.models.query import QuerySet
class PostMixin(object):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class PostQuerySet(QuerySet, PostMixin):
pass
class PostManager(models.Manager, PostMixin):
def get_query_set(self):
return PostQuerySet(self.model, using=self._db)
You can now use the from_queryset() method on you manager to change its base Queryset.
This allows you to define your Queryset methods and your manager methods only once
from the docs
For advanced usage you might want both a custom Manager and a custom QuerySet. You can do that by calling Manager.from_queryset() which returns a subclass of your base Manager with a copy of the custom QuerySet methods:
class InqueryQueryset(models.Queryset):
def custom_method(self):
""" available on all default querysets"""
class BaseMyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
MyInquiryManager = BaseInquiryManager.from_queryset(InquiryQueryset)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
A slightly improved version of T. Stone’s approach:
def objects_extra(mixin_class):
class MixinManager(models.Manager, mixin_class):
class MixinQuerySet(QuerySet, mixin_class):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
Class decorators make usage as simple as:
class SomeModel(models.Model):
...
#objects_extra
class objects:
def filter_by_something_complex(self, whatever parameters):
return self.extra(...)
...
Update: support for nonstandard Manager and QuerySet base classes, e. g. #objects_extra(django.contrib.gis.db.models.GeoManager, django.contrib.gis.db.models.query.GeoQuerySet):
def objects_extra(Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet):
def oe_inner(Mixin, Manager=django.db.models.Manager, QuerySet=django.db.models.query.QuerySet):
class MixinManager(Manager, Mixin):
class MixinQuerySet(QuerySet, Mixin):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
if issubclass(Manager, django.db.models.Manager):
return lambda Mixin: oe_inner(Mixin, Manager, QuerySet)
else:
return oe_inner(Mixin=Manager)
based on django 3.1.3 source code, i found a simple solution
from django.db.models.manager import BaseManager
class MyQuerySet(models.query.QuerySet):
def my_custom_query(self):
return self.filter(...)
class MyManager(BaseManager.from_queryset(MyQuerySet)):
...
class MyModel(models.Model):
objects = MyManager()
There are use-cases where we need to call custom QuerySet methods from the manager instead of using the get_manager method of a QuerySet.
A mixin would suffice based on the solution posted in one of the accepted solution comments.
class CustomQuerySetManagerMixin:
"""
Allow Manager which uses custom queryset to access queryset methods directly.
"""
def __getattr__(self, name):
# don't delegate internal methods to queryset
# NOTE: without this, Manager._copy_to_model will end up calling
# __getstate__ on the *queryset* which causes the qs (as `all()`)
# to evaluate itself as if it was being pickled (`len(self)`)
if name.startswith('__'):
raise AttributeError
return getattr(self.get_queryset(), name)
For example,
class BookQuerySet(models.QuerySet):
def published(self):
return self.filter(published=True)
def fiction(self):
return self.filter(genre="fiction")
def non_fiction(self):
return self.filter(genre="non-fiction")
class BookManager(CustomQuerySetManagerMixin, models.Manager):
def get_queryset(self):
return BookQuerySet(self.model, using=self._db).published()
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.CharField(choices=[('fiction', _('Fiction')), ('non-fiction', _('Non-Fiction'))])
published = models.BooleanField(default=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
objects = BookManager()
class Author(models.Model):
name = models.CharField(max_length=200)
With the above, we can access related objects (Book) like below without defining new methods in the manager for each queryset method.
fiction_books = author.books.fiction()
The following works for me.
def get_active_for_account(self,account,*args,**kwargs):
"""Returns a queryset that is
Not deleted
For the specified account
"""
return self.filter(account = account,deleted=False,*args,**kwargs)
This is on the default manager; so I used to do something like:
Model.objects.get_active_for_account(account).filter()
But there is no reason it should not work for a secondary manager.