Filtrando widget foreignkey Django Admin

Un perro que mira fijamente. Efectivamente, no tiene nada que ver la imagen con el contenido del post. :D

Hace un tiempo en un poryecto nos pidieron ordenar por el nombre de la foreignkey sindicada a otra foreign key al mostrarla en el widget select del form de Django. La solución fué realizar para ello una función en el admin.py. Voy a poner un caso de ejemplo para entender como filtrar y ordenar el contenido de un select list dentro del django admin.

Imaginad que tenemos un proyecto en el que tenemos usuarios, que tienen asignado un cargo. Por defecto, en el Django Admin esto generará un select list en el formulario del usuario con todos los cargos de la BBDD. Esto es poco útil cuando son muy extensos, por ejemplo. En este post voy a explicar dos cosas:

* Cómo modificar el orden de un select list para la foreign key.
* Como mostrarlo directamente filtrado.

Tenemos dos modelos.
Por un lado, el usuario:

class Consumer(models.Model):
activoChoices = (
('S', 'Si'),
('N', 'No')
)
codigo = models.IntegerField(
primary_key=True, null=False, blank=False, editable=False)
nombre = models.CharField(max_length=500, null=False, blank=True)
apellido = models.CharField(max_length=500, null=False, blank=True)
direccion = models.CharField(max_length=500, null=True, blank=True)
poblacion = models.CharField(max_length=500, null=True, blank=True)
provincia = models.IntegerField(null=True, blank=True, editable=True)
nif = models.CharField(max_length=500, null=True, blank=True)
activo = models.CharField(
max_length=300,
null=False,
blank=False,
choices=activoChoices,
default='S'
)
cargo = models.ForeignKey(
Cargo, related_name='consumer_cargo', null=True, blank=True)
    def __unicode__(self):
return self.nombre
    def save(self, *args, **kwargs):
if not self.codigo:
no = Consumer.objects.count()
if no == 0:
self.codigo = 1
else:
self.codigo = self.__class__.objects.all().order_by(
"-codigo")[0].codigo + 1
        super(Consumer, self).save(*args, **kwargs)

Si os fijáis, tiene asociado un Cargo, como Foreign Key. Por otro lado, tenemos el propio Cargo:

class Cargo(models.Model):
activoChoices = (
('S', 'Si'),
('N', 'No')
)
ID = models.IntegerField(
primary_key=True, null=False, blank=False, editable=False)
nombre = models.CharField(max_length=300, null=False, blank=False)
activo = models.CharField(
max_length=300,
null=False,
blank=False,
choices=activoChoices,
default='S'
)
def __unicode__(self):
return self.nombre
    def save(self, *args, **kwargs):
if not self.ID:
no = Cargo.objects.count()
if no == 0:
self.ID = 1
else:
self.ID = self.__class__.objects.all().order_by(
"-ID")[0].ID + 1
        super(Cargo, self).save(*args, **kwargs)

El cargo puede estar activo, o no. (Por X razón ya no está en uso, o cualquier otra cosa… Echadle imaginación ;-)

Por defecto, el widget listará todos los cargos disponibles.

Listado desordenado de todos los cargos independientemente de si están activos o no.

Para realizar ambas cosas, debemos definir la función formfield_for_foreignkey en nuestro admin.py

def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'cargo':
kwargs['queryset'] = Cargo.objects.filter(activo='S').order_by('nombre')
return super(ConsumerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

En este caso, modificamos el queryset que mostrará el select “cargo” (esto se corresponde al field del model de Consumer definido como “cargo”. Estamos filtrando que muestre únicamente los marcados como “activo”, y los ordene por “nombre”. Obteniendo como resultado:

Listado de cargos ordenados alfabéticamente y filtrados.
Esto tiene un montón de posibilidades. Puedes hacer muchas otras cosas en el queryset del select. Invertir orden, etc... Incluso ordenar por una foreignkey de esta foreignkey.

order_by(‘foreignKeyName__field’)

Si te es útil, no dudes en compartirlo. Y si tienes dudas, pregunta :)

Saludos!
Alberto García.

Este post está extraído de: http://blog.algargar.com/2015/11/12/filtrando-y-ordenando-widget-foreignkey-django-admin/