I have no idea how to remove "--------" default action value in django admin.
or replace "------" with any other string ("Select Option").
Thanks..
You could try overriding the ModelAdmin.get_action_choices method. However, this is an undocumented, internal method, so I wouldn't recommend changing it unless it's absolutely essential that you remove/replace the dashes.
remove default action
class YourModelAdmin(ModelAdmin):
def get_action_choices(self, request):
choices = super(DocumentAdmin, self).get_action_choices(request)
# choices is a list, just change it.
# the first is the BLANK_CHOICE_DASH
choices.pop(0)
return choices
replace with other string
class YourModelAdmin(ModelAdmin):
def get_action_choices(self, request):
default_choices = [("", "-----other string----")]
return super(DocumentAdmin, self).get_action_choices(request, default_choices)
set default action for your action, you can see my answer here https://stackoverflow.com/a/41276533/1265727
With the help of #Alasdair my issue is resolved.
I use this code in model.py and its change my default option value to "Select Options"
from django.db.models.fields import BLANK_CHOICE_DASH
BLANK_CHOICE_DASH = [("", "---------")]
def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
"""
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in six.itervalues(self.get_actions(request)):
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
After some time later, i also find a solution of this problem..
You can also override empty_value_display for all admin pages with AdminSite.empty_value_display, or for specific fields like this:
from django.contrib import admin
class AuthorAdmin(admin.ModelAdmin):
fields = ('name', 'title', 'view_birth_date')
def view_birth_date(self, obj):
return obj.birth_date
view_birth_date.short_name = 'birth_date'
view_birth_date.empty_value_display = '???'
Related
We are using Django_Filterset in our project. I have been asked to set a default filter value for a foreign key column in the model
class RegFilter(django_filters.FilterSet):
class Meta:
model = models.Reg
fields = {
'id': ['exact'],
'nom_id': ['icontains'],
'nom_name': ['icontains'],
'product__name': ['icontains']
}
The product name should default to a product already in the db when the initial screen is displayed - any idea how this can be achieved? Appreciate your help.
I built on the answer given by #Gayathri and fixed a few small issues. He had a typo in his code, and the code wouldn't work with multiple choice fields, since they require the MultiValueDict functionality for multiple entries.
Given a rather regular view, here is the full code (in Python3 syntax):
class BookListView(FilterView):
model = Book
filterset_class = BookFilter
def get_filterset_kwargs(self, filterset_class):
kwargs = super().get_filterset_kwargs(filterset_class)
if kwargs['data'] is None:
filter_values = MultiValueDict()
else:
filter_values = kwargs['data'].copy()
if not filter_values:
# we need to use `setlist` for multi-valued fields to emulate this coming from a query dict
filter_values.setlist('status', ['new', 'ready'])
filter_values['sorting'] = '-created'
kwargs['data'] = filter_values
return kwargs
I managed to solve this one and thought the solution might help someone else in a situation similar to me.
In the views.py, override the FilterView method
def get_filterset_kwargs(self, filterset_class):
kwargs = super(RegFilter, self).get_filterset_kwargs(filterset_class)
if kwargs['data'] is None:
request_dict = {}
else:
request_dict = kwargs['data'].dict()
# This default will not be populated if any other filter options are chosen to restrict the query set
if not request_dict:
request_dict.update({
'product__name': 'ABC Product'
})
request_dict = kwargs['data']
return kwargs
This should now set the default product as 'ABC product' when no other options are passed to restrict the data fetched.
I would like to change the default selected action named "---------" (BLANK_CHOICE_DASH) to another specific action. Is there a better way to implement this than adding some javascript code that would override the action in load time?
1.Override the get_action_choices() method in your ModelAdmin, clear the default blank choice and reorder the list。
class YourModelAdmin(ModelAdmin):
def get_action_choices(self, request):
choices = super(YourModelAdmin, self).get_action_choices(request)
# choices is a list, just change it.
# the first is the BLANK_CHOICE_DASH
choices.pop(0)
# do something to change the list order
# the first one in list will be default option
choices.reverse()
return choices
2.Specific action.Override ModelAdmin.changelist_view, use extra_context to update action_form
ChoiceField.initial used to set the default selected choice.
so if your action name is "print_it", you can do this.
class YourModelAdmin(ModelAdmin):
def changelist_view(self,request, **kwargs):
choices = self.get_action_choices(request)
choices.pop(0) # clear default_choices
action_form = self.action_form(auto_id=None)
action_form.fields['action'].choices = choices
action_form.fields['action'].initial = 'print_it'
extra_context = {'action_form': action_form}
return super(DocumentAdmin, self).changelist_view(request, extra_context)
I think you can override the get_action_choices() method in the ModelAdmin.
class MyModelAdmin(admin.ModelAdmin):
def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
"""
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in six.itervalues(self.get_actions(request)):
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
in your admin.py file
class MyModelAdmin(admin.ModelAdmin):
def get_action_choices(self, request, **kwargs):
choices = super(MyModelAdmin, self).get_action_choices(request)
# choices is a list, just change it.
# the first is the BLANK_CHOICE_DASH
choices.pop(0)
# do something to change the list order
# the first one in list will be default option
choices.reverse()
return choices
and in your class
class TestCaseAdmin(MyModelAdmin):
I would like to make custom filters for django admin instead of the normal 'is_staff' and 'is_superuser'. I have read this list_filter in Django docs.
Custom Filters work in this way:
from datetime import date
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class DecadeBornListFilter(SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('decade born')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'decade'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
return (
('80s', _('in the eighties')),
('90s', _('in the nineties')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
# Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__gte=date(1980, 1, 1),
birthday__lte=date(1989, 12, 31))
if self.value() == '90s':
return queryset.filter(birthday__gte=date(1990, 1, 1),
birthday__lte=date(1999, 12, 31))
class PersonAdmin(ModelAdmin):
list_filter = (DecadeBornListFilter,)
But i have already made custom functions for list_display like this:
def Student_Country(self, obj):
return '%s' % obj.country
Student_Country.short_description = 'Student-Country'
Is it possible i could use the custom functions for list_display in list_filter instead of writing a new custom function for list_filter? Any suggestions or improvements are welcome.. Need some guidance on this... Thanks...
You can indeed add custom filters to admin filters by extending SimpleListFilter. For instance, if you want to add a continent filter for 'Africa' to the country admin filter used above, you can do the following:
In admin.py:
from django.contrib.admin import SimpleListFilter
class CountryFilter(SimpleListFilter):
title = 'country' # or use _('country') for translated title
parameter_name = 'country'
def lookups(self, request, model_admin):
countries = set([c.country for c in model_admin.model.objects.all()])
return [(c.id, c.name) for c in countries] + [
('AFRICA', 'AFRICA - ALL')]
def queryset(self, request, queryset):
if self.value() == 'AFRICA':
return queryset.filter(country__continent='Africa')
if self.value():
return queryset.filter(country__id__exact=self.value())
class CityAdmin(ModelAdmin):
list_filter = (CountryFilter,)
Your list_display ,method returns a string, but if I understand correctly, what you want to do is add a filter which allows selection of countries of students, correct?
For this simple relation filter, and in fact for the "Student-Country" list display column as well, you don't need to create a custom filter class, nor a custom list display method; this would suffice:
class MyAdmin(admin.ModelAdmin):
list_display = ('country', )
list_filter = ('country', )
The way django does list_filter, as explained in the docs, is first by automatically matching fields you provide to pre-built filter classes; these filters include CharField and ForeignKey.
list_display similarly automates the population of the changelist column using the field passed by retrieving the related objects and returning the unicode value of these (same as in the method you provided above).
In addition to Rick Westera answer, here is the Django Docs for this situation
ModelAdmin.list_filter
Set list_filter to activate filters in the right sidebar of the change list page of the admin
list_filter should be a list or tuple of elements
the lookup function is that show the exists values in admin;
in queryset function, self.value() is that was filtering by..
from django.contrib.admin import SimpleListFilter
class AllPriceFilter(admin.SimpleListFilter):
title = 'All Price'
parameter_name = 'All Price'
def lookups(self, request, model_admin):
return [(i.all_price, i.all_price) for i in model_admin.model.objects.all()]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(all_price__lte=self.value())
class MyAdmin(admin.ModelAdmin):
list_filter = (AllPriceFilter, )
I have a admin form with custom validation. Some of the form fields are displayed in the list view via list_editable. When I modify these fields via the list view the custom validation does not kick in. It does work when I use the regular change form, though. So the question is how do I validate changes done via the "change_list" page.
The code might make it clearer
class ProjectForm(ModelForm):
class Meta:
model = Project
def clean(self):
print "validating!"
data = self.cleaned_data
if data.get('on_frontpage') and not data.get('frontpage_image'):
raise ValidationError('To put a project on the frontpage you must \
specify a "Frontpage image" first.')
return data
class ProjectAdmin(AdminImageMixin, DisplayableAdmin, SortableAdmin):
form = ProjectForm
...
list_editable = ("status", "on_frontpage",)
list_display = ("title", "status", "on_frontpage")
Thanks!
Found it. One can specify the form used on the "change_list" page by overriding "get_changelist_formset" method in ModelAdmin:
https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L524
Override the ModelAdmin.get_changelist_formset(request, **kwargs) method:
from django.forms import BaseModelFormSet
class MyAdminFormSet(BaseModelFormSet):
pass
class MyModelAdmin(admin.ModelAdmin):
def get_changelist_formset(self, request, **kwargs):
kwargs['formset'] = MyAdminFormSet
return super().get_changelist_formset(request, **kwargs)
For more details please check the Django admin site documentation.
I think #Jorge Barata's is the correct answer, thank you very much.
Please allow me to attach a success example here.
class MyAdminFormSet(BaseModelFormSet):
def clean(self):
form_set = self.cleaned_data
for form_data in form_set:
if form_data['field1'] != form_data['field2']:
raise forms.ValidationError(f'Item: {form_data["id"]} is not valid')
return form_set
Tested on Django 2.2
I have a standard admin change form for an object, with the usual StackedInline forms for a ForeignKey relationship. I would like to be able to link each inline item to its corresponding full-sized change form, as the inline item has inlined items of its own, and I can't nest them.
I've tried everything from custom widgets to custom templates, and can't make anything work. So far, the "solutions" I've seen in the form of snippets just plain don't seem to work for inlines. I'm getting ready to try some DOM hacking with jQuery just to get it working and move on.
I hope I must be missing something very simple, as this seems like such a simple task!
Using Django 1.2.
There is a property called show_change_link since Django 1.8.
I did something like the following in my admin.py:
from django.utils.html import format_html
from django.core.urlresolvers import reverse
class MyModelInline(admin.TabularInline):
model = MyModel
def admin_link(self, instance):
url = reverse('admin:%s_%s_change' % (instance._meta.app_label,
instance._meta.module_name),
args=(instance.id,))
return format_html(u'Edit', url)
# … or if you want to include other fields:
return format_html(u'Edit: {}', url, instance.title)
readonly_fields = ('admin_link',)
The currently accepted solution here is good work, but it's out of date.
Since Django 1.3, there is a built-in property called show_change_link = True that addresses this issue.
This can be added to any StackedInline or TabularInline object. For example:
class ContactListInline(admin.TabularInline):
model = ContactList
fields = ('name', 'description', 'total_contacts',)
readonly_fields = ('name', 'description', 'total_contacts',)
show_change_link = True
The result will be something line this:
I had similar problem and I came up with custom widget plus some tweaks to model form. Here is the widget:
from django.utils.safestring import mark_safe
class ModelLinkWidget(forms.Widget):
def __init__(self, obj, attrs=None):
self.object = obj
super(ModelLinkWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
if self.object.pk:
return mark_safe(
u'<a target="_blank" href="../../../%s/%s/%s/">%s</a>' %\
(
self.object._meta.app_label,
self.object._meta.object_name.lower(),
self.object.pk, self.object
)
)
else:
return mark_safe(u'')
Now since widget for each inline need to get different object in constructor you can't just set it in standard way, but in Form's init method:
class TheForm(forms.ModelForm):
...
# required=False is essential cause we don't
# render input tag so there will be no value submitted.
link = forms.CharField(label='link', required=False)
def __init__(self, *args, **kwargs):
super(TheForm, self).__init__(*args, **kwargs)
# instance is always available, it just does or doesn't have pk.
self.fields['link'].widget = ModelLinkWidget(self.instance)
Quentin's answer above works, but you also need to specify fields = ('admin_link',)
There is a module for this purpose. Check out:
django-relatives
I think: args=[instance.id] should be args=[instance.pk]. It worked for me!