Czy wiesz jak działa Django Manager i w jaki sposób uprościć swoje zapytania?
Nie? Witaj w klubie, też kiedyś nie wiedziałam.
Naukę Django zaczęłam na drugim roku studiów, to właśnie tu stworzyłam pierwszą stronkę internetową (razem z Eweliną 😉 ). Django oferowało tyle możliwości na start, w dodatku świetny tutorial prowadzący krok po kroku, czego chcieć więcej.
Ale chwila!
Czy nie warto wiedzieć nad czym masz kontrolę, co możesz zmienić na własne potrzeby? Nie zawsze staniemy przed wyzwaniem stworzenia prostego CRUDa. Ba! W większości przypadków nasze aplikacje będą wymagały dodatkowych funkcjonalności, więc szczególnie jeśli jesteś na początku nauki programowania/frameworku warto się zagłębić i zrozumieć, co tak naprawdę dzieje się w naszym kodzie. Może w ten sposób podniesiesz jego jakość, albo zapunktujesz na rozmowie kwalifikacyjnej? Kto wie!
Rozszyfrujmy podstawowe zagadnienia, takie jak Queryset i Model Manager.
Czym jest Queryset?
Queryset jest zbiorem obiektów danego modelu, przy pomocy którego jesteśmy w stanie wykonać operacje odczytu, filtrowania czy sortowania.
Modelem nazwiemy strukturę pozwalającą na zdefiniowanie właściwości, zachowań. Jest szablonem tworzenia poszczególnych obiektów.
Stwórzmy przykładowy model – Article
.
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
objects = ArticleManager()
Aby klasa była traktowana jako model musi dziedziczyć po klasie django.db.models.Model. Każdy model będzie posiadać odpowiadającą mu tabelę w bazie danych, a każde pole poszczególny atrybut danej tabeli. Django domyślnie dostarcza bogate API dostępu do bazy danych (ORM), za pomocą którego możesz wykonywać operacje dodawania, pobierania czy usuwania.
Pobierzmy wszystkie projekty występujące w naszej bazie danych za pomocą metody all() wywołanej na atrybucie objects.
Article.objects.all()
Chwila, ale czym jest objects?
Jest to obiekt interfejsu (Manager) posiadającego wszystkie metody dostepu do bazy.
objects = models.Manager()
Django Manager jest interfejsem, który dostarcza operacje na bazie do modelu Django. To właśnie Manager oferuje nam funkcje – all()
, filter()
czy order_by()
. Domyślnie każdy model ma dostęp do operacji na bazie danych przez obiekt – objects.
Czy mogę w takim razie zdefiniować swój własny Manager? Jak najbardziej!
Tworzenie własnego Django Managera
Wróćmy do modelu Article
.
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
objects = ArticleManager()
Założmy, że potrzebujemy pobrać wszystkie opublikowane artykuły.
Article.objects.filter(published=True)
Czy mogę sam zdefiniować taką metodę?
Tak, aby stworzyć nowe metody, wystarczy stworzyć nowy Manager, w tym przypadku ArticleManager i podpiąć go pod zmienną objects. Dzięki temu, że ArticleManager
dziedziczy po models.Manager masz dostęp zarówno do funkcji tej klasy czyli all(), filter() itp, jak również do stworzonej funkcji get_published(), która zwróci tylko opublikowane artykuły.
from django.db import models class
ArticleManager(models.Manager):
def get_published(self):
return self.filter(published=True)
def get_archived(self):
return self.filter(archived=True)
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
objects = ArticleManager()
Od tej pory możemy używać metody get_published() do pobrania opublikowanych postów.
Article.objects.get_published()
Zamiast:
Article.objects.filter(published=True)
Często w zapytaniach tworzymy tak zwane “chainy” – łańcuchy wywołań killku funkcji. Taki ‘chain’ utworzymy w tym przypadku za pomocą metod – filter() i exclude().
Article.objects.filter(published=True).exclude(title__startswith='H')
Sprawdźmy chainowanie funkcji get_published().
Article.objects.get_published().exclude(title__startswith='H')
Działa!
Sprawdzmy chainowanie dwóch nowych metod – get_published() i get_archived().
Uwaga
Aby mieć możliwość chainowania stworzonej metody jak inne wbudowane funkcje (np.: order_by(), filter()) musimy nadpisać Queryset.
Django Manager domyślnie używa podstawowego modelu Queryset. Zgodnie z treścią błędu, który został otrzymany, Queryset nie ma takich metod jak get_published(), get_archived(), więc nie jest w stanie ich wywołać. Dodajmy takie metody tworząc własny Queryset.
from django.db import models
class ArticleQuerySet(models.QuerySet):
def get_published(self):
return self.filter(published=True)
def get_archived(self):
return self.filter(archived=True)
class ArticleManager(models.Manager):
def get_queryset(self):
return ArticleQuerySet(self.model, using=self._db)
def get_published(self):
return self.get_queryset().get_published()
def get_archived(self):
return self.get_queryset().get_archived()
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
objects = ArticleManager()
Spróbujmy ponownie.
Działa!
Dodatkowy Django Manager
Jeden Manager? Stwórzmy ich więcej! Możesz nie tylko dopisać nowe metody, ale również stworzyć odrębne dostępy do danych z modelu.
class PublishedArticleManager(models.Manager):
def get_queryset(self):
return super(PublishedArticleManager, self).get_queryset().filter(published=True)
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
published_objects = PublishedArticleManager()
Wystarczy, że stworzysz kolejny atrybut klasy published i przypiszesz do niego instancję klasy PubblishedArticleManager. Pobranie opublikowanych artykułów możesz wykonać na 2 sposoby:
Article.objects.filter(published=True)
Lub z użyciem nowego Managera:
Article.published_objects.all()
Z możliwością dowolnych operacji na zwróconym QuerySecie.
Article.published_objects.filter(published_at__date__eq='2020-11-22')
Uwaga
Jeśli po dodaniu dodatkowego Managera nadal chcesz używać objects, pamiętaj koniecznie o dodaniu deklaracji zmiennej objects.
class PublishedArticleManager(models.Manager):
def get_queryset(self):
return super(PublishedArticleManager, self).get_queryset().filter(published=True)
class Article(models.Model):
title = models.CharField(max_length=30)
published = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
objects = models.Manager() #domyślny Manager
published_objects = PublishedArticleManager()
Bez jej dodania otrzymasz taki błąd:
Zastosowanie
Jest kilka powodów, dla których warto pochylić się nad własnymi Managerami:
- dodanie nowych metod do Managera,
- modyfikacja działania domyślnego Managera,
- dodanie dodatkowego Managera.
Nie masz pomysłu, gdzie może Ci się to przydać?
- nieaktywni/aktywni użytkownicy,
- opublikowany/szkic posta na blogu,
- pobranie danych o konkretnych statusach,
- dostępność produktów,
- delikatne usuwanie (soft deleting).
Po dodatkową wiedzę warto sięgnąć do dokumentacji.
Podsumowanie
Queryset i Django Manager to jedne z podstawowych zagadnień, które warto rozumieć, a ich znajomość pozwoli Ci na dowolne modyfikacje.
Może i Ty masz jakieś pomysły na zastosowanie własnego Django Managera i Querysetu? Jeśli tak, zachęcam Cię do pozostawienia komentarza.
A może masz jakiś problem związany z Django? Albo coś sprawiło Ci dużą trudność na początku nauki tego frameworka? Daj koniecznie znać 😉