2010-02-12 10 views
7

Quiero permitir que los administradores de mi sitio filtren usuarios de un país específico en el sitio de administración. Así que lo más natural hacerlo sería algo como esto:Django-Admin: atributo list_filter de UserProfile

#admin.py 
class UserAdmin(django.contrib.auth.admin.UserAdmin): 
    list_filter=('userprofile__country__name',) 

#models.py 
class UserProfile(models.Model) 
    ... 
    country=models.ForeignKey('Country') 

class Country(models.Model) 
    ... 
    name=models.CharField(max_length=32) 

Pero, debido a la forma en que los usuarios y sus UserProfiles se manejan en Django esto lleva al siguiente error:

'UserAdmin.list_filter[0]' refers to field 'userprofile__country__name' that is missing from model 'User' 

¿Cómo superar esta limitación?

Respuesta

10

Lo que usted está buscando es administración personalizada FilterSpecs. La mala noticia es que es probable que el soporte para aquellos no se envíe pronto (puede seguir la discusión here).

Sin embargo, al precio de un truco sucio, puede evitar la limitación. Algunos puntos importantes sobre cómo FilterSpecs se construyen antes de sumergirse en el código:

  • Cuando la construcción de la lista de FilterSpec para mostrar en la página, Django usa la lista de campos que ya ha proporcionado en list_filter
  • Esos campos tiene que ser campos reales en el modelo, relación no inversa, ni propiedades personalizadas.
  • Django mantiene una lista de FilterSpec clases, cada una asociada con una función de prueba .
  • Para cada uno de los campos en list_filter, Django usará la primera clase FilterSpec para el cual el función prueba devuelve verdadera para el campo.

Ok, ahora con esto en mente, eche un vistazo al siguiente código. Está adaptado de a django snippet. La organización del código se deja a su discreción, solo tenga en cuenta que esto debe ser importado por la aplicación admin.

from myapp.models import UserProfile, Country 
from django.contrib.auth.models import User 
from django.contrib.auth.admin import UserAdmin 

from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec 
from django.utils.encoding import smart_unicode 
from django.utils.translation import ugettext_lazy as _ 

class ProfileCountryFilterSpec(ChoicesFilterSpec): 
    def __init__(self, f, request, params, model, model_admin): 
     ChoicesFilterSpec.__init__(self, f, request, params, model, model_admin) 

     # The lookup string that will be added to the queryset 
     # by this filter 
     self.lookup_kwarg = 'userprofile__country__name' 
     # get the current filter value from GET (we will use it to know 
     # which filter item is selected) 
     self.lookup_val = request.GET.get(self.lookup_kwarg) 

     # Prepare the list of unique, country name, ordered alphabetically 
     country_qs = Country.objects.distinct().order_by('name') 
     self.lookup_choices = country_qs.values_list('name', flat=True) 

    def choices(self, cl): 
     # Generator that returns all the possible item in the filter 
     # including an 'All' item. 
     yield { 'selected': self.lookup_val is None, 
       'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 
       'display': _('All') } 
     for val in self.lookup_choices: 
      yield { 'selected' : smart_unicode(val) == self.lookup_val, 
        'query_string': cl.get_query_string({self.lookup_kwarg: val}), 
        'display': val } 

    def title(self): 
     # return the title displayed above your filter 
     return _('user\'s country') 

# Here, we insert the new FilterSpec at the first position, to be sure 
# it gets picked up before any other 
FilterSpec.filter_specs.insert(0, 
    # If the field has a `profilecountry_filter` attribute set to True 
    # the this FilterSpec will be used 
    (lambda f: getattr(f, 'profilecountry_filter', False), ProfileCountryFilterSpec) 
) 


# Now, how to use this filter in UserAdmin, 
# We have to use one of the field of User model and 
# add a profilecountry_filter attribute to it. 
# This field will then activate the country filter if we 
# place it in `list_filter`, but we won't be able to use 
# it in its own filter anymore. 

User._meta.get_field('email').profilecountry_filter = True 

class MyUserAdmin(UserAdmin): 
    list_filter = ('email',) + UserAdmin.list_filter 

# register the new UserAdmin 
from django.contrib.admin import site 
site.unregister(User) 
site.register(User, MyUserAdmin) 

claramente no lo es la panacea pero va a hacer el trabajo, a la espera de una mejor solución para llegar. (Por ejemplo, uno que subclase ChangeList y anular get_filters).

+1

Gracias, estoy atascado con Django 1.2 en un proyecto y esto fue realmente útil. Dos notas: 1) El bit "userprofile__" de arriba se deriva del nombre del modelo de la clase de perfil de usuario. Mi clase de perfil se llama Perfil, por lo que en mi caso quiero "profile__country__name", me llevó un minuto darme cuenta. 2) Para el caso general, también debe sobrescribir el método 'lookup_allowed' de ModelAdmin para evitar excepciones de Operación Sospechosa al hacer búsquedas en la relación. http://www.hoboes.com/Mimsy/hacks/fixing-django-124s-suspiciousoperation-filtering/ ayudado aquí. – ejucovy

Cuestiones relacionadas