2012-04-05 1 views
26

У меня есть основной Django модели, как:Джанго Tastypie Advanced Фильтрация: Как сделать сложные Lookups с Q объекты

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 не повезло:

и в классе Meta для tastypie я фильтрации установить в качестве:

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

Любые идеи, как я могу решить это?

Благодаря - Ньютон

ответ

40

Вы на правильном пути. Тем не менее, 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, который просто применит его напрямую, а не пытается распаковать его значение из словаря в качестве элемента.

+0

Это прекрасно работает. Спасибо – nknganda

+3

Словарь не имеет метода 'extend'. Должно быть: orm_filters.update ({'custom': qset}) –

+1

Это решение вызывает вызов БД дважды (для semi_filtered, а затем для настраиваемого фильтра). Для меня работает несколько другой код: if 'custom' в apply_filters: custom = apply_filters.pop ('custom') return Outreaches.objects.filter (custom) else: return super (OutreachResource, self) .apply_filters (request, apply_filters) –

0

Я решил эту проблему следующим образом:

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 

Этот метод чувствует себя более чистого разделения проблем, чем другие, которые я видел.

+0

Это действительно плохая идея поместить информацию о конкретном экземпляре ресурса. Итак, 'self.q_filters.append (a_new_q_object)'.Это связано с тем, что в развернутой среде с несколькими потоками вы можете оказаться в состоянии одного запроса, влияющем на чужие. Так, например, все фильтры, созданные в одном запросе, могут быть применены к совершенно другому, в зависимости от времени. См. Документы здесь: http://django-tastypie.readthedocs.io/en/latest/resources.html#why-class-based. Это проблема, связанная с передачей объекта 'bundle' во всем мире. –

0

Взяв идею в ответ астевановича и немного ее очистив, следующее должно работать и быть более кратким.

Основное отличие заключается в том, что 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() 

 Смежные вопросы

  • Нет связанных вопросов^_^