Respostas:
Para conseguir isso e ter um link 'Todos' utilizável em sua barra lateral (ou seja, um que mostra tudo em vez de mostrar pendentes), você precisa criar um filtro de lista personalizado, herdando de django.contrib.admin.filters.SimpleListFilter
e filtrando por 'pendente' por padrão. Algo nesse sentido deve 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: Requer Django 1.4 (obrigado Simon)
choices
método na solução, ele continuará irritantemente a adicionar sua própria opção Todos no topo da lista de opções.
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)
Retirou a resposta de ha22109 acima e modificou para permitir a seleção de "Todos" comparando HTTP_REFERER
e 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)
KeyError
, enquanto se você usar o get()
método do dict, você pode especificar um padrão. Especifiquei um padrão de string vazia para que split () não lance AttributeError
. Isso é tudo.
has_key()
é preterido em favor de key in d
, no entanto. Mas eu sei que você acabou de tirar a resposta de ha22109. Uma pergunta: por que usar request.META['PATH_INFO']
quando você poderia apenas usar request.path_info
(mais curto)?
Eu sei que esta questão é bastante antiga agora, mas ainda é válida. Acredito que essa seja a maneira mais correta de fazer isso. É essencialmente o mesmo que o método de Greg, mas formulado como uma classe extensível para fácil reutilização.
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,)
Aqui está minha solução genérica usando redirecionamento, ele apenas verifica se há algum parâmetro GET; se nenhum existir, ele redireciona com o parâmetro get padrão. Eu também tenho um list_filter definido para que ele pegue e exiba o padrão.
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)
A única ressalva é quando você acessa diretamente a página com "?" presente na url, não há HTTP_REFERER definido, então ele usará o parâmetro padrão e o redirecionará. Isso é bom para mim, funciona muito bem quando você clica no filtro de administrador.
ATUALIZAÇÃO :
Para contornar a advertência, acabei escrevendo uma função de filtro personalizado que simplificou a funcionalidade changelist_view. Aqui está o 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)
E o changelist_view agora só passa o parâmetro padrão se nenhum estiver presente. A ideia era livrar-se da capacidade dos filtros genéricos de visualizar tudo sem usar parâmetros get. Para ver todos atribuímos o status = 8 para esse 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)
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 )
Você pode simplesmente usar return queryset.filter()
ou if self.value() is None
e o método Override de SimpleListFilter
from django.utils.encoding import force_text
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,
}
Observe que, se em vez de pré-selecionar um valor de filtro, você quiser sempre pré-filtrar os dados antes de mostrá-los no admin, você deve substituir o ModelAdmin.queryset()
método.
Uma pequena melhoria na resposta de Greg usando DjangoChoices, Python> = 2.5 e claro 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]
Obrigado a Greg pela boa solução!
Eu sei que não é a melhor solução, mas mudei o index.html no modelo de admin, linha 25 e 37 assim:
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>
Tive que fazer uma modificação para que a filtragem funcionasse corretamente. A solução anterior funcionou para mim quando a página carregou. Se uma 'ação' foi realizada, o filtro voltou para 'Todos' e não para o meu padrão. Esta solução carrega a página de alteração do administrador com o filtro padrão, mas também mantém as alterações do filtro ou o filtro atual quando outra atividade ocorre na página. Não testei todos os casos, mas na realidade isso pode estar limitando a configuração de um filtro padrão para ocorrer apenas quando a página carrega.
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)
Um pouco fora do assunto, mas minha busca por uma questão semelhante me trouxe até aqui. Eu queria ter uma consulta padrão por uma data (ou seja, se nenhuma entrada for fornecida, mostre apenas objetos com timestamp
de 'Hoje'), o que complica um pouco a questão. Aqui está o que eu descobri:
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),
)
Esta é uma substituição simples do padrão DateFieldListFilter
. Ao definir self.date_params
, ele garante que o menu suspenso do filtro será atualizado para qualquer opção que corresponda a self.used_parameters
. Por esse motivo, você deve garantir que self.used_parameters
são exatamente o que seria usado por uma dessas seleções suspensas (ou seja, descobrir o que date_params
seria ao usar 'Hoje' ou 'Últimos 7 dias' e construir oself.used_parameters
para corresponder a eles).
Isto foi construído para funcionar com Django 1.4.10
Este pode ser um tópico antigo, mas pensei em adicionar minha solução, pois não consegui encontrar respostas melhores nas pesquisas do Google.
Faça o que (não tenho certeza se é Deminic Rodger ou ha22109) respondeu no 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)
Em seguida, precisamos criar um 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
Esta é a versão mais limpa que consegui gerar de um filtro com 'Todos' redefinido e um valor padrão selecionado.
Se me mostra por padrão as viagens que estão acontecendo no momento.
class HappeningTripFilter(admin.SimpleListFilter):
"""
Filter the Trips Happening in the Past, Future or now.
"""
default_value = 'now'
title = 'Happening'
parameter_name = 'happening'
def lookups(self, request, model_admin):
"""
List the Choices available for this filter.
"""
return (
('all', 'All'),
('future', 'Not yet started'),
('now', 'Happening now'),
('past', 'Already finished'),
)
def choices(self, changelist):
"""
Overwrite this method to prevent the default "All".
"""
value = self.value() or self.default_value
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_text(lookup),
'query_string': changelist.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
"""
Returns the Queryset depending on the Choice.
"""
value = self.value() or self.default_value
now = timezone.now()
if value == 'future':
return queryset.filter(start_date_time__gt=now)
if value == 'now':
return queryset.filter(start_date_time__lte=now, end_date_time__gte=now)
if value == 'past':
return queryset.filter(end_date_time__lt=now)
return queryset.all()
Criou uma subclasse de Filtro reutilizável, inspirada por algumas das respostas aqui (principalmente de Greg).
Reutilizável - conectável em qualquer ModelAdmin
classe padrão
Extensível - Fácil de adicionar lógica adicional / personalizada paraQuerySet
filtragem
Fácil de usar - Em sua forma mais básica, apenas um atributo personalizado e um método personalizado precisam ser implementados (além daqueles exigidos para a subclasse SimpleListFilter)
Admin intuitivo - O link de filtro "Todos" está funcionando conforme o esperado; como são todos os outros
Sem redirecionamentos - não há necessidade de inspecionar a GET
carga útil da solicitação, independente de HTTP_REFERER
(ou qualquer outro material relacionado à solicitação, em sua forma básica)
Não (changelist) manipulação de visualização - E nenhuma manipulação de template (deus me livre)
(a maioria dos import
s são apenas para dicas de tipo e exceções)
from typing import List, Tuple, Any
from django.contrib.admin.filters import SimpleListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
class PreFilteredListFilter(SimpleListFilter):
# Either set this or override .get_default_value()
default_value = None
no_filter_value = 'all'
no_filter_name = _("All")
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = None
# Parameter for the filter that will be used in the URL query.
parameter_name = None
def get_default_value(self):
if self.default_value is not None:
return self.default_value
raise NotImplementedError(
'Either the .default_value attribute needs to be set or '
'the .get_default_value() method must be overridden to '
'return a URL query argument for parameter_name.'
)
def get_lookups(self) -> List[Tuple[Any, str]]:
"""
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.
"""
raise NotImplementedError(
'The .get_lookups() method must be overridden to '
'return a list of tuples (value, verbose value).'
)
# Overriding parent class:
def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()
# Overriding parent class:
def queryset(self, request, queryset: QuerySet) -> QuerySet:
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if self.value() is None:
return self.get_default_queryset(queryset)
if self.value() == self.no_filter_value:
return queryset.all()
return self.get_filtered_queryset(queryset)
def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
return queryset.filter(**{self.parameter_name: self.get_default_value()})
def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
try:
return queryset.filter(**self.used_parameters)
except (ValueError, ValidationError) as e:
# Fields may raise a ValueError or ValidationError when converting
# the parameters to the correct type.
raise IncorrectLookupParameters(e)
# Overriding parent class:
def choices(self, changelist: ChangeList):
"""
Overridden to prevent the default "All".
"""
value = self.value() or force_str(self.get_default_value())
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_str(lookup),
'query_string': changelist.get_query_string({self.parameter_name: lookup}),
'display': title,
}
from django.contrib import admin
from .models import SomeModelWithStatus
class StatusFilter(PreFilteredListFilter):
default_value = SomeModelWithStatus.Status.FOO
title = _('Status')
parameter_name = 'status'
def get_lookups(self):
return SomeModelWithStatus.Status.choices
@admin.register(SomeModelWithStatus)
class SomeModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter, )
Espero que isso ajude alguém; feedback sempre apreciado.