I have an Order model with a custom model manager called OrderManager as follows.
class Order(models.Model):
# Model fields are declared here.
...
objects = OrderManager()
all_objects = models.Manager()
class OrderManager(models.Manager):
def get_queryset(self):
return super(OrderManager, self).get_queryset().filter(Q(x='y') | Q(x='y'))
I updated OrderManager class by adding another filter to the queryset, and the new implementation turned out to be
class OrderManager(models.Manager):
def get_queryset(self):
return super(OrderManager, self).get_queryset().filter(~Q(order_status='Cancelled'), Q(x='y') | Q(x='y'))
However, when I retrieve data from the Order model using the updated manager, it ignores the changes and still brings the objects whose order_status is 'Cancelled'.
orders = Order.objects.all() # Retrieves cancelled orders as well.
On the other hand, applying the last additional filter explicitly works:
orders = Order.objects.filter(~Q(order_status='Cancelled') # Ignores the cancelled orders.
Is it a migration issue or what? I feel like there is a silly mistake I'm making somewhere (probably not related to this code) but I just got blind. Any suggestions are much appreciated.
ps: I'm using Django 1.5.5 on Webfaction.
Before django 1.6 method was named get_query_set, not get_queryset (check additional underscore).
So in django 1.5 and earlier it is needed to override get_query_set method:
class OrderManager(models.Manager):
def get_query_set(self):
return super(OrderManager, self).get_query_set().filter(~Q(order_status='Cancelled'), Q(x='y') | Q(x='y'))
Related
One of my models has number of related objects in it's __str__. This makes the admin site run very slow.
Is it possible to set up the model in a way that would always do prefetch_related, even if not explicitly requested?
You can implement a manager [Django-doc] that will automatically add a .prefetch_related(..) to the queryset.
For example:
class MyModelManager(models.Manager):
def get_queryset(self):
return super().get_queryset().prefetch_related('related_model')
class MyModel(models.Model):
# …
_base_manager = MyModelManager()
objects = MyModelManager()
Adding as an answer since I cannot add a comment (this answer):
The _base_manager attribute needs to be a class and not an object.
class MyModel(models.Model):
# …
_base_manager = MyModelManager
objects = MyModelManager()
I have a Abstract Model SoftDelete like follow.
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=0)
deleted_at = models.DateTimeField(null=True)
objects = SoftDeleteManager()
def delete(self):
self.is_deleted = True
self.deleted_at = timezone.now()
self.save()
class Meta:
abstract = True
class Employee(SafeDeleteModel):
pass
Whenever model gets deleted i set is_deleted to True and updating the timestamp deleted_at, and created the custom manager to override initial queryset which return only non deleted fields(is_deleted=False).
employee = Employee.objects.get(pk=1)
employee.delete()
employee.refresh_from_db() // not raising DoesNotExist
But lets say i have a Employee model which uses the SafeDeleteModel for soft delete, after deleting the model like Employee.objects.get(pk=1).delete() when i call employee.refresh_from_db(),its not raising DoesNotExist, but updates the value of is_deleted, deleted_at as expected, what mistake i made here, why its not raising DoesNotExist?
There's been a change in Django 2.1: refresh_from_db() now uses a model's _base_manager, not the _default_manager anymore, like for related queries. This to ensure an object can be refreshed even if it cannot be found by the default manager.
So you should set your SoftDeleteManager as the base manager using base_manager_name. But note this comment:
Base managers aren’t used when querying on related models. For example, if the Question model from the tutorial had a deleted field and a base manager that filters out instances with deleted=True, a queryset like Choice.objects.filter(question__name__startswith='What') would include choices related to deleted questions.
Also I don't know how you would retrieve any object that has been deleted after you make this change, except if you make a special manager to not filter the deleted objects (e.g. deleted_objects).
Note also that I would expect the safedelete package you referred to in your comments to have the same issue, as it's not changing the _base_manager either.
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.
I use custom abstract model with manager throughout my project.
class BaseQueryset(models.QuerySet):
pass
class BaseManager(models.Manager):
queryset_class = BaseQueryset
def get_queryset(self, exclude_no_published=True):
""" exclude all objects with is_published=False by default """
q = self.queryset_class(self.model)
if exclude_no_published:
q = q.exclude(is_published=False)
return q
def all_objects(self):
""" allows geting all objects in admin """
return self.get_queryset(exclude_no_published=False)
class BaseAbstractModel(models.Model):
is_published = models.BooleanField(default=True)
objects = BaseManager()
class Meta:
abstract = True
All models inherit from this abstract model and I need a way to represent all objects in admin. So I wrote my own mixin for admin classes with get_queryset method
class AdminFullQuerysetMixin(object):
def get_queryset(self, request):
"""
Allows showing all objects despite on is_public=False
"""
qs = self.model.objects.all_objects()
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs
There is my typical admin class:
#admin.register(SomeModel)
class SomeModelAdmin(AdminFullQuerysetMixin, admin.ModelAdmin):
list_display = ('name', 'slug', 'is_published')
list_filter = ('is_published',)
list_editable = ('is_published',)
All works fine, I can see all objects in admin whether with is_published False or True. But such attributes like list_filter or list_editable don't work, when I use it in admin objects list page. There is no exception provided, just text at the top of the list: "Please correct the error below".
What methods except get_queryset should I override for solving my problem?
You may want to read this :
if you use custom Manager objects, take note that the first Manager
Django encounters (in the order in which they’re defined in the model)
has a special status. Django interprets the first Manager defined in a
class as the “default” Manager, and several parts of Django (...) will
use that Manager exclusively for that model. As a result, it’s a
good idea to be careful in your choice of default manager in order to
avoid a situation where overriding get_queryset() results in an
inability to retrieve objects you’d like to work with.
I strongly suspect you fell upon one of those cases...
The solution would then be to change your model to:
class BaseAbstractModel(models.Model):
is_published = models.BooleanField(default=True)
# this one will be the default manager
all_objects = models.Manager()
# and this one will be known as 'objects'
objects = BaseManager()
class Meta:
abstract = True
Then you can remove your AdminFullQuerysetMixin (or rewrite it's get_queryset() method to use self.model._default_manager instead)
NB : I may of course be wrong and the problem be totally unrelated ;)
One of my models has a deleted flag, which is used to hide objects globally:
class NondeletedManager(models.Manager):
"""Returns only objects which haven't been deleted"""
def get_query_set(self):
return super(NondeletedManager, self).get_query_set().exclude(deleted=True)
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
objects = NondeletedManager()
all_conversations = models.Manager() # includes deleted conversations
How can I override the default queryset used by Django admin module to include deleted conversations?
You can override get_queryset method in your model admin class.
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
Note in Django<=1.5 the method was named just queryset.
Konrad is correct, but this is more difficult than the example given in the documentation.
Deleted conversations can't be included in a queryset that already excludes them. So I don't see an option other than re-implementing admin.ModelAdmin.queryset entirely.
class ConversationAdmin (admin.ModelAdmin):
def queryset (self, request):
qs = Conversation.all_conversations
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs
You can do this with a Django proxy model.
# models.py
class UnfilteredConversation(Conversation):
class Meta:
proxy = True
# this will be the 'default manager' used in the Admin, and elsewhere
objects = models.Manager()
# admin.py
#admin.register(UnfilteredConversation)
class UnfilteredConversationAdmin(Conversation):
# regular ModelAdmin stuff here
...
Or, if you have an existing ModelAdmin class you want to re-use:
admin.site.register(UnfilteredConversation, ConversationAdmin)
This approach avoids issues that can arise with overriding the default manager on the original Conversation model - because the default manager is also used in ManyToMany relationships and reverse ForeignKey relationships.
What would be so wrong with the following:
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
objects = models.Manager() # includes deleted conversations
nondeleted_conversations = NondeletedManager()
So in your own apps/projects, you use Conversation.nondeleted_conversations() and let the built-in admin app do it's thing.
Natan Yellin is correct, but you can change the managers order and the first will be the default, then it is the used by the admin:
class Conversation(BaseModel):
...
deleted = models.BooleanField(default=False)
all_conversations = models.Manager() # includes deleted conversations
objects = NondeletedManager()
The admin implementation of get_queryset() use ._default_manager instead .objects, as show next
qs = self.model._default_manager.get_queryset()
ref Django github BaseModelAdmin implementation
This only ensures that every time you use YourModel.objects, you will not include deleted objects, but the generic views and others uses ._default_manager too. Then if you don't override get_queryset is not a solution. I've just check on a ListView and admin.
The accepted solution works great for me but I needed a little bit more flexibility, so I ended up extending the changelist view to add in a custom queryset parameter. I can now configure my default queryset/filter as such and it can still be modified by using a different filter (get parameters):
def changelist_view(self, request, extra_context=None):
if len(request.GET) == 0 :
q = request.GET.copy()
q['status__gt'] = 4
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(WorksheetAdmin,self).changelist_view(request, extra_context=extra_context)
To extend on some of these answers with what I found most concise and useful.
I've made the assumption you have a field like "name" to show the entries.
# admin.py
from django.contrib import admin
#admin.register(Conversation)
class ConversationAdmin(admin.ModelAdmin):
list_display = ('name', '_is_deleted')
# Nice to have but indicates that an object is deleted
#admin.display(
boolean=True,
ordering='deleted'
)
def _is_deleted(self, obj):
return obj.deleted
def get_queryset(self, request):
return Conversation.all_conversations
Which will give you an interface like:
The problem I found with subclassing a model was that it caused issues with meta inheritance and reverse-path lookups.