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()