python - template - personalizar admin django
Filtro predeterminado en el administrador de Django (13)
¿Cómo puedo cambiar la opción de filtro predeterminada de ''ALL''? Tengo un campo llamado status
que tiene tres valores: activate
, pending
y rejected
. Cuando uso list_filter
en Django admin, el filtro está configurado por defecto en ''Todos'', pero quiero establecerlo en pendiente de forma predeterminada.
Esta es mi solución genérica usando redirección, solo comprueba si hay algún parámetro GET, si no existe ninguno, redirige con el parámetro get predeterminado. También tengo un list_filter establecido para que lo levante y muestre el predeterminado.
from django.shortcuts import redirect
class MyModelAdmin(admin.ModelAdmin):
...
list_filter = (''status'', )
def changelist_view(self, request, extra_context=None):
referrer = request.META.get(''HTTP_REFERER'', '''')
get_param = "status__exact=5"
if len(request.GET) == 0 and ''?'' not in referrer:
return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
La única advertencia es cuando haces un acceso directo a la página con "?" presente en la url, no hay HTTP_REFERER configurado por lo que utilizará el parámetro predeterminado y la redirección. Esto está bien para mí, funciona muy bien cuando haces clic en el filtro de administración.
ACTUALIZAR :
Para evitar la advertencia, terminé escribiendo una función de filtro personalizada que simplificó la funcionalidad changelist_view. Aquí está el filtro:
class MyModelStatusFilter(admin.SimpleListFilter):
title = _(''Status'')
parameter_name = ''status''
def lookups(self, request, model_admin): # Available Values / Status Codes etc..
return (
(8, _(''All'')),
(0, _(''Incomplete'')),
(5, _(''Pending'')),
(6, _(''Selected'')),
(7, _(''Accepted'')),
)
def choices(self, cl): # Overwrite this method to prevent the default "All"
from django.utils.encoding import force_text
for lookup, title in self.lookup_choices:
yield {
''selected'': self.value() == force_text(lookup),
''query_string'': cl.get_query_string({
self.parameter_name: lookup,
}, []),
''display'': title,
}
def queryset(self, request, queryset): # Run the queryset based on your lookup values
if self.value() is None:
return queryset.filter(status=5)
elif int(self.value()) == 0:
return queryset.filter(status__lte=4)
elif int(self.value()) == 8:
return queryset.all()
elif int(self.value()) >= 5:
return queryset.filter(status=self.value())
return queryset.filter(status=5)
Y el changelist_view ahora solo pasa el parámetro predeterminado si no hay ninguno presente. La idea era deshacerse de la capacidad de filtros genéricos para ver todo sin usar parámetros de obtención. Para ver todo, le asigné el estado = 8 para ese propósito .:
class MyModelAdmin(admin.ModelAdmin):
...
list_filter = (''status'', )
def changelist_view(self, request, extra_context=None):
if len(request.GET) == 0:
get_param = "status=5"
return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
return super(MyModelAdmin, self).changelist_view(request, extra_context=extra_context)
Esto puede ser un hilo viejo, pero pensé que agregaría mi solución ya que no pude encontrar mejores respuestas en las búsquedas de Google.
Haga lo que (no está seguro si su Deminic Rodger, o ha22109) respondió en el ModelAdmin para changelist_view
class MyModelAdmin(admin.ModelAdmin):
list_filter = (CustomFilter,)
def changelist_view(self, request, extra_context=None):
if not request.GET.has_key(''decommissioned__exact''):
q = request.GET.copy()
q[''decommissioned__exact''] = ''N''
request.GET = q
request.META[''QUERY_STRING''] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
Entonces tenemos que crear un SimpleListFilter personalizado
class CustomFilter(admin.SimpleListFilter):
title = ''Decommissioned''
parameter_name = ''decommissioned'' # i chose to change it
def lookups(self, request, model_admin):
return (
(''All'', ''all''),
(''1'', ''Decommissioned''),
(''0'', ''Active (or whatever)''),
)
# had to override so that we could remove the default ''All'' option
# that won''t work with our default filter in the ModelAdmin class
def choices(self, cl):
yield {
''selected'': self.value() is None,
''query_string'': cl.get_query_string({}, [self.parameter_name]),
# ''display'': _(''All''),
}
for lookup, title in self.lookup_choices:
yield {
''selected'': self.value() == lookup,
''query_string'': cl.get_query_string({
self.parameter_name: lookup,
}, []),
''display'': title,
}
def queryset(self, request, queryset):
if self.value() == ''1'':
return queryset.filter(decommissioned=1)
elif self.value() == ''0'':
return queryset.filter(decommissioned=0)
return queryset
Para lograr esto y tener un enlace ''Todos'' utilizable en su barra lateral (es decir, uno que muestre todo en lugar de mostrar pendiente), necesitaría crear un filtro de lista personalizado, heredando de django.contrib.admin.filters.SimpleListFilter
y filtrando en ''pendiente'' por defecto. Algo en esta línea debería funcionar:
from datetime import date
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class StatusFilter(SimpleListFilter):
title = _(''Status'')
parameter_name = ''status''
def lookups(self, request, model_admin):
return (
(None, _(''Pending'')),
(''activate'', _(''Activate'')),
(''rejected'', _(''Rejected'')),
(''all'', _(''All'')),
)
def choices(self, cl):
for lookup, title in self.lookup_choices:
yield {
''selected'': self.value() == lookup,
''query_string'': cl.get_query_string({
self.parameter_name: lookup,
}, []),
''display'': title,
}
def queryset(self, request, queryset):
if self.value() in (''activate'', ''rejected''):
return queryset.filter(status=self.value())
elif self.value() == None:
return queryset.filter(status=''pending'')
class Admin(admin.ModelAdmin):
list_filter = [StatusFilter]
EDITAR: Requiere Django 1.4 (gracias Simon)
Sé que esta pregunta ya es bastante antigua, pero sigue siendo válida. Creo que esta es la manera más correcta de hacer esto. Es esencialmente lo mismo que el método de Greg, pero formulado como una clase extensible para su fácil reutilización.
from django.contrib.admin import SimpleListFilter
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _
class DefaultListFilter(SimpleListFilter):
all_value = ''_all''
def default_value(self):
raise NotImplementedError()
def queryset(self, request, queryset):
if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value:
return queryset
if self.parameter_name in request.GET:
return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]})
return queryset.filter(**{self.parameter_name:self.default_value()})
def choices(self, cl):
yield {
''selected'': self.value() == self.all_value,
''query_string'': cl.get_query_string({self.parameter_name: self.all_value}, []),
''display'': _(''All''),
}
for lookup, title in self.lookup_choices:
yield {
''selected'': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)),
''query_string'': cl.get_query_string({
self.parameter_name: lookup,
}, []),
''display'': title,
}
class StatusFilter(DefaultListFilter):
title = _(''Status '')
parameter_name = ''status__exact''
def lookups(self, request, model_admin):
return ((0,''activate''), (1,''pending''), (2,''rejected''))
def default_value(self):
return 1
class MyModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter,)
Sé que no es la mejor solución, pero cambié el index.html en la plantilla de administrador, línea 25 y 37 de esta manera:
25: <th scope="row"><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag_flag__exact=1{% endifequal %}">{{ model.name }}</a></th>
37: <td><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag__exact=1{% endifequal %}" class="changelink">{% trans ''Change'' %}</a></td>
Simplemente puede usar return queryset.filter()
o if self.value() is None
y método Override de SimpleListFilter
def choices(self, changelist):
for lookup, title in self.lookup_choices:
yield {
''selected'': force_text(self.value()) == force_text(lookup),
''query_string'': changelist.get_query_string(
{self.parameter_name: lookup}, []
),
''display'': title,
}
Tenga en cuenta que si en lugar de preseleccionar un valor de filtro desea prefiltrar siempre los datos antes de mostrarlos en el administrador, debe reemplazar el método ModelAdmin.queryset()
.
Tomó la respuesta de ha22109 arriba y se modificó para permitir la selección de "Todos" al comparar HTTP_REFERER
y PATH_INFO
.
class MyModelAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
test = request.META[''HTTP_REFERER''].split(request.META[''PATH_INFO''])
if test[-1] and not test[-1].startswith(''?''):
if not request.GET.has_key(''decommissioned__exact''):
q = request.GET.copy()
q[''decommissioned__exact''] = ''N''
request.GET = q
request.META[''QUERY_STRING''] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
Tuve que hacer una modificación para que el filtrado funcione correctamente. La solución anterior funcionó para mí cuando se cargó la página. Si se realizó una ''acción'', el filtro volvió a ''Todos'' y no a mi predeterminado. Esta solución carga la página de cambio de administrador con el filtro predeterminado, pero también mantiene los cambios de filtro o el filtro actual cuando ocurre otra actividad en la página. No he probado todos los casos, pero en realidad puede estar limitando la configuración de un filtro predeterminado para que ocurra solo cuando se carga la página.
def changelist_view(self, request, extra_context=None):
default_filter = False
try:
ref = request.META[''HTTP_REFERER'']
pinfo = request.META[''PATH_INFO'']
qstr = ref.split(pinfo)
querystr = request.META[''QUERY_STRING'']
# Check the QUERY_STRING value, otherwise when
# trying to filter the filter gets reset below
if querystr is None:
if len(qstr) < 2 or qstr[1] == '''':
default_filter = True
except:
default_filter = True
if default_filter:
q = request.GET.copy()
q[''registered__isnull''] = ''True''
request.GET = q
request.META[''QUERY_STRING''] = request.GET.urlencode()
return super(MyAdmin, self).changelist_view(request, extra_context=extra_context)
Un poco fuera de tema pero mi búsqueda de una pregunta similar me llevó aquí. Estaba buscando tener una consulta predeterminada por fecha (es decir, si no se proporciona ninguna entrada, mostrar solo los objetos con la timestamp
de timestamp
''Hoy''), lo que complica un poco la pregunta. Aquí es lo que se me ocurrió:
from django.contrib.admin.options import IncorrectLookupParameters
from django.core.exceptions import ValidationError
class TodayDefaultDateFieldListFilter(admin.DateFieldListFilter):
""" If no date is query params are provided, query for Today """
def queryset(self, request, queryset):
try:
if not self.used_parameters:
now = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
self.used_parameters = {
(''%s__lt'' % self.field_path): str(now + datetime.timedelta(days=1)),
(''%s__gte'' % self.field_path): str(now),
}
# Insure that the dropdown reflects ''Today''
self.date_params = self.used_parameters
return queryset.filter(**self.used_parameters)
except ValidationError, e:
raise IncorrectLookupParameters(e)
class ImagesAdmin(admin.ModelAdmin):
list_filter = (
(''timestamp'', TodayDefaultDateFieldListFilter),
)
Esto es una anulación simple del DateFieldListFilter
predeterminado. Al establecer self.date_params
, asegura que el menú desplegable del filtro se actualizará a la opción que coincida con los self.used_parameters
. Por esta razón, debe asegurarse de que los self.used_parameters
exactamente lo que usaría una de esas selecciones desplegables (es decir, descubra cuál sería la date_params
cuando use el ''Hoy'' o ''Últimos 7 días'' y construya el self.used_parameters
para que coincida con ellos).
Esto fue creado para trabajar con Django 1.4.10
Una ligera mejora en la respuesta de Greg usando DjangoChoices, Python> = 2.5 y, por supuesto, Django> = 1.4.
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class OrderStatusFilter(SimpleListFilter):
title = _(''Status'')
parameter_name = ''status__exact''
default_status = OrderStatuses.closed
def lookups(self, request, model_admin):
return ((''all'', _(''All'')),) + OrderStatuses.choices
def choices(self, cl):
for lookup, title in self.lookup_choices:
yield {
''selected'': self.value() == lookup if self.value() else lookup == self.default_status,
''query_string'': cl.get_query_string({self.parameter_name: lookup}, []),
''display'': title,
}
def queryset(self, request, queryset):
if self.value() in OrderStatuses.values:
return queryset.filter(status=self.value())
elif self.value() is None:
return queryset.filter(status=self.default_status)
class Admin(admin.ModelAdmin):
list_filter = [OrderStatusFilter]
¡Gracias a Greg por la buena solución!
class MyModelAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
if not request.GET.has_key(''decommissioned__exact''):
q = request.GET.copy()
q[''decommissioned__exact''] = ''N''
request.GET = q
request.META[''QUERY_STRING''] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
def changelist_view( self, request, extra_context = None ):
default_filter = False
try:
ref = request.META[''HTTP_REFERER'']
pinfo = request.META[''PATH_INFO'']
qstr = ref.split( pinfo )
if len( qstr ) < 2:
default_filter = True
except:
default_filter = True
if default_filter:
q = request.GET.copy()
q[''registered__exact''] = ''1''
request.GET = q
request.META[''QUERY_STRING''] = request.GET.urlencode()
return super( InterestAdmin, self ).changelist_view( request, extra_context = extra_context )