2012-04-05 2 views
26

Ich habe eine grundlegende Django Modell wie:Django Tastypie Erweiterte Filter: Wie komplex Lookups mit Q tun Objekte

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) 

ich wie eine komplexe Abfrage auf dem obigen Modell ausführen müssen:

qset = (
    Q(name__icontains=query) | 
    Q(description__icontains=query) | 
    Q(email__icontains=query) 
    ) 
results = Business.objects.filter(qset).distinct() 

ich habe das versucht folgende Verwendung tastypie ohne Glück:

und in der Klasse Meta für tastypie Ich habe Filtern nach:

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

Irgendwelche Ideen, wie ich das angehen kann?

Dank - Newton

Antwort

40

Sie sind auf dem richtigen Weg. Allerdings soll build_filters die Ressourcen-Suche in eine ORM-Suche umwandeln.

Die Standardimplementierung teilt das Abfrageschlüsselwort basierend auf __ in Schlüssel_Bits, Wertepaare auf und versucht dann, eine Zuordnung zwischen der nachgeschlagenen Ressource und ihrem ORM-Äquivalent zu finden.

Ihr Code sollte nicht den Filter dort anwenden, nur bauen Sie es. Hier ist ein verbessertes und feste Version:

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 

Weil Sie Q-Objekte verwenden, die Standard-apply_filters Methode nicht intelligent genug ist, um Ihre benutzerdefinierten Filterschlüssel anwenden (da es keine), aber er schnell wieder außer Kraft setzen kann und Fügen Sie einen speziellen Filter namens "custom" hinzu. Dabei kann Ihr build_filters einen geeigneten Filter finden, konstruieren, was es bedeutet, und ihn an apply_filters übergeben, was ihn einfach direkt anwendet, anstatt zu versuchen, seinen Wert aus einem Wörterbuch als Element zu entpacken.

+0

Das funktioniert gut. Danke – nknganda

+3

Wörterbuch hat keine Methode "erweitern". Sollte sein: orm_filters.update ({'custom': qset}) –

+1

Diese Lösung verursacht den Aufruf der DB zweimal (für semi_filtered und dann für benutzerdefinierte Filter). Ein etwas anderer Code funktioniert für mich: Wenn 'Benutzerdefiniert' in anwendbare_Filter: Benutzerdefiniert = anwendbare_Filter.pop ('Benutzerdefiniert') gibt Outreaches.Objects.Filter (Benutzerdefiniert) zurück sonst: return Super (OutreachResource, Selbst) .Apply_Filter (Anforderung, anwendbare_Filter) –

0

Ich löste dieses Problem wie folgt:

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 

Diese Methode wie eine saubere Trennung von Bedenken fühlt sich, als die anderen, die ich gesehen habe.

+0

Es ist eine wirklich schlechte Idee, anforderungsspezifische Informationen zu einer Ressourceninstanz zu stellen. Also 'self.q_filters.append (a_new_q_object)'.Dies liegt daran, dass Sie in einer bereitgestellten Umgebung mit mehreren Threads möglicherweise den Status einer Anfrage beeinflussen, der die anderer beeinflusst. So könnten beispielsweise alle in einer Anfrage aufgebauten Filter je nach Timing tatsächlich auf eine ganz andere Anwendung angewendet werden. Sehen Sie sich die Dokumentation hier an: http://django-tastypie.readthedocs.io/en/latest/resources.html#why-class-based Dies ist das Problem, dass das Passieren eines "Bündel" -Objekts überall hin gelöst wird. –

0

Die Idee in astevanovic's Antwort nehmen und es ein bisschen aufräumen, sollte das Folgende funktionieren und ist prägnanter.

Der Hauptunterschied besteht darin, dass apply_filters robuster gemacht wird, indem None als Schlüssel anstelle von custom verwendet wird (was zu einem Spaltennamen führen könnte).

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