I'd like to by default only return "published" instances (published=True). Is it possible to override .objects so that MyModel.objects.all() actually returns MyModel.objects.filter(published=True)?
Is this sensible? How would I get the unpublished ones in the rare cases where I did want them?
You can do this by writing a custom Manager -- just override the get_queryset method and set your objects to a Manager instance. For example:
class MyModelManager(models.Manager):
def get_queryset(self):
return super(MyModelManager, self).get_queryset().filter(published=True)
class MyModel(models.Model):
# fields
# ...
objects = MyModelManager()
See the docs for details. It's sensible if that's going to be your usual, default case. To get unpublished, create another manager which you can access with something like MyModel.unpublished_objects. Again, the docs have examples on this type of thing.
Related
Problem: I've implemented a custom manager for a model with just one custom query set named get_by_tag and it's working fine if I use it this way:
ViewStatistic.objects.get_by_tag('some-tag-name').filter(user=user_id)
But when I change the order of queries, in this way:
ViewStatistic.objects.filter(user=user_id).get_by_tag('some-tag-name')
it doesn't work! and raises this error:
AttributeError: 'QuerySet' object has no attribute 'get_by_tag'
Am I missing something?! How can I do this in such a order?
P.S: The custom manager is something like this:
class MyCustomManager(models.Manager):
def get_by_tag(self, tag_name):
posts = Post.objects.filter(tags__pk=tag_name)
return super().get_queryset().filter(post__pk__in=posts)
If you want to use your queryset methods inside of queryset chain and not only directly after the manager, you should define them as methods of custom QuerySet class that you connect to a manager.
Two solutions are described in Django documentation Creating a manager with QuerySet methods.
Common part - a custom QuerySet class with queryset methods
class MyCustomQuerySet(models.QuerySet):
def get_by_tag(self, tag_name):
return self.filter(post__pk__in=Post.objects.filter(tags__pk=tag_name))
# more possible queryset methods ...
A) if your manager has only queryset methods and no other custom methods
then you can create it simply from the QuerySet.
class MyModel(models.Model):
objects = MyCustomQuerySet.as_manager()
B) if your manager need also other methods that do not return a queryset:
class MyCustomManager(models.Manager):
... # other methods
class MyModel(models.Model):
objects = MyCustomManager.from_queryset(MyCustomQuerySet)()
When you say ViewStatistic.objects it returns the object of <django.db.models.manager.Manager>
In your case since it have derived class MyCustomManager having base class models.manager, so it return object of <your_app.models.MyCustomManager> which have get_by_tag function, and you can access get_by_tag.
For second case ViewStatistic.objects.filter return django.db.models.query.QuerySet object and ofcourse it hasn't no method named get_by_tag that's why you get AttributeError.
One more point related to queryset is
The result of refining a QuerySet is itself a QuerySet, so it’s possible to chain refinements together.
https://docs.djangoproject.com/en/3.0/topics/db/queries/#chaining-filters
In your case get_by_tag return QuerySet further you performed .filter() operation, which is fine.
The django official documentation link can be followed related to models Manager and query for more details.
https://docs.djangoproject.com/en/3.0/topics/db/queries/
I have two Django models, one that stores promotion codes and another that tracks who redeemed a particular promotion code. I'm trying to create an instance method that determines whether or not a specific user has redeemed a specific code. The problem is that I'm not seeing one of my PromotionManager methods, 'redeemed_by_user'. Here are my classes:
from django.contrib.auth.models import User
from django.db import models
class PromotionManager(models.Manager):
def redeemed_by_user(self, promotion, user):
redemption_count = PromotionRedeemed.objects.filter(promotion=promotion, redeemer=user).count()
if redemption_count == 1:
return True
elif redemption_count == 0;
return False
else:
raise ValueError('Invalid redemption count')
class Promotion(models.Model):
code = models.CharField(max_length=16)
objects = PromotionManager()
class PromotionRedeemed(models.Model):
promotion = models.ForeignKey('Promotion')
user = models.ManyToManyField(User)
If I start the the Django extension shell_plus and do the following:
In [1]: user = User.objects.get(username='smith')
In [2]: promotion = Promotion.objects.get(code='bigsale')
and then I do this:
In [3]: dir(promotion)
I don't see the redeemed by user method. I was under the impression that I could move methods like this from my class to a custom manager class. Is that not the case? If so, can anyone explain why? As I understand it, class manager methods are supposed to act on table-level queries and class intance methods on row-level objects. Isn't objects.filter acting on the table level? I tried moving the method back to the Promotion class and I can see it there but I'd just like to understand why I can't see it in the manager class.
Whatever you are seeing is absolutely correct but there is small correction that you should make. When you do a dir(some_instance) then you see a property named objects .
objects = PromotionManager()
This line sets all the manager methods to the objects property so if you try to access the method via some_instance.objects.method_name then you will be able to access it although you can't use it because Django doesn't allow this. You will see an error like manager methods are not accessible from instances. dir is supposed to show only those methods which are accessible from your model instance.
From the docs,
A Manager is the interface through which database query operations are provided to Django models. By default, Django adds a Manager with the name "objects" to every Django model class.
A model’s manager is an object through which Django models perform database queries. Each Django model has at least one manager, and you can create custom managers in order to customize database access.
Adding extra manager methods(custom managers) is the preferred way to add “table-level” functionality to your models whereas for “row-level” functionality use model methods.
Objects is a special attribute through which you query your database. It’s an instance of the class django.db.models.Manager; it’s where all the default methods for performing queries against the entire model class — all(), get(), filter(), etc.
The dir() function, with an argument, attempt to return a list of valid attributes for that object.
If you dir(promotion), promotion is an instance of Promotion Model object. It returns the attributes of a Promotion instance, which includes the objects attribute. But, you defined objects as PromotionManager(), and the redeemed_by_user() is a method of the Manager instance.
If you dir(promotion.objects) , django would raise an error, AttributeError: Manager isn't accessible via Poke instances. Because, its true. objects is a Manager available at the class level, not to the instances.
From the docs,
Managers are accessible only via model classes, rather than from model instances, to enforce a separation between “table-level” operations and “record-level” operations.
So, if you dir(Promotion.objects), you could see all custom methods defined in the Manager instance of the model.
You use dir on the wrong object.
Moreover, you replaced default manager with yours.
The first manager applied to a model class has special meaning for Django, and is a default one, so add own manager this way, please:
objects = models.Manager()
<your_custom_name> = PromotionManager()
I am looking for some way to define some wrapper that is called before i call to Model.objects.all().
I want whenever i call, Model.objects it call my method (wrapper) and then return the objects back to the query.
Lets take an example:
MyModel.objcts.filter(name="Jack")
Wrapper:
def mymodelWrapper(self):
return self.objects.annotate(size=Sum('id', field='order_size_weight*requested_selling_price'))
I want to run annotate in the background and also want to apply the filter.
I Know what i want to achieve, its the code i am looking for how to do that.
What you are talking about is perfectly doable with Django by using a custom model manager:
class MyModelManager(models.Manager):
def get_query_set(self):
return super(MyModelManager, self).get_query_set().annotate(size=Sum('id', field='order_size_weight*requested_selling_price'))
class MyModel(models.Model):
objects = MyModelManager()
# fields
Also see other similar topics:
Is it possible to override .objects on a django model?
Override djangos's object.all() with the request data
Django custom model managers
How can i override the model manager of a many-to-many field that i have considering the following:
class TermsManager(models.Manager):
def all(self):
return super(TermsManager, self).all().filter(condition_here)
class Term(models.Model):
objects = TermsManager()
name = models.CharField(max_length=255)
class Object(models.Model):
title = models.CharField(max_length=255)
terms = models.ManyToManyField(Term, blank=True)
class Channel(Object):
class Meta:
proxy = True
I also have a class which inherits from TermManager called ChannelTermManager.
How can i override the "terms" field of the Channel model so that
mychannel.terms calls the ChannelTermManager instead of TermManager?
First of all, you shouldn't be overriding all(). If you want to change the default queryset, override get_query_set like so:
class TermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
This is because all() is often omitted when other queryset functions are chained on, and you want your queryset to behave the same whether all() is explicitly called or not.
But even so, what you're doing is still problematic. As explained in the documentation for managers, filtering the default related queryset will affect all sorts of automatic things behind the scenes (such as when dumping data to create backups/fixtures, etc.). You almost definitely do not want this. And you really don't want your related object managers doing this either (by setting use_for_related_fields = True), because you'll be masking what's actually stored in the database, rather than simply detecting out of date data and creating alerts or whatever to clean it up. use_for_related_fields is intended for creating managers that augment the normal capabilities of the vanilla manager, not to filter.
I had a similar situation to yours however, and I handled it like so:
class FilteredTermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
class Term(models.Model):
allTerms = models.Manger() # Establish this as the default/automatic manager
objects = FilteredTermsManager()
name = models.CharField(max_length=255)
This way, I could do all my initial querying on the model through my filtered queryset and it looks like "regular Django", but all relational and behind the scenes queries would work on the unfiltered database. And I could always access the true full set of objects by manually doing Term.allTerms.all().
As for using different managers for different related objects, there's nothing you can really do there. But why not just add Channel specific objects to your custom manager, and simply not call them from methods that operate on get Term querysets from Object?
Django's ForeignRelatedObjectsDescriptor.create_manager(...) function dynamically creates the RelatedManager classes and subsequently initializes an instance of the dynamically created class.
If I wanted to override the RelatedManager.add(...) method, how would I do it?
The RelatedManager classes are created in file: django/db/models/fields/related.py.
An example of how I'd like to use a custom RelatedManager is...
class Record(Model):
string = CharField()
class Managed(Model):
record = ForeignKey('Record')
boolean = BooleanField()
def view_function(...):
record = Record(string='Example')
record.save()
record.managed_set.add(Managed(boolean=True)) # How to override add()?
Any suggestions would be appreciated.
I'm not sure what you need the override for - the default queryset already does what you want.
But to answer the question, you can define a custom Manager on the model and set use_for_related_fields=True to ensure it gets used as the automatic manager. See the documentation on controlling automatic Manager types.
I think I am having the same problem.
I have a custom manager that overrides self._db and get_query_set() to route it to different databases.
I dynamically created a model class, and has its _default_manager set with my custom manager.
This works for the class itself, but not for related field (foreign or many2many), even though I did set sets use_for_related_fields = True.
For related field, appending db_manager(dbname) (for example, record.managed_set.db_manager(dbname)) can fix all() method, but not for add() method.
To understand what I mean, see this django ticket: http://code.djangoproject.com/ticket/13358
I think it works for all(), but not add().
RelatedManager.add() calls RelatedManager._add_items() which calls Manager.bulk_create().
So if you extend Manager.bulk_create(), you might be able to achieve what you are after.