framework python django tastypie django-q

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



install django tastypie (3)

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 compilarlo. 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 lo que significa 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()