2012-04-05 11 views
26

que tienen un modelo básico de Django como:Django Tastypie Filtrado avanzado: Cómo hacer búsquedas complejas con Q objetos

class Business(models.Model): 
    name = models.CharField(max_length=200, unique=True) 
    email = models.EmailField() 
    phone = models.CharField(max_length=40, blank=True, null=True) 
    description = models.TextField(max_length=500) 

necesito para ejecutar una consulta compleja en el modelo anterior como:

qset = (
    Q(name__icontains=query) | 
    Q(description__icontains=query) | 
    Q(email__icontains=query) 
    ) 
results = Business.objects.filter(qset).distinct() 

he intentado lo siguiente usando tastypie sin suerte:

y en el Meta clase para tastypie he filtrado establecer como:

filtering = { 
     'name: ALL, 
     'description': ALL, 
     'email': ALL, 
     'query': ['icontains',], 
    } 

¿Alguna idea de cómo puedo hacer frente a esto?

Gracias - Newton

Respuesta

40

Está en el camino correcto. Sin embargo, se supone que build_filters pasa la búsqueda de recursos a una búsqueda ORM.

La implementación predeterminada divide la palabra clave de consulta basada en __ en key_bits, pares de valores y luego trata de encontrar una asignación entre el recurso buscado y su equivalente ORM.

Se supone que su código no aplica el filtro allí solo lo construye. Aquí es una versión mejorada y fija:

def build_filters(self, filters=None): 
    if filters is None: 
     filters = {} 
    orm_filters = super(BusinessResource, self).build_filters(filters) 

    if('query' in filters): 
     query = filters['query'] 
     qset = (
       Q(name__icontains=query) | 
       Q(description__icontains=query) | 
       Q(email__icontains=query) 
       ) 
     orm_filters.update({'custom': qset}) 

    return orm_filters 

def apply_filters(self, request, applicable_filters): 
    if 'custom' in applicable_filters: 
     custom = applicable_filters.pop('custom') 
    else: 
     custom = None 

    semi_filtered = super(BusinessResource, self).apply_filters(request, applicable_filters) 

    return semi_filtered.filter(custom) if custom else semi_filtered 

Puesto que está utilizando objetos Q, el apply_filters método estándar no es lo suficientemente inteligente como para solicitar su clave de filtro personalizado (ya que no hay ninguno), sin embargo se puede reemplazar con rapidez y agregue un filtro especial llamado "personalizado". Al hacerlo, su build_filters puede encontrar un filtro apropiado, construir lo que significa y pasarlo como personalizado para apply_filters, que simplemente lo aplicará directamente en lugar de tratar de descomprimir su valor de un diccionario como un elemento.

+0

Esto funciona bien. Gracias – nknganda

+3

Diccionario no tiene ningún método 'extender'. Debería ser: orm_filters.update ({'custom': qset}) –

+1

Esta solución provoca que se llame al DB dos veces (para semi_filtered y luego para el filtro personalizado). Un código ligeramente diferente me funciona: si 'custom' en applicable_filters: custom = applicable_filters.pop ('custom') return Outreaches.objects.filter (custom) else: return super (OutreachResource, self) .apply_filters (request, applicable_filters) –

0

He resuelto este problema de este modo:

Class MyResource(ModelResource): 

    def __init__(self, *args, **kwargs): 
    super(MyResource, self).__init__(*args, **kwargs) 
    self.q_filters = [] 

    def build_filters(self, filters=None): 
    orm_filters = super(MyResource, self).build_filters(filters) 

    q_filter_needed_1 = [] 
    if "what_im_sending_from_client" in filters: 
     if filters["what_im_sending_from_client"] == "my-constraint": 
     q_filter_needed_1.append("something to filter") 

    if q_filter_needed_1: 
     a_new_q_object = Q() 
     for item in q_filter_needed: 
     a_new_q_object = a_new_q_object & Q(filtering_DB_field__icontains=item) 
     self.q_filters.append(a_new_q_object) 

    def apply_filters(self, request, applicable_filters): 
    filtered = super(MyResource, self).apply_filters(request, applicable_filters) 

    if self.q_filters: 
     for qf in self.q_filters: 
     filtered = filtered.filter(qf) 
     self.q_filters = [] 

    return filtered 

Este método se siente como una separación más limpia de las preocupaciones que los otros que he visto.

+0

Es una mala idea colocar información específica de la solicitud en una instancia de recurso. Entonces 'self.q_filters.append (a_new_q_object)'.Esto se debe a que en un entorno desplegado con múltiples hilos, puede terminar con un estado de solicitud que influya en el de otro. Entonces, por ejemplo, todos los filtros creados en una solicitud podrían aplicarse a uno completamente diferente, según el tiempo. Vea los documentos aquí: http://django-tastypie.readthedocs.io/en/latest/resources.html#why-class-based Este es el problema que se resuelve al pasar un objeto 'bundle' por todas partes. –

0

Tomando la idea en la respuesta de Astevanovic y limpiándola un poco, lo siguiente debería funcionar y es más sucinto.

La principal diferencia es que apply_filters se hace más robusto utilizando None como la clave en lugar de custom (que podría entrar en conflicto con el nombre de una columna).

def build_filters(self, filters=None): 
    if filters is None: 
     filters = {} 
    orm_filters = super(BusinessResource, self).build_filters(filters) 

    if 'query' in filters: 
     query = filters['query'] 
     qset = (
       Q(name__icontains=query) | 
       Q(description__icontains=query) | 
       Q(email__icontains=query) 
       ) 
     orm_filters.update({None: qset}) # None is used as the key to specify that these are non-keyword filters 

    return orm_filters 

def apply_filters(self, request, applicable_filters): 
    return self.get_object_list(request).filter(*applicable_filters.pop(None, []), **applicable_filters) 
    # Taking the non-keyword filters out of applicable_filters (if any) and applying them as positional arguments to filter() 
Cuestiones relacionadas