2011-01-21 10 views
10

Me preguntaba si había una forma de usar el filtro de Django() en los conjuntos de consultas utilizando una propiedad de python generada dinámicamente usando property(). Tengo first_name y last_name de cada usuario, y quiero filtrar en función de su nombre concatenado first_name last_name. (La razón detrás de esto es que cuando hago autocompletar que busco para ver si la consulta coincide con el nombre, apellido, o parte de la concatenación. Quiero John S para que coincida con John Smith, por ejemplo.consulta de django en función de la propiedad dinámica()

creé una propiedad de name:.

def _get_name(self): 
    return self.first_name + " " + self.last_name 
    name = property(_get_name) 

de esta manera puedo llamar user.name para obtener el nombre concatenado

Sin embargo, si trato de hacer User.objects.filter(name__istartswith=query) me sale el error Cannot resolve keyword 'name' into field.

¿Alguna idea sobre cómo hacer esto? ¿Tengo que crear otro campo en la base de datos para almacenar el nombre completo?

Respuesta

9

filter() funciona en el nivel de la base de datos (en realidad escribe SQL), por lo que no será posible usarlo para consultas basadas en su código python (dynamic property in your question).

Esta es una respuesta formulado a partir de muchas otras respuestas en este departamento:)

+0

Interesante, no puede filtrar en las propiedades, pero puede agrupar los campos existentes y filtrar por el grupo [respuesta a continuación] (http://stackoverflow.com/a/19040011/3999748) – Persijn

0

pensar que no es posible en Django para filtrar las propiedades que no se presente como una base de datos de archivado, pero ¿qué se puede hacer para que la búsqueda fresca de autocompletar es algo como esto:

if ' ' in query: 
    query = query.split() 
    search_results = list(chain(User.objects.filter(first_name__icontains=query[0],last_name__icontains=query[1]), 
            User.objects.filter(first_name__icontains=query[1],last_name__icontains=query[0]))) 
else: 
    search_results = User.objects.filter(Q(first_name__icontains=query)| Q(last_name__icontains=query)) 

Este código le da al usuario de su sistema una flexibilidad para comenzar a escribir el nombre o el apellido y el usuario estará agradecido por permitir esto.

+0

pensado en eso, pero hay algunos problemas. Algunas personas pueden tener espacios en su nombre o apellido, por lo que dividirlo por el espacio puede estar roto. De lo contrario, sin embargo, ya lo tengo buscar nombre o apellido. Sin embargo, configuré una señal para llenar el campo de nombre al momento de la creación, así que funciona ahora. Gracias. – munchybunch

12

La respuesta aceptada no es del todo cierto.

Durante muchos casos, puede anular get() en el administrador de modelo para pop propiedades dinámicas de los argumentos de palabras clave, a continuación, añadir los atributos reales que desea consultar en contra en el diccionario argumentos kwargs palabra clave. Asegúrese de devolver un super para que cualquier llamada normal get() devuelva el resultado esperado.

Solo estoy pegando mi propia solución, pero para el __startswith y otras consultas condicionales podría agregar un poco de lógica al split el doble guión bajo y manejarlo adecuadamente.

Aquí fue mi trabajo en torno a permitir que consulta por una propiedad dinámica:

class BorrowerManager(models.Manager): 
    def get(self, *args, **kwargs): 
     full_name = kwargs.pop('full_name', None) 
     # Override #1) Query by dynamic property 'full_name' 
     if full_name: 
      names = full_name_to_dict(full_name) 
      kwargs = dict(kwargs.items() + names.items()) 
     return super(BorrowerManager, self).get(*args, **kwargs) 

En models.py:

class Borrower(models.Model): 
    objects = BorrowerManager() 

    first_name = models.CharField(null=False, max_length=30) 
    middle_name = models.CharField(null=True, max_length=30) 
    last_name = models.CharField(null=False, max_length=30) 
    created = models.DateField(auto_now_add=True) 

En utils.py (para el sake of context):

def full_name_to_dict(full_name): 
    ret = dict() 
    values = full_name.split(' ') 
    if len(values) == 1: 
     raise NotImplementedError("Not enough names to unpack from full_name") 
    elif len(values) == 2: 
     ret['first_name'] = values[0] 
     ret['middle_name'] = None 
     ret['last_name'] = values[1] 
     return ret 
    elif len(values) >= 3: 
     ret['first_name'] = values[0] 
     ret['middle_name'] = values[1:len(values)-1] 
     ret['last_name'] = values[len(values)-1] 
     return ret 
    raise NotImplementedError("Error unpacking full_name to first, middle, last names") 
+2

¡Bien! por cierto, podrías usar algo como 'isinstance (getattr (self.model, attr), property)' para verificar si el argumento es propiedad o no. Estoy haciendo algo de manera similar .. –

+1

'kwargs = dict (kwargs.items() + names.items())' se puede escribir como 'kwargs.update (names)' – thebjorn

+1

esto parece que ya no funciona en Django 1.9 –

3

Tuve un problema similar y estaba buscando una solución.Dando por sentado que un motor de búsqueda sería la mejor opción (por ejemplo django-haystack con Elasticsearch), que es como me gustaría poner en práctica algo de código para sus necesidades utilizando sólo el ORM de Django (se puede sustituir icontains con istartswith):

from django.db.models import Value 
from django.db.models.functions import Concat 

queryset = User.objects.annotate(full_name=Concat('first_name', Value(' '), 'last_name') 
return queryset.filter(full_name__icontains=value) 

En mi caso, no sabía si el usuario insertaría 'first_namelast_name' o viceversa, así que utilicé el siguiente código.

from django.db.models import Q, Value 
from django.db.models.functions import Concat 

queryset = User.objects.annotate(first_last=Concat('first_name', Value(' '), 'last_name'), last_first=Concat('last_name', Value(' '), 'first_name')) 
return queryset.filter(Q(first_last__icontains=value) | Q(last_first__icontains=value)) 

Con Django < 1.8, probablemente tendría que recurrir a extra con la función de SQL CONCAT, algo así como lo siguiente:

queryset.extra(where=['UPPER(CONCAT("auth_user"."last_name", \' \', "auth_user"."first_name")) LIKE UPPER(%s) OR UPPER(CONCAT("auth_user"."first_name", \' \', "auth_user"."last_name")) LIKE UPPER(%s)'], params=['%'+value+'%', '%'+value+'%']) 
Cuestiones relacionadas