What's the difference between Custom model manager methods and QuerySet methods? - django

I am trying to get my head around writing Custom managers. I found the online documentation a little sparse. Toying around myself with code, I discovered the following patterns:
Given the following model...
class QuestionQuerySet(models.QuerySet):
def QS_first (self):
return self.first()
class QuestionManager(models.Manager):
def get_queryset(self):
return QuestionQuerySet(self.model, using=self._db)
def MN_first(self):
return self.get_queryset().first()
class Question(models.Model):
front = models.ForeignKey('Sentence', related_name='question_fronts')
....
I then get the following results...
Grammar.objects.filter(stage=1).question_set.MN_first()
<Question: [<Sentence: eve gideceğim>, <Sentence: I will go home>]>
Grammar.objects.filter(stage=1).question_set.QS_first()
AttributeError: 'RelatedManager' object has no attribute 'QS_first'
But
Question.objects.filter(grammar=1).QS_first()
<Question: [<Sentence: eve gideceğim>, <Sentence: I will go home>]>
Question.objects.filter(grammar=1).MN_first()
AttributeError: 'QuestionQuerySet' object has no attribute 'MN_first'
Why is it that the Manager methods are called when accessing the object through a DB relationship, but the Queryset methods are called when accessing the object directly? If I want the one method universally accessible (DRY), what would be the best solution?

Have a look at the QuerySet.as_manager() method. It allows you to create a manager from a queryset, so that you don't need to duplicate code in a custom manager and queryset,

Related

Getting fields from extra manager methods using django-rest-framework

I have the following custom model manager in Django that is meant to count the number of related comments and add them to the objects query set:
class PublicationManager(models.Manager):
def with_counts(self):
return self.annotate(
count_comments=Coalesce(models.Count('comment'), 0)
)
Adding this manager to the model does not automatically add the extra field in DRF. In my API view, I found a way to retrieve the count_comments field by overriding the get function such as:
class PublicationDetails(generics.RetrieveUpdateAPIView):
queryset = Publication.objects.with_counts()
...
def get(self, request, pk):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset.get(id=pk))
data = {**serializer.data}
data['count_comments'] = queryset.get(id=pk).count_comments
return Response(data)
This works for a single instance, but when I try to apply this to a paginated list view using pagination_class, overriding the get method seems to remove pagination functionality (i.e. I get a list of results instead of the usual page object with previous, next, etc.). This leads me to believe I'm doing something wrong: should I be adding the custom manager's extra field to the serializer instead? I'm not sure how to proceed given that I'm using a model serializer. Should I be using a basic serializer?
Update
As it turns out, I was using the model manager all wrong. I didn't understand the idea of table-level functionality when what I really wanted was row-level functionality to count the number of comments related to a single instance. I am now using a custom get_paginated_response method with Comment.objects.filter(publication=publication).count().
Original answer
I ended up solving this problem by creating a custom pagination class and overriding the get_paginated_response method.
class PaginationPublication(pagination.PageNumberPagination):
def get_paginated_response(self, data):
for item in data:
publication = Publication.objects.with_counts().get(id=item['id'])
item['count_comments'] = publication.count_comments
return super().get_paginated_response(data)
Not sure it's the most efficient solution, but it works!

Django - Meta.base_manager_name - make related argument in the custom queryset and manager

I have a custom model manager and a custom queryset defined specifically for related obj which means I have defined Meta.base_manager_name in the model.
I would like to use a all() manager method which fetches related obj on a OneToOneFeild.
Now I know this does not make sense since OneToOneFeild will always return one obj there is no need for a all() method. I am working on django-oscar project and am extending its "Partner" model. It originally has a field "users" with ManyToManyField and now changed to a OneToOneFeild.
The users field is called in code several times using relation user.partners.all(). I don't want to extend/modify all these places (am I being lazy here?) since I want to keep the code as upgrade friendly as possible and so instead I wanted to have all() model manager defined which will work. Not sure if it is a good idea?
the all() method takes user arg to return queryset of the user instance
class PartnerQuerySet(models.QuerySet):
def all(self, user):
return self.filter(user=user)
class PartnerManager(models.Manager):
def get_queryset(self):
return PartnerQuerySet(self.model, using=self._db)
def all(self, user):
return self.get_queryset().all(users)
class Partner(models.Model):
objects = PartnerManager()
class Meta:
base_manager_name = 'objects'
The problem is when it is used with related obj it asks for user arg which makes sense but since I am using it with a related obj I wanted to use the related obj as arg so,
user.partner.all() - should use user as arg and fetch the results
user.partner.all(user) - and I should not have to do the below
2 related questions:
1) Does this make sense - should I be doing this?
2) how I can achieve user.partner.all() without adding user in arg
PS: I know i can work with middleware to get_current_user but this function is not reliable as per some of the responses on a different question on SO.
I don't think what you are trying to do will work. Your new situation with a OneToOneField gives you the partner instance.
>>>> user.partner
<Partner xxx>
While in the old situation with the ManyToManyField, the PartnerQuerySet would've been returned.
>>>> user.partner
<PartnerQuerySet []>
A solution would be to create a custom OneToOneField, but this would most probably violate the "simple is better than complex" rule and in the end may even be more work than changing all existing .all()'s.

Using Django filters inside model function

The main purpose of a model is to contain business logic, so I want most of my code inside Django model in the form of methods. For example I want to write a method named get_tasks_by_user() inside task model. So that I can access it as
Tasks.get_tasks_by_user(user_id)
Following is my model code:
class Tasks(models.Model):
slug=models.URLField()
user=models.ForeignKey(User)
title=models.CharField(max_length=100)
objects=SearchManager()
def __unicode__(self):
return self.title
days_passed = property(getDaysPassed)
def get_tasks_by_user(self,userid):
return self.filters(user_id=userid)
But this doesn't seems to work, I have used it in view as:
tasks = Tasks.objects.get_tasks_by_user(user_id)
But it gives following error:
'SearchManager' object has no attribute 'get_tasks_by_user'
If I remove objects=SearchManager, then just name of manager in error will change so I think that is not issue. Seems like I am doing some very basic level mistake, how can I do what I am trying to do? I know I can do same thing via :Tasks.objects.filters(user_id=userid) but I want to keep all such logic in model. What is the correct way to do so?
An easy way to do this is by using classmethod decorator to make it a class method. Inside class Tasks:
#classmethod
def get_tasks_by_user(cls, userid):
return cls.objects.filters(user_id=userid)
This way you can simply call:
tasks = Tasks.get_tasks_by_user(user_id)
Alternatively, you can use managers per Tom's answer.
To decided on which one to choose in your specific case, you can refer James Bennett's (the release manager of Django) blog post on when to use managers/classmethod.
Any methods on a model class will only be available to instances of that model, i.e. individual objects.
For your get_tasks_by_user function to be available as you want it (on the collection), it needs to be implemented on the model manager.
class TaskManager(models.Manager):
def get_tasks_by_user(self, user_id):
return super(TaskManager, self).get_query_set().filter(user=user_id)
class Task(models.Model):
# ...
objects = TaskManager()

Find object in child class from object in parent class in django

Let's say I have a parent class (ThingsThatMigrate) and two children (Coconut and Swallow). Now let's say I have a ThingsThatMigrate object. How can I determine if it is in fact a Coconut or a Swallow? Once having done so, how can I get to the Coconut or Swallow object?
Django doesn't offer such model polymorphism out of the box.The easiest way to do what you are trying to achieve is to store the content type of a new object in it. There's a simple generic app called django-polymorphic-models which offers you this functionality - and - additionally a downcast-method that will return the child object!
Concrete or abstract inheritance? If concrete:
>>> things = ThingsThatMigrate.objects.all().select_related('coconut', 'swallow')
>>> for thing in things:
... thing = thing.coconut or thing.swallow or thing
... print thing
This can be automated using django-model-utils InheritanceManager (then you don't need to worry about select_related or manually listing all possible subclasses). Maintained by another Django core developer.
It's not particularly pretty or efficient, but the best way I can think of implementing this without storing the subclass meta data in the DB (like django-polymorphic-models does) would be a child() method in your ThingsThatMigrate model class:
from django.core.exceptions import ObjectDoesNotExist
def child(self):
for subclass in self.__class__.__subclasses__():
try:
return getattr(self, subclass.__name__.lower())
except (AttributeError, ObjectDoesNotExist):
continue
On a Django CMS I work with (Merengue http://www.merengueproject.org/), we store the "classname" attribute that stores what is the real class of the object.
In order to get the real instance we used the following method:
def get_real_instance(self):
""" get object child instance """
def get_subclasses(cls):
subclasses = cls.__subclasses__()
result = []
for subclass in subclasses:
if not subclass._meta.abstract:
result.append(subclass)
else:
result += get_subclasses(subclass)
return result
if hasattr(self, '_real_instance'): # try looking in our cache
return self._real_instance
subclasses = get_subclasses(self.__class__)
if not subclasses: # already real_instance
self._real_instance = getattr(self, self.class_name, self)
return self._real_instance
else:
subclasses_names = [cls.__name__.lower() for cls in subclasses]
for subcls_name in subclasses_names:
if hasattr(self, subcls_name):
return getattr(self, subcls_name, self).get_real_instance()
return self
The important thing of this function is that it keeps in mind if the class is abstract or not, wich change the logic a little bit.
As DrMeer suggested, I highly recommend django-model-utils (hosted on bitbucket now though). I'm not sure it's convincing enough though.
Let a code example prove it:
>>> ThingsThatMigrate.objects.all().select_subclasses()
Coconut, Coconut, Swallow, Coconut, ThingsThatMigrate
It takes one line, objects = InheritanceManager() in your parent model.
From the docs:
If you have a Place that is also a Restaurant, you can get from the Place object to the Restaurant object by using the lower-case version of the model name...

Django custom manager with RelatedManager

There must be a problem with super(InviteManager, self).get_query_set() here but I don't know what to use. When I look through the RelatedManager of a user instance,
len(Invite.objects.by_email()) == len(user.invite_set.by_email())
Even if the user does not have any invites. However, user.invite_set.all() correctly returns all of the Invite objects that are keyed to the User object.
class InviteManager(models.Manager):
"""with this we can get the honed querysets like user.invite_set.rejected"""
use_for_related_fields = True
def by_email(self):
return super(InviteManager, self).get_query_set().exclude(email='')
class Invite(models.Model):
"""an invitation from a user to an email address"""
user = models.ForeignKey('auth.User', related_name='invite_set')
email = models.TextField(blank=True)
objects = InviteManager()
'''
u.invite_set.by_email() returns everything that Invite.objects.by_email() does
u.invite_set.all() properly filters Invites and returns only those where user=u
'''
You may want a custom QuerySet that implements a by_email filter. See examples on Subclassing Django QuerySets.
class InviteQuerySet(models.query.QuerySet):
def by_email(self):
return self.exclude(email='')
class InviteManager(models.Manager):
def get_query_set(self):
model = models.get_model('invite', 'Invite')
return InviteQuerySet(model)
Try:
def by_email(self):
return super(InviteManager, self).exclude(email='')
If nothing else, the .get_query_set() is redundant. In this case, it may be returning a whole new queryset rather than refining the current one.
The documentation specifies that you should not filter the queryset using get_query_set() when you replace the default manager for related sets.
Do not filter away any results in this type of manager subclass
One reason an automatic manager is used is to access objects that are related to from some other model. In those situations, Django has to be able to see all the objects for the model it is fetching, so that anything which is referred to can be retrieved.
If you override the get_query_set() method and filter out any rows, Django will return incorrect results. Don’t do that. A manager that filters results in get_query_set() is not appropriate for use as an automatic manager.
Try using .all() in place of .get_query_set(). That seemed to do the trick for a similar problem I was having.
def by_email(self):
return super(InviteManager, self).all().exclude(email='')