Django Tastypie Advanced Filtering: cómo realizar búsquedas complejas con objetos Q

Tengo un modelo básico de Django como:

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 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:

 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'] print query qset = ( Q(name__icontains=query) | Q(description__icontains=query) | Q(email__icontains=query) ) results = Business.objects.filter(qset).distinct() orm_filters = {'query__icontains': results} return orm_filters 

y en la clase Meta para tastypie tengo conjunto de filtros como:

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

¿Alguna idea de cómo puedo abordar esto?

Gracias – newton

Estás en el camino correcto. Sin embargo, se supone que build_filters hace una transición de búsqueda de recursos a una búsqueda de ORM.

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

Se supone que su código no debe aplicar el filtro, solo comstackrlo. Aquí hay 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 

Debido a que está utilizando objetos Q, el método apply_filters estándar no es lo suficientemente inteligente como para aplicar su clave de filtro personalizada (ya que no hay ninguna), sin embargo, puede anularla rápidamente y agregar un filtro especial llamado “personalizado”. Al hacerlo, sus build_filters pueden encontrar un filtro apropiado, construir su significado y pasarlo como custom a apply_filters, que simplemente lo aplicará directamente en lugar de intentar descomprimir su valor de un diccionario como elemento.

Resolví este problema así:

 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 de preocupaciones más limpia que las otras que he visto.

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 al usar None como clave en lugar de custom (lo que podría entrar en conflicto con un nombre de 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()