2011-01-02 5 views
24

W Django, jeśli mam klasę modelu, np.W Django, czy możesz dodać metodę do zapytań?

from django.db import models 

class Transaction(models.Model): 
    ... 

następnie jeśli chcę dodać metody do modelu, do przechowywania np. dość złożone filtry, mogę dodać niestandardowego menedżera modelu, np.

class TransactionManager(models.Manager): 

    def reasonably_complex_filter(self): 
     return self.get_query_set().filter(...) 


class Transaction(models.Model): 
    objects = TransactionManager() 

i wtedy mogę zrobić:

>>> Transaction.objects.reasonably_complex_filter() 

Czy jest jakiś sposób mogę dodać niestandardową metodę, która może być przykuty do końca zapytania ustalonym z modelu?

tj dodać niestandardową metodę w taki sposób, że może to zrobić:

>>> Transaction.objects.filter(...).reasonably_complex_filter() 

Odpowiedz

5

Trzeba dodać metody do QuerySet które ostatecznie skończyć z. Musisz utworzyć i użyć podklasy QuerySet, która ma zdefiniowane metody, gdziekolwiek chcesz tę funkcjonalność.

znalazłem ten samouczek, który wyjaśnia, jak to zrobić i powody, dla których warto:

http://adam.gomaa.us/blog/2009/feb/16/subclassing-django-querysets/index.html

+0

Dobrze, gówno. Czy wiesz, czy sugerowana metoda w tym poście działa niezawodnie? Komentarze sugerują, że podejście "__getattr__" ma pewne problemy. –

+0

Nie stosowałem tego kodu w praktyce, ale widziałem "__getattr__" używany w projektach Django niezawodnie przedtem w podobny sposób. –

+1

Artykuł jest dość stary, ale chodzi o to, że musisz w jakiś sposób dołączyć swoje metody do obiektu 'QuerySet'. –

3

Można zmodyfikować metodę get_query_set() wrócić niestandardową QuerySet, dodając metody wymagają. W twoim przypadku, należałoby użyć:

class TransactionManager(models.Manager): 
    def get_query_set(self): 
     return TransactionQuerySet(self.model) 

class TransactionQuerySet(models.query.QuerySet): 
    def reasonably_complex_filter(self): 
     return self.filter(...) 

Widziałem przykłady instacji TransactionQuerySet do modelu Transaction lub w pokrewnej Manager, ale to zależy wyłącznie od Ciebie.

edit: Wydaje mi się, że nie zauważyła, że ​​objects Pierwsze wzmianki o TransactionManager i dlatego Transaction.objects.reasonably_complex_filter() nie jest możliwe w moim realizacji. Można to naprawić na trzy sposoby:

  • Wprowadź reasonably_complex_filter zarówno w Menedżerze, jak i w QuerySet;
  • Użyj Transaction.objects.all().reasonably_complex_filter(), gdy jest to jedyny wymagany filtr;
  • Skorzystaj z odpowiedzi Marcusa Whybrow na rozwiązanie, które zastosuje tę metodę zarówno w QuerySet jak i Manager bez powielania kodu.

Zależy od aplikacji, która opcja jest najbardziej pożądana, chociaż zdecydowanie zaleciłabym przeciwdziałanie powielaniu kodu (chociaż można by zastosować metodę globalną, aby temu zaradzić). Jednak ostatnia opcja może być zbyt kosztowna pod względem kosztów ogólnych, jeśli wymagać będzie tego rodzaju praktyki tylko raz lub jeśli zamierza się używać złożonego filtru w połączeniu z innym filtrem.

0

Tak naprawdę skończyłem z inną metodą.Okazało się, że muszę tylko połączyć się z wywołania filter na końcu mojej niestandardowej metody, więc poprawiłem moją metodę, aby zastosować arbitralne argumenty słów kluczowych i przekazałem je do wywołania filter() pod koniec mojego rozsądnie złożonego zapytania:

class TransactionManager(models.Manager): 

    def reasonably_complex_filter(self, **kwargs): 
     return self.get_query_set().filter(...).filter(**kwargs) 

Wydaje się działać dobrze dla moich celów i jest nieco prostsze niż podklasy QuerySet.

14

To kompletne rozwiązanie, które działa w Django 1.3, dzięki uprzejmości Zach Smith i Benowi.

class Entry(models.Model): 
    objects = EntryManager() # don't forget this 

    is_public = models.BooleanField() 
    owner = models.ForeignKey(User) 


class EntryManager(models.Manager): 
    '''Use this class to define methods just on Entry.objects.''' 
    def get_query_set(self): 
     return EntryQuerySet(self.model) 

    def __getattr__(self, name, *args): 
     if name.startswith("_"): 
      raise AttributeError 
     return getattr(self.get_query_set(), name, *args) 

    def get_stats(self): 
     '''A sample custom Manager method.''' 
     return { 'public_count': self.get_query_set().public().count() } 


class EntryQuerySet(models.query.QuerySet): 
    '''Use this class to define methods on queryset itself.''' 
    def public(self): 
     return self.filter(is_public=True) 

    def by(self, owner): 
     return self.filter(owner=owner) 


stats = Entry.objects.get_stats()  
my_entries = Entry.objects.by(request.user).public() 

Uwaga:get_query_set() sposób is now deprecated in Django 1.6; W tym przypadku należy użyć numeru get_queryset().

29

jako Django 1.7 zdolność to use a query set as a manager dodano:

class PersonQuerySet(models.QuerySet): 
    def authors(self): 
     return self.filter(role='A') 

    def editors(self): 
     return self.filter(role='E') 

class Person(models.Model): 
    first_name = models.CharField(max_length=50) 
    last_name = models.CharField(max_length=50) 
    role = models.CharField(max_length=1, choices=(('A', _('Author')), 
                ('E', _('Editor')))) 
    people = PersonQuerySet.as_manager() 

wynikającą z następujących:

Person.people.authors(last_name='Dahl') 

Dodatkowo dodano także możliwość dodawania custom lookups.