Расширенная фильтрация Django Tastypie: как выполнять сложные поисковые запросы с объектами Q
У меня есть базовая модель Django, например:
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)
Мне нужно выполнить сложный запрос на вышеупомянутой модели, например:
qset = (
Q(name__icontains=query) |
Q(description__icontains=query) |
Q(email__icontains=query)
)
results = Business.objects.filter(qset).distinct()
Я пробовал использовать tastypie, не удачи:
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
и в классе Meta для tastypie У меня есть фильтрация как:
filtering = {
'name: ALL,
'description': ALL,
'email': ALL,
'query': ['icontains',],
}
Любые идеи, как я могу справиться с этим?
Спасибо
- Ньютон
Ответы
Ответ 1
Вы на правильном пути. Тем не менее, build_filters
должен переходить на поиск ресурсов на поиск ORM.
Реализация по умолчанию разбивает ключевое слово запроса на основе __
на key_bits, пары значений, а затем пытается найти сопоставление между поиском ресурсов и его эквивалентом ORM.
В вашем коде не должен применяться фильтр, там его только строят. Вот улучшенная и исправленная версия:
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
Поскольку вы используете объекты Q, стандартный метод apply_filters
недостаточно умен, чтобы применить свой настраиваемый ключ фильтра (поскольку его нет), однако вы можете быстро переопределить его и добавить специальный фильтр под названием "custom". При этом ваш build_filters
может найти подходящий фильтр, построить то, что он означает, и передать его как custom для apply_filters, который просто применит его напрямую, а не пытается распаковать его значение из словаря в качестве элемента.
Ответ 2
Я решил эту проблему следующим образом:
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
Этот метод чувствует себя как более чистое разделение проблем, чем другие, которые я видел.
Ответ 3
Взяв идею в астебановом ответе и немного очистив ее, следующее должно работать и более кратким.
Основное отличие заключается в том, что apply_filters становится более надежным, используя None
в качестве ключа вместо custom
(который может конфликтовать с именем столбца).
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()