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?
Related
I have a class Set with a many-to-many relationship to Item. I have lots of 'set' objects all containing lots of 'items'.
However, the Item class has been subclassed to create Article, Podcast, Video, and Episode. Basically, everything on the database was originally an Item. If my_set is a Set instance, containing Items - how do I create a Queryset which returns those objects in their subclass form? Ie, rather than me getting a Queryset full of Item instances, I get a Queryset with Article, Episode, Video, Podcast instances.
How would I get `my_set.objects.all().as_subclass()' to work?
class Item(models.Model, AdminVideoMixin):
base_attributes = 'foo'
def as_episode(self):
return Episode.objects.get(slug=self.slug)
class Video(Item):
class specific fields
class Article(Item):
class specific fields
class Podcast(Item):
class specific fields
class Episode(Item):
class specific fields
class Set(Item):
front_page = models.BooleanField(max_length=300, blank=False, default=False, null=False)
items = models.ManyToManyField(Item, related_name='in_sets', through='SetMeta', max_length=5000,)
def get_absolute_url(self):
return reverse('foo')
def ordered(self):
return self.items.all().order_by('-itemOrder__order')
def episodes(self):
episode_list = []
for each_item in self.items.all():
episode_list.append(each_item.as_episode())
return episode_list
def __str__(self):
return self.title
As you can see I tried two methods - to write a model method on Item() which returns itself as an Episode - but this only worked for single instances rather than a Queryset. As such I wrote a method on Set which can perform that method on all items within the self, but this doesn't produce a Queryset, just a list, and it feels messy?
Update: have just skimmed the django-polymorphic documentation again, and it seems to be exactly what you want. So the rest of my answer is probably not very useful, unless you are prohibited from taking code out of django-packages
I don't think Django provides a way to express a queryset that returns objects of more than one model type. Querysets are supposed to map inro SQL queries, and I don't think SQL can return rows from more than one table mixed up. (I'm not an SQL expert, so I may be wrong). However, if you don't want a list, Python provides a means to take a queryset and apply a transformation to each Item instance it returns: a generator function. So, for example, you could code
def items_as_subclasses(qs):
for instance in qs:
try:
yield instance.video
continue
except Video.DoesNotExist:
pass
try:
yield instance.article
continue
except Article.DoesNotExist:
pass
try: ...
raise ProbableCodingError(
f"Item instance id={instance.id} does not have a known subclass"
)
and then write
for item_subclass_instance in items_as_subclasses(queryset):
# whatever
or indeed pass "items":items_as_subclasses( queryset) into a template rendering
context.
If there is a long list of possible subclasses it might be better to have a subclass_type field in the base class, and use that to go straight to the only valid subclass field.
There's a nullable OneToOne link from the base class to its particular subclass, so you can write querysets that interrogate subclasses.
Or you could investigate django-polymorphic, which I once skimmed, and which I vaguely remember is for this sort of usage.
Is there a way to filter globally a Django model? We need to set a filter in a single place so that it gets applied in all queries generated by Django ORM including related objects lookups etc. Example:
class A(Model):
n = IntegerField()
class B(Model):
a = ForeignKey(A)
We want to set on A a global filter id__gte=10 (static to make it simple). The filter must be then automatically applied also when doing related queries, e.g.
B.objects.filter(a__n=123) # this code cannot be modified
should expand somehow magically to an equivalent of
B.objects.filter(a__n=123, a__id__gte=10)
We can change models, managers, querysets but we cannot change the code where objects are actually queried (a lot of code, third party apps, generic API).
What about creating a view in the database with the filter and create a Django model pointing to the view?
You should make a custom manager and modify an initial QuerySet. Check out the docs.
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author='Roald Dahl')
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
Then you should use your custom manager (dahl_objects) instead of objects and all queries will be modified.
Or you can override objects manager itself
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = DahlBookManager() # The Dahl-specific manager
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()
Lets say i have 3 classes, A, B, C.
class A(models.Model):
comment = models.CharField(max_length=600, default="None")
rating = models.IntegerField(default=1, choices=CHOICES, name='rating')
date = models.CharField(max_length=50, default='nonee')
class B(models.Model):
Aname = models.ForeignKey('A', related_name='AB')
classC = models.ForeignKey('C', related_name='BC')
class C(models.Model)
#some info
def average_rating(self):
return self.?????.all().aggregate(Avg('rating')).values()[0]
How is it that I go from a view where my self is an object, all the way back to Class A so that I can aggregate the rating numbers. If i understand this correctly, the whole point of class B is just to be an object which shows relationships? I have been able to go between two classes, but when a third "relational" one is there i can't seem to get it to work.
When an operation needs to be performed on a recordset (queryset) basis rather than single record (model), then you should consider custom managers.
Adding extra Manager methods is the preferred way to add “table-level” functionality to your models. (For “row-level” functionality – i.e., functions that act on a single instance of a model object – use Model methods, not custom Manager methods.)
You don't need class B at all. What you need is a ManyToManyField between A and C; that will, behind the scenes, create a table similar to B, but unless you actually need to add fields on that table you're better off not defining it explicitly.
Once you've added the M2M on C, your average_rating method can use it directly:
class C(models.Model)
model_a_s = models.ManyToManyField('A')
def average_rating(self):
return self.model_a_s.all().aggregate(Avg('rating')).values()[0]
(Note, the title of your question is a bit confusing; there are no views involved here at all.)
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.