Django

Bazy danych - Modele

2 lutego 2016 21:00
Paweł Kucmus

Ten artykuł jest kontynuacją czteroczęściowego bloku o Django. Jeśli jeszcze tego nie zrobiłeś powinieneś najpierw odwiedzić: Django - pierwsze kroki

Bazy danych i Django

Django posiada domyślnie wbudowane umiejętności komunikacji z rożnymi bazami danych (np. SQLite, MySQL, PostgreSQL). Nowo utworzony projekt domyślnie ustawiony jest na komunikację z tylko jedną bazą typu SQLite. Te ustawienia są w sam raz dla naszych środowisk - do prób - jednak dla mocniej obciążonych aplikacji należy rozważyć użycie innych baz danych.
Odpowiadające za komunikację z bazą ustawienia znajdują się w pliku settings.py i domyślnie wyglądają tak:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Przy wprowadzeniu do Django wykonaliśmy już:

(pypila-django)$ python manage.py migrate

przy projekcie zorientowanym na SQLite dlatego w głównym katalogu projektu ukazał nam się plik z zawartością owej bazy. Plik został umieszczony właśnie tam dlatego, że taka ścieżka została wskazana przez os.path.join(BASE_DIR, 'db.sqlite3'). Z wymienionych wyżej serwisów tylko SQLite przechowuje dane w takim pliku. Po więcej informacji zapraszam tu: https://docs.djangoproject.com/en/1.9/topics/install/#get-your-database-running.

Dane w bazie

Na początek należy wyróżnić 3 elementy Django z jakimi przyjdzie nam pracować:

Model

Przypomnijcie sobie zajęcia, na których rozmawialiśmy o klasach i obiektach. Stworzyliśmy wtedy uproszczoną reprezentację samochodu w naszym Python'owym kodzie. Owe reprezentacje posiadały jakieś właściwości, stany i mogły podejmować akcje. Model w Django to reprezentacja danych znajdujących się w bazie danych. Potrafi odczytać dane zawarte w bazie dane, zna ich stan, może te dane zmieniać (zapisywać, usuwać).

Instancja modelu reprezentuje jedną konkretną rzecz (rekord) w bazie. Np. mając bazę danych z uczestnikami tego kursu chcę wybrać osobę, która zapisała się jako pierwsza - tej osoby reprezentacją będzie model, który zostanie mi zwrócony.

Teraz trochę o konstrukcji modeli. Wyobraźmy sobie następującą tabelę w bazie:

ID (int, AI, PK) Imię (char) Nazwisko (char) Wiek (int)
1 Ewa Kucmus 26
2 Lena Kucmus 2
3 Jan Kucmus 0

Pierwszy wiersz opisuje pola np. wiek jest polem numerycznym, kolejne to rekordy i zawierają dane w formie zdefiniowanej przez pola.

Model - tak jak rekord w bazie - składa się z pól. Chcąc stworzyć model z powyższej tabeli wystarczy minimalna ilość kodu:

from django.db import models

class CzlonekRodziny(models.Model):
    imie = models.CharField(max_length=128)
    nazwisko = models.CharField(max_length=128)
    wiek = models.IntegerField()

Pierwsze pole z "głównym kluczem" (ID) zostanie utworzone automatycznie.

Na czym teraz stoimy? Na etapie poinformowania naszej aplikacji o tym na jakich danych będziemy operować. To jednak nie wszystko gdyż nasza aplikacja, a baza danych to dwa różne serwisy gdzie jeden tylko czerpie z drugiego. Następnym krokiem więc jest poinformowanie bazy danych o danych jakie mamy zamiar w niej przechowywać - czyli stworzyć/zmienić/usunąć tabele. Służą do tego ostatnio wspomniane migracje. Migracje to generowany na podstawie definicji modeli automatycznie kod, który uruchomiony wprowadza zmiany w kształcie bazy danych. Za pomocą komendy:

(pypila-django)$ python manage.py makemigrations

zostanie utworzony pierwszy plik z migracją tworzącą naszą tabelkę przeznaczoną do przechowywania danych o rodzinie. Następnie musimy ową migrację uruchomić:

(pypila-django)$ python manage.py migrate

Od teraz (o ile wszystko się udało) możemy pracować z naszym modelem. Póki co nie mamy żadnego interfejsu graficznego ale mamy komendę shell, która pozwoli nam na dalsze ćwiczenia. Ale powoli...

Manager

Manager to obiekt "przyszyty" do klasy modelu pozwalający w łatwy sposób zarządzać danymi. Jednak nie danymi jako pojedynczymi wpisami (jak w przypadku samego modelu) ale ich kolekcjami. Analogicznie to przykładu z pierwszym uczestnikiem kursu - gdybym chciał pobrać dane wszystkich uczestników w wieku poniżej 30 lat lub dodać nowego uczestnika zrobiłbym to za pomocą Manager'a.

QuerySet

Instancja modelu reprezentuje pojedynczy "wpis" w bazie - to już ustaliliśmy. QuerySet z kolei reprezentuje zestaw tych danych - kolekcję. Zostanie zwrócony np. przez manager po przefiltrowaniu uczestników po wieku. I można go używać łańcuchowo gdyż zawsze QuerySet poproszony o "listę" wyników zwróci ją w postaci kolejnego QuerySet'u.

Tyle teorii - czas na praktykę

Uruchamiamy komendę shell.

(pypila-django)$ python manage.py shell

I od tego momentu jesteśmy w "skonfigurowanym" pod nasz projekt Django interpreterze Python. Zapiszmy nowych członków rodziny:

>>> from blog.models import CzlonekRodziny

>>> ewa = CzlonekRodziny(imie='Ewa', nazwisko='Kucmus', wiek=26)
>>> ewa.save()
>>> lenka = CzlonekRodziny(imie='Lena', nazwisko='Kucmus', wiek=2)
>>> lenka.save()
>>> jas = CzlonekRodziny(imie='Jan', nazwisko='Kucmus', wiek=0)
>>> jas.save()
>>> jacek = CzlonekRodziny(imie='Jacek', nazwisko='Kowalski', wiek=20)
>>> jacek.save()

Każda ze zmiennych ewa, lenka, jas są konkretnymi reprezentacjami członków rodziny - to cały czas ten sam model ale opisują co innego w "prawdziwym" świecie. Mając te dane zapisane w bazie (metoda save) możemy je z niej wyciągnąć - tu do gry wchodzi Manager Domyślnie - jeśli tego nie nadpiszemy - każdemu modelowi zostaje przypisana domyślna instancja klasy django.db.models.Manager pod atrybutem objects.

>>> ewa = CzlonekRodziny.objects.get(imie='Ewa')

Manager ten posiada kilka metod jedne - jak get - zwracają instancję modelu, inne - jak filter - QuerySet'y.

>>> kucmusy = CzlonekRodziny.objects.filter(nazwisko='Kucmus')

Jak wcześniej wspomniałem QuerySet'y możemy używać łańcuchami np.

>>> kucmusy = CzlonekRodziny.objects.filter(nazwisko='Kucmus').exclude(wiek__lte=2)

mają też część funkcjonalności Python'owych list:

>>> lenka = CzlonekRodziny.objects.filter(nazwisko='Kucmus')[1]

Co ważne jeśli dana metoda ma zwrócić tylko jeden obiekt (np. get) to może dojść do sytuacji gdzie wystąpi błąd. W naszym przypadku mając kilka rekordów z 'Kucmus' w nazwisku musimy być pewni, że wybieramy jeden konkretny i że w ogóle istnieje.

>>> kucmusy = CzlonekRodziny.objects.get(nazwisko='Kucmus')
MultipleObjectsReturned exception
>>> nowak = CzlonekRodziny.objects.get(nazwisko='Nowak')
DoesNotExist exception
>>> ewa = CzlonekRodziny.objects.get(nazwisko='Kucmus', imie='Ewa')

Po więcej kliknij tu: https://docs.djangoproject.com/en/1.9/ref/models/querysets/#methods-that-return-new-querysets

Admin

Django posiada wbudowany panel administracyjny domyślnie dostępny po ścieżce /admin. Nie zawsze nadaje się do zarządzania skomplikowanym systemem ale nadal jest świetnym narzędziem, które dostajemy "za darmo" wraz z instalacją Django.

Po uruchomieniu python manage.py runserver 0.0.0.0:8000 przejdzcie do przeglądarki wpiszcie adres waszego serwera ze ścieżką /admin (np. http://127.0.0.1:8000/admin). Zobaczycie ekran logowania do którego wprowadzacie dane, które podaliście wcześniej przy createsuperuser.

Zobaczycie listę z dwiema pozycjami Users i Groups, gdyż domyślnie są skonfigurowane do bycia dostępnymi z poziomu panelu.

To co chcemy teraz osiągnąć to mieć możliwość zarządzania członkami rodziny z poziomu owego panelu. Najpierw upewniamy się, że w module z naszą aplikacją (obok models.py) znajduje się plik admin.py. To w tym pliku decydujemy o zachowaniach panelu wobec naszych modeli. Aby dodać funkcjonalność zarządzania członkami rodziny powinniśmy edytować nasz plik następująco:

from django.contrib import admin
from blog.models import CzlonekRodziny

admin.site.register(CzlonekRodziny)

I tyle! Po zapisaniu pliku nasz serwer się odświeży, a my zobaczymy w przeglądarce nowy wpis, pod którym znajdziemy rekordy z członkami rodziny. Możemy rozwinąć stronę z rekordami o pewne dodatkowe funkcje przy pomocy ModelAdmin.

from django.contrib import admin
from blog.models import CzlonekRodziny

class CzlonekRodzinyAdmin(admin.ModelAdmin):
    list_display = ('imie', 'nazwisko', 'wiek', )
    search_fields = ('imie', 'nazwisko', )

admin.site.register(CzlonekRodziny, CzlonekRodzinyAdmin)

Więcej na ten temat tu: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/

Comments