I want to manage objects in django admin, though I would like to be able only to edit objects with a specific value of some attribute. Precisely I have now in admin.py:
class UnitAdmin(admin.ModelAdmin):
list_display = ('type', 'name', 'result_file')
list_filter = ['type']
admin.site.register(Unit, UnitAdmin)
And I would like to manage only units with type='SomeSpecificType'. I saw something with overriding SimpleListFilter class, though I can't see how this applies here.
You have to override the get_queryset in de modelAdmin and filter objects that have type='SomeSpecificType.
class UnitAdmin(admin.ModelAdmin):
...
def get_queryset(self, request):
qs = super(UnitAdmin, self).get_queryset(request)
return qs.filter(type='SomeSpecificType')
You can do
class UnitAdmin(admin.ModelAdmin):
list_display = ('type', 'name', 'result_file')
list_filter = ['type']
def get_readonly_fields(self, request, obj=None):
if obj and obj.type == 'SomeSpecificType':
return []
return ["type", "name", "result_file"]
Related
I want to inherit Generic Filtering include (filterset_fields, search_fields, ordering_fields in extra action sold. So how to do it, and any way better for this case?
class ApartmentViewset(viewsets.ModelViewSet):
queryset = Apartment.objects.all().order_by('-timestamp')
serializer_class = ApartmentSerializer
# Set permission for only user owner apartment can edit it.
permission_classes = [
permissions.IsAuthenticatedOrReadOnly, IsOwnerApartmentOrReadOnly]
# Add search by address, filter by district and ordering by price
filter_backends = [filters.SearchFilter,
DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ['district']
search_fields = ['address', 'seller']
ordering_fields = (
'price',
)
# fill current username when create new apartment
def perform_create(self, serializer):
serializer.save(seller=self.request.user)
#action(detail=False)
def sold(self, request):
queryset = self.queryset.filter(issold=True)
serialize = self.serializer_class(queryset, many=True)
return Response(serialize.data)
Generic View (and hence all classes that inherit from it) in DRF has a filter_queryset method which is called by the various mixins to perform filtering, so you can simply call that in your method. If you also want pagination there are the methods paginate_queryset and get_paginated_response:
class ApartmentViewset(viewsets.ModelViewSet):
...
#action(detail=False)
def sold(self, request):
queryset = self.filter_queryset(self.queryset.filter(issold=True))
serialize = self.serializer_class(queryset, many=True)
return Response(serialize.data)
I have the following class in Django admin of a model:
class TopUpsAdmin(admin.ModelAdmin):
search_fields = ('user__email', 'user__phone_number',)
list_filter = ('status',)
Currently the default behavior of the filter is
if search box in the listing page is input with test_1 which is a user name and search up the result(url will have the following parameter /?q=test_1) , if select an option in the filter then the filter results also got affected by the search box query on the URL ?q=test_1&status__exact=1)
I would like to not have the search results filter the list when i choose a filter.
Reading Django docs only provide me how to override the filter with existed query in the parameter(which come from search box).
Any help would be appreciate
Create a Admin SimpleListFilter and apply the below mixin
from django.utils.translation import gettext_lazy as _
class FilterMixin:
def choices(self, changelist):
yield {
'selected': self.value() is None,
'query_string': changelist.get_query_string(remove=[self.parameter_name, 'q']),
'display': _('All'),
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == str(lookup),
'query_string': changelist.get_query_string({self.parameter_name: lookup}, remove=['q']),
'display': title,
}
Complete code-base
#models.py
class Person(models.Model):
class GenderChoice(models.IntegerChoices):
MALE = 1
FEMALE = 2
OTHER = 3
name = models.CharField(max_length=20)
gender = models.IntegerField(choices=GenderChoice.choices)
def __str__(self):
return self.name
# admin.py
class GenderFilter(FilterMixin, admin.SimpleListFilter):
title = 'Gender'
parameter_name = 'gender'
def lookups(self, request, model_admin):
return Person.GenderChoice.choices
def queryset(self, request, queryset):
if self.value():
return queryset.filter(**{'gender': int(self.value())})
else:
return queryset
class PersonAdmin(admin.ModelAdmin):
search_fields = ('name',)
list_filter = (GenderFilter,)
admin.site.register(Person, PersonAdmin)
I think you can override get_search_results method. See the documentation:https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_search_results
Example:
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'age')
search_fields = ('name',)
def get_search_results(self, request, queryset, search_term):
if len(request.GET) > 1:
search_term = None
queryset, use_distinct = super().get_search_results(request,
queryset,
search_term)
return queryset, use_distinct
I have several fields in list_filter. I have to create a generic model.SimpleListFilter that is called for every value in list_filter.
I cannot create individual class for every list_filter.
The best solution would be something like:
class MyModelAdmin(MyCustomFilter):
list_filter = ('client_name', 'case_received_date')`
The lookups and queryset method in MyCustomFilter gets called for each list_filter field.
# admin.py
class MyModelAdmin(admin.ModelAdmin):
list_display = ('client_name','client_application_number')
list_filter = ('client_name', 'case_received_date', MyCustomFilter)
class MyCustomFilter(admin.SimpleListFilter):
def lookups(self, request, model_admin):
pass
def queryset(self, request, queryset):
# Apply the filter selected, if any
pass
You can use a function to generate an infinite number of anonymous classes. Here's an update to your code:
class MyCustomFilter(admin.SimpleListFilter):
def lookups(self, request, model_admin):
pass
def queryset(self, request, queryset):
# Apply the filter selected, if any
pass
def my_custom_filter_maker(field):
class MyAnonymousCls(MyCustomFilter):
title = field
parameter_name = field
return MyAnonymousCls
class MyModelAdmin(admin.ModelAdmin):
list_display = ('client_name','client_application_number')
list_filter = ('client_name', 'case_received_date',
my_custom_filter_maker('field_name'), my_custom_filter_maker('another_field'))
In User model i have a method:
#cached_property
def income(self):
return PartnerIncome.objects.all().aggregate(Sum('income'))['income__sum']*self.share_of_profit
PartnerIncome model:
class PartnerIncome(models.Model):
title = models.CharField(max_length=255)
income = models.FloatField(default=0)
Now i want to show 'inccome' in list_display=('income', ) at admin panel, but each object make extra query to database. How i can make PartnerIncome.objects.all().aggregate(Sum('income')) as a global variable for admin change list...
How about just overriding the get_queryset method in your ModelAdmin?
class UserAdmin(admin.ModelAdmin):
list_display = ('income', ...)
def income(self, obj):
return obj.income
def get_queryset(self, request):
queryset = super(UserAdmin, self).get_queryset(request)
# you logic here to `annotate`the queryset with income
return queryset
Documentation on aggregation:
https://docs.djangoproject.com/en/dev/topics/db/aggregation/
Without me really understanding your models and business logic.. here's a snippet from my own code as an example:
class AuthorAdmin(admin.ModelAdmin):
list_display = ('__str__', 'books_count',)
def books_count(self, obj):
return obj.books_count
def get_queryset(self, request):
return super(AuthorAdmin, self).get_queryset(
request).annotate(books_count=Count('books'))
class MyTemplateAdmin(admin.ModelAdmin):
list_display = ('name')
search_fields = ['name']
inlines = [
Template1Inline,
Template2Inline,
Template3Inline,
]
This works fine. But what I need is to make it dynamic. Whenever the admin adds a new Template to the MyTemplate Model, that needs to be added to the inlines.
Is there a way to do this? Please comment if I am not clear enough on my question.
Thanks in advance!
Just Override the admin's get_inline_instances.
def get_inline_instances(self, request, obj=None):
_inlines = super().get_inline_instances(request, obj=None)
custom_inline = YourDynamicInline(self.model, self.admin_site)
_inlines.append(custom_inline)
return _inlines
I haven't tested this, but in theory you could do:
class MyTemplateAdmin(admin.ModelAdmin):
def __init__(self, *args, **kwargs):
super(MyTemplateAdmin, self).__init__(*args, **kwargs)
#see if there are new templates
#and set the inlines list property
list_display = ('name')
search_fields = ['name']
Hope that helps you out.
In admin.py for the Templates:
class Template1Inline(admin.TabularInline)
pass
class Template2Inline(admin.TabularInline)
pass
Then in the admin.py for MyTemplateAdmin:
import sys, inspect, Templates.admin
class MyTemplateAdmin(admin.ModelAdmin):
list_display = ('name')
search_fields = ['name']
def __init__(self, *args, **kwargs):
inlines = [class_type[1] for class_type in inspect.getmembers(Templates.admin, inspect.isclass)]
super(MyTemplateAdmin, self).__init__(*args, **kwargs)
Templates.admin may not be correct depending on how you have your project setup, but the point is you just import the module that has the Template1Inline classes.
Just a quick idea.
from django.contrib import admin
from mymodule import signals
class MyModuleAdmin(admin.ModelAdmin):
def add_view(self, *args, **kwargs):
signals.my_dynamic_inline_hook_signal.send(
sender = self,
inlines = self.inlines,
args = args,
kwargs = kwargs
)
I'm not completely sure this is what you are looking for. You want inlines that are different instances of the same model?
One way of creating the inlines dynamically is with type() and adding them in get_inline_instances()
class MyTemplateAdmin(admin.ModelAdmin):
list_display = ('name')
search_fields = ['name']
inlines = [some_other_inline]
def get_inline_instances(self, request, obj=None):
new_inlines = []
for inline in (Template, Template2,Template3, ...): # I don't know how you want to get these.
attrs = {
'model': MyTemplate,
# 'extra': 1, Add extra attributes
}
# Create a new uninstanciated inline class
new_inlines.append(type('{}Inline'.format(inline),
(admin.TabularInline, ), attrs))
# Add the dynamically created inlines along with the ordinary.
self.inlines = self.inlines + new_inlines
return super(CommunityAdmin, self).get_inline_instances(request, obj)