Can I use Django model function as a field in admin? - django

I have a model function that returns a numerical value. I would like to be able to filter and sort by that value. When I tried adding it to list_filter Django complained that the model had no such field. Can I tell Django to treat it like a field? If so, how?

Did you tried to use it as a class property, though I didn't try it?
class SomeModel(models.Model):
#property
def list_filter(self):
""" Do your stuffs here """

You can write a custom filter and pass it in list_filter:
from django.contrib.admin import SimpleListFilter
class CustomFilter(SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('active status')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'status'
def lookups(self, request, model_admin):
if request.user.is_superuser:
return (
('active', _('Active')),
('not_active', _('Not Active')),
)
def queryset(self, request, queryset):
# do something here with queryset
class MyAdmin(admin.ModelAdmin):
list_filter = (CustomFilter, 'other_model_field')
Please see here for more information about ModelAdmin.list_filter

Related

DJango, custom filter with list_display custom method

I'm trying to filter by the custom method i made for language column. In admin.py i have the following;
from django.contrib import admin
from .models import *
class MovieAdminModel(admin.ModelAdmin):
search_fields= ('title_en')
list_display = ('get_language',)
list_filter = ('get_language')
def get_language(self, obj):
if obj.language==1:
return 'Persian With English Subtitle'
else:
return 'Persian'
get_language.admin_order_field = "language"
get_language.short_description = "language"
In list_filter i use get_language and this error is returned The value of 'list_filter[0]' refers to 'get_language', which does not refer to a Field. If i use language in list_filter , the filters shows yes or no. Filter should show either Persian With English Subtitle or Persian. How do i solve?
You need to create a custom admin filter for this task, something like this:
from django.contrib.admin import SimpleListFilter
class LanguageFilter(SimpleListFilter):
title = 'Language'
parameter_name = 'language'
def lookups(self, request, model_admin):
return [
('Persian With English Subtitle', 'Persian With English Subtitle'),
('Persian', 'Persian')
]
def queryset(self, request, queryset):
value = self.value()
if value == 'Persian With English Subtitle':
return queryset.filter(language=1)
return queryset.exclude(language=1)
class MovieAdminModel(admin.ModelAdmin):
list_filter = (
LanguageFilter,
)
...

Creating a generic search view in Django

I am struggling to create my custom generic view in django to easily create search pages for certain models. I'd like to use it like this:
class MyModelSearchView(SearchView):
template_name = 'path/to/template.html'
model = MyModel
fields = ['name', 'email', 'whatever']
which will result in a view that returns a search form on GET and both form and results on POST.
The fields specifies which fields of MyModel will be available for a user to search.
class SearchView(FormView):
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
return SearchForm()
def post(self, request, *args, **kwargs):
# perform searching and return results
The problem with the code above is that form will not be submitted if certain fields are not be properly filled. User should be allowed to provide only part of fields to search but with the code I provided the form generated with ModelForm prevents that (for example because a field in a model cannot be blank).
My questions are:
Is it possible to generate a form based on a model to omit this behaviour?
Or is there any simpler way to create SearchView class?
I don't want to manually write forms if it's possible.
One way to accomplish this is to set blank=True on the field in MyModel, as indicated in the docs:
If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
But for this to be a generic solution, you can't count on being able to modify the model fields. You can instead set the fields' required attribute to False immediately after the instance is created:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (field_name, field) in self.fields.items():
field.required = False
Since you're using the ModelForm for searching, you should set all the fields as required=False, by overriding the __init__ method:
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
return SearchForm()
Though I suggest you should user django-filter, which makes it easier and cleaner to filter your searches. First you need to install it:
pip install django-filter
Then add it to your INSTALLED_APPS. After that you can create a filters.py file in your app:
# myapp/filters.py
import django_filters as filters
from .models import MyModel
MyModelFilterSet(filters.FilterSet):
class Meta:
model = MyModel
fields = ['name', 'email', 'whatever']
By default it's going to filter with the __exact lookup. You can change this in a couple of ways, just take a look here and here. To know which lookups you can use, take a look here.
After creating your filters.py file you can add it to a View, like a ListView:
# myapp/views.py
from django.views.generic import ListView
from .filters import MyModelFilterSet
from .models import MyModel
class MyModelSearchView(ListView):
template_name = 'path/to/template.html'
model = MyModel
def get_queryset(self):
qs = self.model.objects.all()
filtered_model_list = MyModelFilterSet(self.request.GET, queryset=qs)
return filtered_model_list.qs
There's a lot more you can do with django-filter. Here's the full documentation.

Get the current queryset shown in ChangeList from the ModelAdmin class

I need to work in ModelAdmin with the elements shown in the ChangeList, but I don't know how to get the current queryset.
For example, if now the first 100 elements are being shown, I want to work with this set, and if the user pass to the next 100, I want to have the new 100 elements in the set.
Other example is when some user applies some filter.
In my Model Admin I have:
list_display = getListDisplay(qs)
And I want to pass to getListDisplay the current queryset, because depending on it, the list_display will be different.
Is there any current queryset attribute somewhere accessible from ModelAdmin class?
After UPD 1 I cannot make this works:
class YourAdmin(admin.ModelAdmin):
def get_queryset(self):
qs = super(YourAdmin, self).get_queryset()
return qs
def __init__(self, *args, **kwargs):
super(YourAdmin, self).__init__(*args, **kwargs)
qs = self.get_queryset()
Here's a link to the documentation for ModelAdmin.get_queryset()
Edit in response to your comments:
It seems to me that what you want to do is build the list_display dynamically. Django has a get_list_display method for model admin. This method receives the request which you can then pass to get_queryset:
class YourAdmin(admin.ModelAdmin):
def get_list_display(self, request):
qs = self.get_queryset(request)
'''
Now build the list_display as a list or tuple
'''
.
.
return list_display
In django admin, there is a thing called actions. It is described in here and works like this:
class YourAdmin(admin.ModelAdmin):
def make_something(self, request, queryset):
queryset.update(status='p')
actions = ['make_something',]
Maybe queryset is the thing you need
UPD 1: After comment, I understood that you need to modify some attributes of your ModelAdmin ojbect. So you can do that right after initialization:
class YourAdmin(admin.ModelAdmin):
def __init__(self, *args, **kwargs):
super(YourAdmin, self).__init__(*args, **kwargs)
qs = self.getquery_set()
// modify your list_display depending on qs
self.list_display = ['some','fileds','here']
Some third-party apps like xadmin allow you to change list_display on the fly, but django doesn't have this feature by default.

Django-admin order by multiple fields

How do I order by multiple fields in the django-admin?
Thanks
Try this:
Set ordering in your model Meta:
class Meta:
ordering = ["some_field", "other_field"]
and add this class in admin.py:
from django.contrib.admin.views.main import ChangeList
class SpecialOrderingChangeList(ChangeList):
"""
Django 1.3 ordering problem workaround
from 1.4 it's enough to use `ordering` variable
"""
def get_query_set(self):
queryset = super(SpecialOrderingChangeList, self).get_query_set()
return queryset.order_by(*self.model._meta.ordering)
Add this method in your admin.ModelAdmin
def get_changelist(self, request, **kwargs):
return SpecialOrderingChangeList
source: https://groups.google.com/forum/?fromgroups#!topic/django-users/PvjClVVgD-s
until django 1.4 (currently in alpha) the django admin only orders by the first column in Meta ordering. You can work around this by overriding the queryset:
class MyAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(MyAdmin, self).queryset(request)
qs = qs.order_by('last_name', 'first_name')
return qs
Further to user535010's response above:
I struggled because after adding the suggested code I was no longer able to order the fields by clicking on the headings in the admin list view. I modified the get_changelist method suggested for MyModelAdmin as follows:
def get_changelist(self, request, **kwargs): #ordering issue in 1.3 workaround
try:
if not request.GET['o']:
return SpecialOrderingChangeList
except KeyError:
pass
return super(MyModelAdmin, self).get_changelist(request)
Django model admin supports ordering by multiple values in Django 2.0+. You can now use it like this:
class MyAdmin(admin.ModelAdmin):
ordering = ['last_name', 'first_name']
The function required to make click-ordering work with the multi-column sort fix is this:
def get_changelist(self, request, **kwargs):
try:
if request.GET['o']:
return super(ModelAdmin, self).get_changelist(request)
except KeyError:
pass
return SpecialOrderingChangeList
Other way round to jenniwren's answer :-)

Django-Admin: CharField as TextArea

I have
class Cab(models.Model):
name = models.CharField( max_length=20 )
descr = models.CharField( max_length=2000 )
class Cab_Admin(admin.ModelAdmin):
ordering = ('name',)
list_display = ('name','descr', )
# what to write here to make descr using TextArea?
admin.site.register( Cab, Cab_Admin )
how to assign TextArea widget to 'descr' field in admin interface?
upd:
In Admin interface only!
Good idea to use ModelForm.
You will have to create a forms.ModelForm that will describe how you want the descr field to be displayed, and then tell admin.ModelAdmin to use that form. For example:
from django import forms
class CabModelForm( forms.ModelForm ):
descr = forms.CharField( widget=forms.Textarea )
class Meta:
model = Cab
class Cab_Admin( admin.ModelAdmin ):
form = CabModelForm
The form attribute of admin.ModelAdmin is documented in the official Django documentation. Here is one place to look at.
For this case, the best option is probably just to use a TextField instead of CharField in your model. You can also override the formfield_for_dbfield method of your ModelAdmin class:
class CabAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(CabAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'descr':
formfield.widget = forms.Textarea(attrs=formfield.widget.attrs)
return formfield
Ayaz has pretty much spot on, except for a slight change(?!):
class MessageAdminForm(forms.ModelForm):
class Meta:
model = Message
widgets = {
'text': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
}
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
admin.site.register(Message, MessageAdmin)
So, you don't need to redefine a field in the ModelForm to change it's widget, just set the widgets dict in Meta.
You don't need to create the form class yourself:
from django.contrib import admin
from django import forms
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'descr': forms.Textarea}
return super().get_form(request, obj, **kwargs)
admin.site.register(MyModel, MyModelAdmin)
See ModelAdmin.get_form.
You can subclass your own field with needed formfield method:
class CharFieldWithTextarea(models.CharField):
def formfield(self, **kwargs):
kwargs.update({"widget": forms.Textarea})
return super(CharFieldWithTextarea, self).formfield(**kwargs)
This will take affect on all generated forms.
If you are trying to change the Textarea on admin.py, this is the solution that worked for me:
from django import forms
from django.contrib import admin
from django.db import models
from django.forms import TextInput, Textarea
from books.models import Book
class BookForm(forms.ModelForm):
description = forms.CharField( widget=forms.Textarea(attrs={'rows': 5, 'cols': 100}))
class Meta:
model = Book
class BookAdmin(admin.ModelAdmin):
form = BookForm
admin.site.register(Book, BookAdmin)
If you are using a MySQL DB, your column length will usually be autoset to 250 characters, so you will want to run an ALTER TABLE to change the length in you MySQL DB, so that you can take advantage of the new larger Textarea that you have in you Admin Django site.
Instead of a models.CharField, use a models.TextField for descr.
You can use models.TextField for this purpose:
class Sample(models.Model):
field1 = models.CharField(max_length=128)
field2 = models.TextField(max_length=1024*2) # Will be rendered as textarea
Wanted to expand on Carl Meyer's answer, which works perfectly till this date.
I always use TextField instead of CharField (with or without choices) and impose character limits on UI/API side rather than at DB level. To make this work dynamically:
from django import forms
from django.contrib import admin
class BaseAdmin(admin.ModelAdmin):
"""
Base admin capable of forcing widget conversion
"""
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(BaseAdmin, self).formfield_for_dbfield(
db_field, **kwargs)
display_as_charfield = getattr(self, 'display_as_charfield', [])
display_as_choicefield = getattr(self, 'display_as_choicefield', [])
if db_field.name in display_as_charfield:
formfield.widget = forms.TextInput(attrs=formfield.widget.attrs)
elif db_field.name in display_as_choicefield:
formfield.widget = forms.Select(choices=formfield.choices,
attrs=formfield.widget.attrs)
return formfield
I have a model name Post where title, slug & state are TextFields and state has choices. The admin definition looks like:
#admin.register(Post)
class PostAdmin(BaseAdmin):
list_display = ('pk', 'title', 'author', 'org', 'state', 'created',)
search_fields = [
'title',
'author__username',
]
display_as_charfield = ['title', 'slug']
display_as_choicefield = ['state']
Thought others looking for answers might find this useful.