Fixed #24474 -- Allowed configuring the admin's empty change list value.
This commit is contained in:
parent
40f0a84cb1
commit
0207bdd2d4
|
@ -174,6 +174,7 @@ class RelatedFieldListFilter(FieldListFilter):
|
||||||
else:
|
else:
|
||||||
self.lookup_title = other_model._meta.verbose_name
|
self.lookup_title = other_model._meta.verbose_name
|
||||||
self.title = self.lookup_title
|
self.title = self.lookup_title
|
||||||
|
self.empty_value_display = model_admin.get_empty_value_display()
|
||||||
|
|
||||||
def has_output(self):
|
def has_output(self):
|
||||||
if self.field.null:
|
if self.field.null:
|
||||||
|
@ -189,7 +190,6 @@ class RelatedFieldListFilter(FieldListFilter):
|
||||||
return field.get_choices(include_blank=False)
|
return field.get_choices(include_blank=False)
|
||||||
|
|
||||||
def choices(self, cl):
|
def choices(self, cl):
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
yield {
|
yield {
|
||||||
'selected': self.lookup_val is None and not self.lookup_val_isnull,
|
'selected': self.lookup_val is None and not self.lookup_val_isnull,
|
||||||
'query_string': cl.get_query_string({},
|
'query_string': cl.get_query_string({},
|
||||||
|
@ -210,7 +210,7 @@ class RelatedFieldListFilter(FieldListFilter):
|
||||||
'query_string': cl.get_query_string({
|
'query_string': cl.get_query_string({
|
||||||
self.lookup_kwarg_isnull: 'True',
|
self.lookup_kwarg_isnull: 'True',
|
||||||
}, [self.lookup_kwarg]),
|
}, [self.lookup_kwarg]),
|
||||||
'display': EMPTY_CHANGELIST_VALUE,
|
'display': self.empty_value_display,
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldListFilter.register(lambda f: f.remote_field, RelatedFieldListFilter)
|
FieldListFilter.register(lambda f: f.remote_field, RelatedFieldListFilter)
|
||||||
|
@ -352,6 +352,7 @@ class AllValuesFieldListFilter(FieldListFilter):
|
||||||
self.lookup_kwarg_isnull = '%s__isnull' % field_path
|
self.lookup_kwarg_isnull = '%s__isnull' % field_path
|
||||||
self.lookup_val = request.GET.get(self.lookup_kwarg)
|
self.lookup_val = request.GET.get(self.lookup_kwarg)
|
||||||
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
|
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
|
||||||
|
self.empty_value_display = model_admin.get_empty_value_display()
|
||||||
parent_model, reverse_path = reverse_field_path(model, field_path)
|
parent_model, reverse_path = reverse_field_path(model, field_path)
|
||||||
# Obey parent ModelAdmin queryset when deciding which options to show
|
# Obey parent ModelAdmin queryset when deciding which options to show
|
||||||
if model == parent_model:
|
if model == parent_model:
|
||||||
|
@ -369,7 +370,6 @@ class AllValuesFieldListFilter(FieldListFilter):
|
||||||
return [self.lookup_kwarg, self.lookup_kwarg_isnull]
|
return [self.lookup_kwarg, self.lookup_kwarg_isnull]
|
||||||
|
|
||||||
def choices(self, cl):
|
def choices(self, cl):
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
yield {
|
yield {
|
||||||
'selected': (self.lookup_val is None
|
'selected': (self.lookup_val is None
|
||||||
and self.lookup_val_isnull is None),
|
and self.lookup_val_isnull is None),
|
||||||
|
@ -396,7 +396,7 @@ class AllValuesFieldListFilter(FieldListFilter):
|
||||||
'query_string': cl.get_query_string({
|
'query_string': cl.get_query_string({
|
||||||
self.lookup_kwarg_isnull: 'True',
|
self.lookup_kwarg_isnull: 'True',
|
||||||
}, [self.lookup_kwarg]),
|
}, [self.lookup_kwarg]),
|
||||||
'display': EMPTY_CHANGELIST_VALUE,
|
'display': self.empty_value_display,
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldListFilter.register(lambda f: True, AllValuesFieldListFilter)
|
FieldListFilter.register(lambda f: True, AllValuesFieldListFilter)
|
||||||
|
|
|
@ -174,6 +174,7 @@ class AdminReadonlyField(object):
|
||||||
self.is_first = is_first
|
self.is_first = is_first
|
||||||
self.is_checkbox = False
|
self.is_checkbox = False
|
||||||
self.is_readonly = True
|
self.is_readonly = True
|
||||||
|
self.empty_value_display = model_admin.get_empty_value_display()
|
||||||
|
|
||||||
def label_tag(self):
|
def label_tag(self):
|
||||||
attrs = {}
|
attrs = {}
|
||||||
|
@ -186,12 +187,11 @@ class AdminReadonlyField(object):
|
||||||
|
|
||||||
def contents(self):
|
def contents(self):
|
||||||
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
|
field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
|
||||||
try:
|
try:
|
||||||
f, attr, value = lookup_field(field, obj, model_admin)
|
f, attr, value = lookup_field(field, obj, model_admin)
|
||||||
except (AttributeError, ValueError, ObjectDoesNotExist):
|
except (AttributeError, ValueError, ObjectDoesNotExist):
|
||||||
result_repr = EMPTY_CHANGELIST_VALUE
|
result_repr = self.empty_value_display
|
||||||
else:
|
else:
|
||||||
if f is None:
|
if f is None:
|
||||||
boolean = getattr(attr, "boolean", False)
|
boolean = getattr(attr, "boolean", False)
|
||||||
|
@ -207,7 +207,7 @@ class AdminReadonlyField(object):
|
||||||
if isinstance(f.remote_field, ManyToManyRel) and value is not None:
|
if isinstance(f.remote_field, ManyToManyRel) and value is not None:
|
||||||
result_repr = ", ".join(map(six.text_type, value.all()))
|
result_repr = ", ".join(map(six.text_type, value.all()))
|
||||||
else:
|
else:
|
||||||
result_repr = display_for_field(value, f)
|
result_repr = display_for_field(value, f, self.empty_value_display)
|
||||||
return conditional_escape(result_repr)
|
return conditional_escape(result_repr)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -273,6 +273,15 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
||||||
'object_id': obj.pk
|
'object_id': obj.pk
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_empty_value_display(self):
|
||||||
|
"""
|
||||||
|
Return the empty_value_display set on ModelAdmin or AdminSite.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return mark_safe(self.empty_value_display)
|
||||||
|
except AttributeError:
|
||||||
|
return mark_safe(self.admin_site.empty_value_display)
|
||||||
|
|
||||||
def get_fields(self, request, obj=None):
|
def get_fields(self, request, obj=None):
|
||||||
"""
|
"""
|
||||||
Hook for specifying fields.
|
Hook for specifying fields.
|
||||||
|
|
|
@ -48,6 +48,8 @@ class AdminSite(object):
|
||||||
# URL for the "View site" link at the top of each admin page.
|
# URL for the "View site" link at the top of each admin page.
|
||||||
site_url = '/'
|
site_url = '/'
|
||||||
|
|
||||||
|
_empty_value_display = '-'
|
||||||
|
|
||||||
login_form = None
|
login_form = None
|
||||||
index_template = None
|
index_template = None
|
||||||
app_index_template = None
|
app_index_template = None
|
||||||
|
@ -154,6 +156,14 @@ class AdminSite(object):
|
||||||
"""
|
"""
|
||||||
return six.iteritems(self._actions)
|
return six.iteritems(self._actions)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def empty_value_display(self):
|
||||||
|
return self._empty_value_display
|
||||||
|
|
||||||
|
@empty_value_display.setter
|
||||||
|
def empty_value_display(self, empty_value_display):
|
||||||
|
self._empty_value_display = empty_value_display
|
||||||
|
|
||||||
def has_permission(self, request):
|
def has_permission(self, request):
|
||||||
"""
|
"""
|
||||||
Returns True if the given HttpRequest has permission to view
|
Returns True if the given HttpRequest has permission to view
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.contrib.admin.utils import (
|
||||||
display_for_field, display_for_value, label_for_field, lookup_field,
|
display_for_field, display_for_value, label_for_field, lookup_field,
|
||||||
)
|
)
|
||||||
from django.contrib.admin.views.main import (
|
from django.contrib.admin.views.main import (
|
||||||
ALL_VAR, EMPTY_CHANGELIST_VALUE, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
|
ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
|
||||||
)
|
)
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
|
@ -194,20 +194,22 @@ def items_for_result(cl, result, form):
|
||||||
first = True
|
first = True
|
||||||
pk = cl.lookup_opts.pk.attname
|
pk = cl.lookup_opts.pk.attname
|
||||||
for field_name in cl.list_display:
|
for field_name in cl.list_display:
|
||||||
|
empty_value_display = cl.model_admin.get_empty_value_display()
|
||||||
row_classes = ['field-%s' % field_name]
|
row_classes = ['field-%s' % field_name]
|
||||||
try:
|
try:
|
||||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
result_repr = EMPTY_CHANGELIST_VALUE
|
result_repr = empty_value_display
|
||||||
else:
|
else:
|
||||||
|
empty_value_display = getattr(attr, 'empty_value_display', empty_value_display)
|
||||||
if f is None or f.auto_created:
|
if f is None or f.auto_created:
|
||||||
if field_name == 'action_checkbox':
|
if field_name == 'action_checkbox':
|
||||||
row_classes = ['action-checkbox']
|
row_classes = ['action-checkbox']
|
||||||
allow_tags = getattr(attr, 'allow_tags', False)
|
allow_tags = getattr(attr, 'allow_tags', False)
|
||||||
boolean = getattr(attr, 'boolean', False)
|
boolean = getattr(attr, 'boolean', False)
|
||||||
if boolean:
|
if boolean or not value:
|
||||||
allow_tags = True
|
allow_tags = True
|
||||||
result_repr = display_for_value(value, boolean)
|
result_repr = display_for_value(value, empty_value_display, boolean)
|
||||||
# Strip HTML tags in the resulting text, except if the
|
# Strip HTML tags in the resulting text, except if the
|
||||||
# function has an "allow_tags" attribute set to True.
|
# function has an "allow_tags" attribute set to True.
|
||||||
if allow_tags:
|
if allow_tags:
|
||||||
|
@ -218,11 +220,11 @@ def items_for_result(cl, result, form):
|
||||||
if isinstance(f.remote_field, models.ManyToOneRel):
|
if isinstance(f.remote_field, models.ManyToOneRel):
|
||||||
field_val = getattr(result, f.name)
|
field_val = getattr(result, f.name)
|
||||||
if field_val is None:
|
if field_val is None:
|
||||||
result_repr = EMPTY_CHANGELIST_VALUE
|
result_repr = empty_value_display
|
||||||
else:
|
else:
|
||||||
result_repr = field_val
|
result_repr = field_val
|
||||||
else:
|
else:
|
||||||
result_repr = display_for_field(value, f)
|
result_repr = display_for_field(value, f, empty_value_display)
|
||||||
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
|
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
|
||||||
row_classes.append('nowrap')
|
row_classes.append('nowrap')
|
||||||
if force_text(result_repr) == '':
|
if force_text(result_repr) == '':
|
||||||
|
|
|
@ -378,18 +378,17 @@ def help_text_for_field(name, model):
|
||||||
return smart_text(help_text)
|
return smart_text(help_text)
|
||||||
|
|
||||||
|
|
||||||
def display_for_field(value, field):
|
def display_for_field(value, field, empty_value_display):
|
||||||
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
|
|
||||||
if field.flatchoices:
|
if field.flatchoices:
|
||||||
return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE)
|
return dict(field.flatchoices).get(value, empty_value_display)
|
||||||
# NullBooleanField needs special-case null-handling, so it comes
|
# NullBooleanField needs special-case null-handling, so it comes
|
||||||
# before the general null test.
|
# before the general null test.
|
||||||
elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField):
|
elif isinstance(field, models.BooleanField) or isinstance(field, models.NullBooleanField):
|
||||||
return _boolean_icon(value)
|
return _boolean_icon(value)
|
||||||
elif value is None:
|
elif value is None:
|
||||||
return EMPTY_CHANGELIST_VALUE
|
return empty_value_display
|
||||||
elif isinstance(field, models.DateTimeField):
|
elif isinstance(field, models.DateTimeField):
|
||||||
return formats.localize(timezone.template_localtime(value))
|
return formats.localize(timezone.template_localtime(value))
|
||||||
elif isinstance(field, (models.DateField, models.TimeField)):
|
elif isinstance(field, (models.DateField, models.TimeField)):
|
||||||
|
@ -404,14 +403,13 @@ def display_for_field(value, field):
|
||||||
return smart_text(value)
|
return smart_text(value)
|
||||||
|
|
||||||
|
|
||||||
def display_for_value(value, boolean=False):
|
def display_for_value(value, empty_value_display, boolean=False):
|
||||||
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
|
|
||||||
if boolean:
|
if boolean:
|
||||||
return _boolean_icon(value)
|
return _boolean_icon(value)
|
||||||
elif value is None:
|
elif value is None:
|
||||||
return EMPTY_CHANGELIST_VALUE
|
return empty_value_display
|
||||||
elif isinstance(value, datetime.datetime):
|
elif isinstance(value, datetime.datetime):
|
||||||
return formats.localize(timezone.template_localtime(value))
|
return formats.localize(timezone.template_localtime(value))
|
||||||
elif isinstance(value, (datetime.date, datetime.time)):
|
elif isinstance(value, (datetime.date, datetime.time)):
|
||||||
|
|
|
@ -33,9 +33,6 @@ ERROR_FLAG = 'e'
|
||||||
IGNORED_PARAMS = (
|
IGNORED_PARAMS = (
|
||||||
ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR)
|
ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR)
|
||||||
|
|
||||||
# Text to display within change-list table cells if the value is blank.
|
|
||||||
EMPTY_CHANGELIST_VALUE = '-'
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeList(object):
|
class ChangeList(object):
|
||||||
def __init__(self, request, model, list_display, list_display_links,
|
def __init__(self, request, model, list_display, list_display_links,
|
||||||
|
|
|
@ -205,6 +205,33 @@ subclass::
|
||||||
to its documentation for some caveats when time zone support is
|
to its documentation for some caveats when time zone support is
|
||||||
enabled (:setting:`USE_TZ = True <USE_TZ>`).
|
enabled (:setting:`USE_TZ = True <USE_TZ>`).
|
||||||
|
|
||||||
|
.. attribute:: ModelAdmin.empty_value_display
|
||||||
|
|
||||||
|
.. versionadded:: 1.9
|
||||||
|
|
||||||
|
This attribute overrides the default display value for record's fields that
|
||||||
|
are empty (``None``, empty string, etc.). The default value is ``-`` (a
|
||||||
|
dash). For example::
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
class AuthorAdmin(admin.ModelAdmin):
|
||||||
|
empty_value_display = '-empty-'
|
||||||
|
|
||||||
|
You can also override ``empty_value_display`` for all admin pages with
|
||||||
|
:attr:`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 = '???'
|
||||||
|
|
||||||
.. attribute:: ModelAdmin.exclude
|
.. attribute:: ModelAdmin.exclude
|
||||||
|
|
||||||
This attribute, if given, should be a list of field names to exclude from
|
This attribute, if given, should be a list of field names to exclude from
|
||||||
|
@ -583,6 +610,33 @@ subclass::
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('first_name', 'last_name', 'colored_name')
|
list_display = ('first_name', 'last_name', 'colored_name')
|
||||||
|
|
||||||
|
* If the value of a field is ``None``, an empty string, or an iterable
|
||||||
|
without elements, Django will display ``-`` (a dash). You can override
|
||||||
|
this with :attr:`AdminSite.empty_value_display`::
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
admin.site.empty_value_display = '(None)'
|
||||||
|
|
||||||
|
You can also use :attr:`AdminSite.empty_value_display`::
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
empty_value_display = 'unknown'
|
||||||
|
|
||||||
|
Or on a field level::
|
||||||
|
|
||||||
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'birth_date_view')
|
||||||
|
|
||||||
|
def birth_date_view(self, obj):
|
||||||
|
return obj.birth_date
|
||||||
|
|
||||||
|
birth_date_view.empty_value_display = 'unknown'
|
||||||
|
|
||||||
|
.. versionadded:: 1.9
|
||||||
|
|
||||||
|
The ability to customize ``empty_value_display`` was added.
|
||||||
|
|
||||||
* If the string given is a method of the model, ``ModelAdmin`` or a
|
* If the string given is a method of the model, ``ModelAdmin`` or a
|
||||||
callable that returns True or False Django will display a pretty
|
callable that returns True or False Django will display a pretty
|
||||||
"on" or "off" icon if you give the method a ``boolean`` attribute
|
"on" or "off" icon if you give the method a ``boolean`` attribute
|
||||||
|
@ -604,7 +658,6 @@ subclass::
|
||||||
class PersonAdmin(admin.ModelAdmin):
|
class PersonAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'born_in_fifties')
|
list_display = ('name', 'born_in_fifties')
|
||||||
|
|
||||||
|
|
||||||
* The ``__str__()`` (``__unicode__()`` on Python 2) method is just
|
* The ``__str__()`` (``__unicode__()`` on Python 2) method is just
|
||||||
as valid in ``list_display`` as any other model method, so it's
|
as valid in ``list_display`` as any other model method, so it's
|
||||||
perfectly OK to do this::
|
perfectly OK to do this::
|
||||||
|
@ -2474,6 +2527,16 @@ Templates can override or extend base admin templates as described in
|
||||||
|
|
||||||
Path to a custom template that will be used by the admin site app index view.
|
Path to a custom template that will be used by the admin site app index view.
|
||||||
|
|
||||||
|
.. attribute:: AdminSite.empty_value_display
|
||||||
|
|
||||||
|
.. versionadded:: 1.9
|
||||||
|
|
||||||
|
The string to use for displaying empty values in the admin site's change
|
||||||
|
list. Defaults to a dash. The value can also be overridden on a per
|
||||||
|
``ModelAdmin`` basis and on a custom field within a ``ModelAdmin`` by
|
||||||
|
setting an ``empty_value_display`` attribute on the field. See
|
||||||
|
:attr:`ModelAdmin.empty_value_display` for examples.
|
||||||
|
|
||||||
.. attribute:: AdminSite.login_template
|
.. attribute:: AdminSite.login_template
|
||||||
|
|
||||||
Path to a custom template that will be used by the admin site login view.
|
Path to a custom template that will be used by the admin site login view.
|
||||||
|
|
|
@ -52,6 +52,12 @@ Minor features
|
||||||
applications for the current user, has been added to the
|
applications for the current user, has been added to the
|
||||||
:meth:`AdminSite.each_context() <django.contrib.admin.AdminSite.each_context>`
|
:meth:`AdminSite.each_context() <django.contrib.admin.AdminSite.each_context>`
|
||||||
method.
|
method.
|
||||||
|
* :attr:`AdminSite.empty_value_display
|
||||||
|
<django.contrib.admin.AdminSite.empty_value_display>` and
|
||||||
|
:attr:`ModelAdmin.empty_value_display
|
||||||
|
<django.contrib.admin.ModelAdmin.empty_value_display>` were added to override
|
||||||
|
the display of empty values in admin change list. You can also customize the
|
||||||
|
value for each field.
|
||||||
|
|
||||||
:mod:`django.contrib.auth`
|
:mod:`django.contrib.auth`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -130,3 +130,12 @@ class DynamicSearchFieldsChildAdmin(admin.ModelAdmin):
|
||||||
search_fields = super(DynamicSearchFieldsChildAdmin, self).get_search_fields(request)
|
search_fields = super(DynamicSearchFieldsChildAdmin, self).get_search_fields(request)
|
||||||
search_fields += ('age',)
|
search_fields += ('age',)
|
||||||
return search_fields
|
return search_fields
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyValueChildAdmin(admin.ModelAdmin):
|
||||||
|
empty_value_display = '-empty-'
|
||||||
|
list_display = ('name', 'age_display', 'age')
|
||||||
|
|
||||||
|
def age_display(self, obj):
|
||||||
|
return obj.age
|
||||||
|
age_display.empty_value_display = '†'
|
||||||
|
|
|
@ -20,9 +20,9 @@ from .admin import (
|
||||||
BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin,
|
BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin,
|
||||||
CustomPaginationAdmin, CustomPaginator, DynamicListDisplayChildAdmin,
|
CustomPaginationAdmin, CustomPaginator, DynamicListDisplayChildAdmin,
|
||||||
DynamicListDisplayLinksChildAdmin, DynamicListFilterChildAdmin,
|
DynamicListDisplayLinksChildAdmin, DynamicListFilterChildAdmin,
|
||||||
DynamicSearchFieldsChildAdmin, FilteredChildAdmin, GroupAdmin,
|
DynamicSearchFieldsChildAdmin, EmptyValueChildAdmin, FilteredChildAdmin,
|
||||||
InvitationAdmin, NoListDisplayLinksParentAdmin, ParentAdmin, QuartetAdmin,
|
GroupAdmin, InvitationAdmin, NoListDisplayLinksParentAdmin, ParentAdmin,
|
||||||
SwallowAdmin, site as custom_site,
|
QuartetAdmin, SwallowAdmin, site as custom_site,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Band, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser, Event,
|
Band, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser, Event,
|
||||||
|
@ -109,14 +109,67 @@ class ChangeListTests(TestCase):
|
||||||
list_display = m.get_list_display(request)
|
list_display = m.get_list_display(request)
|
||||||
list_display_links = m.get_list_display_links(request, list_display)
|
list_display_links = m.get_list_display_links(request, list_display)
|
||||||
cl = ChangeList(request, Child, list_display, list_display_links,
|
cl = ChangeList(request, Child, list_display, list_display_links,
|
||||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||||
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
cl.formset = None
|
cl.formset = None
|
||||||
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
||||||
context = Context({'cl': cl})
|
context = Context({'cl': cl})
|
||||||
table_output = template.render(context)
|
table_output = template.render(context)
|
||||||
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
||||||
row_html = '<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th><td class="field-parent nowrap">-</td></tr></tbody>' % link
|
row_html = (
|
||||||
|
'<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th>'
|
||||||
|
'<td class="field-parent nowrap">-</td></tr></tbody>' % link
|
||||||
|
)
|
||||||
|
self.assertNotEqual(table_output.find(row_html), -1,
|
||||||
|
'Failed to find expected row element: %s' % table_output)
|
||||||
|
|
||||||
|
def test_result_list_set_empty_value_display_on_admin_site(self):
|
||||||
|
"""
|
||||||
|
Test that empty value display can be set on AdminSite
|
||||||
|
"""
|
||||||
|
new_child = Child.objects.create(name='name', parent=None)
|
||||||
|
request = self.factory.get('/child/')
|
||||||
|
# Set a new empty display value on AdminSite.
|
||||||
|
admin.site.empty_value_display = '???'
|
||||||
|
m = ChildAdmin(Child, admin.site)
|
||||||
|
list_display = m.get_list_display(request)
|
||||||
|
list_display_links = m.get_list_display_links(request, list_display)
|
||||||
|
cl = ChangeList(request, Child, list_display, list_display_links,
|
||||||
|
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||||
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
|
cl.formset = None
|
||||||
|
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
||||||
|
context = Context({'cl': cl})
|
||||||
|
table_output = template.render(context)
|
||||||
|
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
||||||
|
row_html = (
|
||||||
|
'<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th>'
|
||||||
|
'<td class="field-parent nowrap">???</td></tr></tbody>' % link
|
||||||
|
)
|
||||||
|
self.assertNotEqual(table_output.find(row_html), -1,
|
||||||
|
'Failed to find expected row element: %s' % table_output)
|
||||||
|
|
||||||
|
def test_result_list_set_empty_value_display_in_model_admin(self):
|
||||||
|
"""
|
||||||
|
Test that empty value display can be set in ModelAdmin or individual fields.
|
||||||
|
"""
|
||||||
|
new_child = Child.objects.create(name='name', parent=None)
|
||||||
|
request = self.factory.get('/child/')
|
||||||
|
m = EmptyValueChildAdmin(Child, admin.site)
|
||||||
|
list_display = m.get_list_display(request)
|
||||||
|
list_display_links = m.get_list_display_links(request, list_display)
|
||||||
|
cl = ChangeList(request, Child, list_display, list_display_links,
|
||||||
|
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||||
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
|
cl.formset = None
|
||||||
|
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
||||||
|
context = Context({'cl': cl})
|
||||||
|
table_output = template.render(context)
|
||||||
|
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
||||||
|
row_html = (
|
||||||
|
'<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th>'
|
||||||
|
'<td class="field-age_display">†</td><td class="field-age">-empty-</td></tr></tbody>' % link
|
||||||
|
)
|
||||||
self.assertNotEqual(table_output.find(row_html), -1,
|
self.assertNotEqual(table_output.find(row_html), -1,
|
||||||
'Failed to find expected row element: %s' % table_output)
|
'Failed to find expected row element: %s' % table_output)
|
||||||
|
|
||||||
|
@ -132,14 +185,17 @@ class ChangeListTests(TestCase):
|
||||||
list_display = m.get_list_display(request)
|
list_display = m.get_list_display(request)
|
||||||
list_display_links = m.get_list_display_links(request, list_display)
|
list_display_links = m.get_list_display_links(request, list_display)
|
||||||
cl = ChangeList(request, Child, list_display, list_display_links,
|
cl = ChangeList(request, Child, list_display, list_display_links,
|
||||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||||
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
cl.formset = None
|
cl.formset = None
|
||||||
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
|
||||||
context = Context({'cl': cl})
|
context = Context({'cl': cl})
|
||||||
table_output = template.render(context)
|
table_output = template.render(context)
|
||||||
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
|
||||||
row_html = '<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th><td class="field-parent nowrap">Parent object</td></tr></tbody>' % link
|
row_html = (
|
||||||
|
'<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th>'
|
||||||
|
'<td class="field-parent nowrap">Parent object</td></tr></tbody>' % link
|
||||||
|
)
|
||||||
self.assertNotEqual(table_output.find(row_html), -1,
|
self.assertNotEqual(table_output.find(row_html), -1,
|
||||||
'Failed to find expected row element: %s' % table_output)
|
'Failed to find expected row element: %s' % table_output)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ from django.contrib.admin.utils import (
|
||||||
NestedObjects, display_for_field, flatten, flatten_fieldsets,
|
NestedObjects, display_for_field, flatten, flatten_fieldsets,
|
||||||
label_for_field, lookup_field,
|
label_for_field, lookup_field,
|
||||||
)
|
)
|
||||||
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
||||||
from django.db import DEFAULT_DB_ALIAS, models
|
from django.db import DEFAULT_DB_ALIAS, models
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -96,6 +95,9 @@ class NestedObjectsTests(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class UtilsTests(TestCase):
|
class UtilsTests(TestCase):
|
||||||
|
|
||||||
|
empty_value = '-empty-'
|
||||||
|
|
||||||
def test_values_from_lookup_field(self):
|
def test_values_from_lookup_field(self):
|
||||||
"""
|
"""
|
||||||
Regression test for #12654: lookup_field
|
Regression test for #12654: lookup_field
|
||||||
|
@ -136,7 +138,7 @@ class UtilsTests(TestCase):
|
||||||
field, attr, resolved_value = lookup_field(name, article, mock_admin)
|
field, attr, resolved_value = lookup_field(name, article, mock_admin)
|
||||||
|
|
||||||
if field is not None:
|
if field is not None:
|
||||||
resolved_value = display_for_field(resolved_value, field)
|
resolved_value = display_for_field(resolved_value, field, self.empty_value)
|
||||||
|
|
||||||
self.assertEqual(value, resolved_value)
|
self.assertEqual(value, resolved_value)
|
||||||
|
|
||||||
|
@ -145,53 +147,53 @@ class UtilsTests(TestCase):
|
||||||
Regression test for #12550: display_for_field should handle None
|
Regression test for #12550: display_for_field should handle None
|
||||||
value.
|
value.
|
||||||
"""
|
"""
|
||||||
display_value = display_for_field(None, models.CharField())
|
display_value = display_for_field(None, models.CharField(), self.empty_value)
|
||||||
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
self.assertEqual(display_value, self.empty_value)
|
||||||
|
|
||||||
display_value = display_for_field(None, models.CharField(
|
display_value = display_for_field(None, models.CharField(
|
||||||
choices=(
|
choices=(
|
||||||
(None, "test_none"),
|
(None, "test_none"),
|
||||||
)
|
)
|
||||||
))
|
), self.empty_value)
|
||||||
self.assertEqual(display_value, "test_none")
|
self.assertEqual(display_value, "test_none")
|
||||||
|
|
||||||
display_value = display_for_field(None, models.DateField())
|
display_value = display_for_field(None, models.DateField(), self.empty_value)
|
||||||
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
self.assertEqual(display_value, self.empty_value)
|
||||||
|
|
||||||
display_value = display_for_field(None, models.TimeField())
|
display_value = display_for_field(None, models.TimeField(), self.empty_value)
|
||||||
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
self.assertEqual(display_value, self.empty_value)
|
||||||
|
|
||||||
# Regression test for #13071: NullBooleanField has special
|
# Regression test for #13071: NullBooleanField has special
|
||||||
# handling.
|
# handling.
|
||||||
display_value = display_for_field(None, models.NullBooleanField())
|
display_value = display_for_field(None, models.NullBooleanField(), self.empty_value)
|
||||||
expected = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL
|
expected = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL
|
||||||
self.assertHTMLEqual(display_value, expected)
|
self.assertHTMLEqual(display_value, expected)
|
||||||
|
|
||||||
display_value = display_for_field(None, models.DecimalField())
|
display_value = display_for_field(None, models.DecimalField(), self.empty_value)
|
||||||
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
self.assertEqual(display_value, self.empty_value)
|
||||||
|
|
||||||
display_value = display_for_field(None, models.FloatField())
|
display_value = display_for_field(None, models.FloatField(), self.empty_value)
|
||||||
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
self.assertEqual(display_value, self.empty_value)
|
||||||
|
|
||||||
def test_number_formats_display_for_field(self):
|
def test_number_formats_display_for_field(self):
|
||||||
display_value = display_for_field(12345.6789, models.FloatField())
|
display_value = display_for_field(12345.6789, models.FloatField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12345.6789')
|
self.assertEqual(display_value, '12345.6789')
|
||||||
|
|
||||||
display_value = display_for_field(Decimal('12345.6789'), models.DecimalField())
|
display_value = display_for_field(Decimal('12345.6789'), models.DecimalField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12345.6789')
|
self.assertEqual(display_value, '12345.6789')
|
||||||
|
|
||||||
display_value = display_for_field(12345, models.IntegerField())
|
display_value = display_for_field(12345, models.IntegerField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12345')
|
self.assertEqual(display_value, '12345')
|
||||||
|
|
||||||
@override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True)
|
@override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True)
|
||||||
def test_number_formats_with_thousand_seperator_display_for_field(self):
|
def test_number_formats_with_thousand_seperator_display_for_field(self):
|
||||||
display_value = display_for_field(12345.6789, models.FloatField())
|
display_value = display_for_field(12345.6789, models.FloatField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12,345.6789')
|
self.assertEqual(display_value, '12,345.6789')
|
||||||
|
|
||||||
display_value = display_for_field(Decimal('12345.6789'), models.DecimalField())
|
display_value = display_for_field(Decimal('12345.6789'), models.DecimalField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12,345.6789')
|
self.assertEqual(display_value, '12,345.6789')
|
||||||
|
|
||||||
display_value = display_for_field(12345, models.IntegerField())
|
display_value = display_for_field(12345, models.IntegerField(), self.empty_value)
|
||||||
self.assertEqual(display_value, '12,345')
|
self.assertEqual(display_value, '12,345')
|
||||||
|
|
||||||
def test_label_for_field(self):
|
def test_label_for_field(self):
|
||||||
|
|
Loading…
Reference in New Issue