Zastosowanie frameworka Django do stworzenia aplikacji internetowej
„Czat” – prostego czata, w którym zarejestrowani użytkownicy będą mogli wymieniać się
krótkimi wiadomościami.
Do tworzenia aplikacji z użyciem Django możesz użyć dowolnych narzędzi, np. terminala i ulubionego edytora kodu.
Sugerujemy jednak wykorzystanie środowiska PyCharm, ponieważ w dużym stopniu ułatwia
pracę nad projektami w języku Python.
Przed rozpoczęciem pracy przygotuj w katalogu projekty_django`wirtualne środowisko Pythona
i w aktywnym środowisku zainstaluj pakiet Django:
Terminal nr
(.venv)~/projekty_django$pipinstalldjango==5.1.6
Ostrzeżenie
Za każdym razem przed rozpoczęciem pracy nad projektem upewnij się, że środowisko wirtualne zostało aktywowane.
Polecenie django-admin.pystartproject utworzy katalog czat1 – nazwa tego katalogu może być zmieniana.
Wewnątrz znajdziemy katalog projektu o takiej samej nazwie – czat1 – oraz
skrypt manage.py, który służy do zarządzania projektem.
Katalog projektu projekty_django/czat1/czat1 będzie zawierał widoczne poniżej pliki:
Polecenie manage.pymigrate tworzy domyślną bazę danych SQLite3 zapisaną w pliku db.sqlite3.
W bazie tworzone są tabele do przechowywania danych aplikacji domyślnie dostarczanych przez Django, m. in.:
W PyCharmie można również skonfigurować uruchamianie serwera. Rozwijamy listę Current File z górnego paska narzędzi
i wybieramy Edit Configurations. Następnie klikamy ikonę plus, a następnie Python.
W polu „Name” wpisujemy nazwę, np. Runserver. W polu „script” klikamy ikonę katalogu i wskazujemy
położenie skryptu manage.py. Pod spodem jako parametr skryptu wpisujemy runserver.
Jako katalog roboczy wskazujemy katalog projekty_django/czat1.
Od tej pory możemy uruchamiać serwer klikając przycisk Run na górnym pasku narzędzi lub skrótu SHIFT+F10.
Łączymy się z serwerem wpisując w przeglądarce adres: 127.0.0.1:8000.
Wskazówka
Jeżeli serwer uruchomiliśmy w terminalu, można również kliknąć adres
http://127.0.0.1:8000 z wciśniętym klawiszem CTRL,
aby otworzyć przeglądarkę.
W terminalu możemy obserwować żądania obsługiwane przez serwer.
Serwer zatrzymujemy naciskając w terminalu skrót CTRL+C lub za pomocą przycisku Stop (CTRL+F2).
Budowanie aplikacji w Django nawiązuje do wzorca projektowego MVC, czyli
Model-Widok-Kontroler (zob. materiał MVC).
Zaczynamy od zdefiniowania modelu (zob. model bazy danych), czyli klasy, która posłuży do utworzenia tabeli
w bazie danych zawierającej wiadomości. Atrybuty klasy odpowiadają polom tabeli. Instancje (obiekty) tej klasy
reprezentować będą wiadomości utworzone przez użytkowników, czyli rekordy tabeli.
Każda wiadomość będzie zwierała treść, datę dodania oraz identyfikator autora (użytkownika).
W pliku czat/models.py wpisujemy:
Plik models.pyKod nr
1fromdjango.contrib.auth.modelsimportUser 2fromdjango.dbimportmodels 3 4 5classWiadomosc(models.Model): 6 7"""Klasa reprezentująca wiadomość w systemie""" 8tekst=models.CharField('treść wiadomości',max_length=250) 9data_pub=models.DateTimeField('data publikacji',auto_now_add=True)10autor=models.ForeignKey(User,on_delete=models.CASCADE)1112classMeta:# ustawienia dodatkowe13verbose_name='wiadomość'# nazwa obiektu w języku polskim14verbose_name_plural='wiadomości'# nazwa obiektów w l.m.15ordering=['data_pub']# domyślne porządkowanie danych1617def__str__(self):18returnself.tekst# "autoprezentacja"
Definiując klasę Wiadomosc podajemy nazwy poszczególnych właściwości (pól)
oraz typy przechowywanych w nich danych.
Informacja
Typy pól:
CharField – pole znakowe, przechowuje niezbyt długie napisy, np. nazwy;
DateTimeField – pole daty i czasu;
ForeignKey – pole klucza obcego, czyli relacji;
wymaga nazwy powiązanego modelu jako pierwszego argumentu.
Właściwości pól:
pierwszy argument w definicji pola, np. treśćwiadomości określa przyjazną nazwę używaną np. w formularzach;
on_delete=models.CASCADE – jeżeli klucz obcy, w tym przypadku wskazujący autora, zostanie usunięty, usunięte zostaną również dodane przez niego wiadomości;
max_length – maksymalna długość pola znakowego;
auto_now_add=True – data i czas wstawione zostaną automatycznie.
Podklasa Meta pozwala określić formy liczby pojedynczej (verbose_name) i mnogiej (verbose_name_plural)
używane podczas wypisywania obiektów, a także domyślny sposób sortowania wiadomości (ordering=['data_pub']) wg daty dodania.
W metodzie __str__() decydujemy o tym, co powinno zostać zwrócone, jeżeli będziemy chcieli
wypisać obiekt naszej klasy. W typ przypadku zwracamy treść wiadomości, co będzie przydatne np. w panelu administracyjnym.
Operacje wykonywane na bazie danych nazywane są w Django migracjami.
Każda migracja powiązana jest z aplikacją wchodzącą w skład projektu.
Domyślnie wykonywane są migracje dla aplikacji admin, auth, contenttypes oraz sessions dostarczanych przez Django.
Po dodaniu lub zmianie modelu należy utworzyć migrację, aby w bazie danych zostały utworzone lub zmodyfikowane tabele,
w których zapisywane są przetwarzane w aplikacji dane. Następnie migrację należy wykonać. W tym celu wydajemy polecenia:
Domyślnie Django korzysta z bazy SQLite zapisanej w pliku db.sqlite3.
Możemy zobaczyć, co zawiera. W terminalu wydajemy polecenie pythonmanage.pydbshell,
które otworzy bazę w powłoce sqlite3, o ile będzie zainstalowana w systemie.
Następnie:
.tables - pokaże listę tabel;
.schemaczat_wiadomosc - pokaże instrukcje SQL-a użyte do utworzenia podanej tabeli
Dostarczany przez Django panel administratora pozwala zarządzać użytkownikami
i wprowadzać dane dla zarejestrowanych modeli. W pliku czat/admin.py umieszczamy kod:
Plik admin.pyKod nr
1from.modelsimportWiadomosc23# rejestrujemy model Wiadomosc w panelu administracyjnym4admin.site.register(Wiadomosc)
Na początku importujemy zawartość pliku models.py, a następnie rejestrujemy w panelu zdefiniowany wcześniej
model: admin.site.register(models.Wiadomosc).
– na pytanie o nazwę, email i hasło administratora, podajemy: „admin”, ENTER, „zaq1@WSX”.
Uruchom/zrestartuj serwer, w przeglądarce wpisz adres 127.0.0.1:8000/admin/
i zaloguj się na konto administratora.
Dodaj użytkowników „adam” i „ewa” z hasłami „zaq1@WSX”.
Na stronie „Zmień użytkownik”, która wyświetli się po kliknięciu przycisku Zapisz i kontynuuj edycję,
zaznacz opcję „W zespole”, aby użytkownicy mogli zalogować się do panelu administracyjnego.
W sekcji „Uprawnienia użytkownika” zaznacz prawa dodawania (add), zmieniania (change),
usuwania (del) oraz wyświetlania (view) wiadomości (wpisy typu: „Czat | wiadomosc | Can add wiadomosc”)
i przypisz je użytkownikowi naciskając strzałkę w prawo.
Zaloguj się na utworzone konta. Jako „adam” dodaj dwie przykładowe wiadomości, jako „ewa” – jedną.
Drugim krokiem podczas dodawania strony jest dodanie widoku (zob. widok, więcej »»»),
czyli w tym przypadku funkcji index() w pliku views.py:
Plik views.pyKod nr
1fromdjango.shortcutsimportrender2fromdjango.httpimportHttpResponse34defindex(request):5"""Strona główna aplikacji."""6returnHttpResponse('Witaj w aplikacji Czat!')
Każdy widok powinien zwracać do klienta (przeglądarki) obiekt typu HttpResponse.
W najprostszym przypadku może on zawierać tylko tekst, np.: returnHttpResponse("WitajwaplikacjiCzat!").
Uruchom serwer deweloperski i wejdź na stronę główną, tj. adres URL: 127.0.0.1:8000/admin/ –
powinieneś zobaczyć tekst podany jako argument metody HttpResponse().
Typową odpowiedzią na żądanie jest zwrócenie do przeglądarki strony HTML.
Takie zadanie realizujemy za pomocą szablonów, tj. plików HTML, które
umieszczamy w podkatalogu templates/nazwa_aplikacji.
W katalogu aplikacji tworzymy więc katalogi templates/czat, a w nim plik index.html.
Możemy to zrobić za pomocą poleceń wydawanych w terminalu, np.:
Do tworzenie katalogów i pustych plików możemy użyć systemowego menedżera plików lub używanego edytora
programistycznego.
Np. w PyCharmie, aby utworzyć wymagane katalogi i pliki klikamy prawym klawiszem katalog aplikacji
czat, wybieramy New / File i wpisujemy templates/czat/index.html.
Do szablonu templates/czat/index.html dodajemy kod:
Plik index.html. Kod nr
1<!-- templates/czat/index.html -->2<html>3<head></head>4<body>5<h1>Witaj w aplikacji Czat!</h1>6</body>7</html>
W pliku views.py zmieniamy funkcję index() tak, aby zwracała utworzony szablon:
Plik views.pyKod nr
4defindex(request):5"""Strona główna aplikacji."""6# return HttpResponse("Witaj w aplikacji Czat!")7returnrender(request,'czat/index.html')
Funkcja render() jako pierwszy parametr pobiera obiekt typu HttpRequest zawierający informacje
o żądaniu, jako drugi nazwę szablonu z katalogiem nadrzędnym.
Chcemy, żeby użytkownicy, którzy nie będą mogli logować się do panelu administracyjnego, również mieli możliwość
dodawania i przeglądania wiadomości. Musimy im więc stworzyć możliwość logowania i wylogowywania się.
Podobnie jak w przypadku strony głównej zaczniemy od zdefiniowania adresów URL, następnie dodamy powiązane z nimi
widoki, a na końcu zmienimy i utworzymy odpowiednie szablony.
1fromdjango.shortcutsimportrender,redirect2# from django.http import HttpResponse3fromdjango.contrib.authimportlogin,logout4fromdjango.urlsimportreverse5fromdjango.contribimportmessages
Logowanie rozpoczyna się od żadania typu GET wysłanego na adres /loguj.
Widok loguj() zwraca wtedy szablon: returnrender(request,'czat/loguj.html',kontekst).
W słowniku kontekst w kluczu form przekazujemy do szablonu pusty formularz logowania
– instancję klasy AuthenticationForm().
Kiedy użytkownik wypełni formularz logowania danymi i kliknie przycisk „Zaloguj”, otrzymamy żądanie typu POST.
Po wykryciu takiego żądania (ifrequest.method=='POST':) tworzymy instancję
formularza wypełnioną przesłanymi danymi: form=AuthenticationForm(request,request.POST).
Jeżeli dane są poprawne ifform.is_valid():, możemy zalogować użytkownika
za pomocą funkcji login(request,form.get_user()).
Przygotowujemy również informację zwrotną dla użytkownika, wykorzystując system komunikatów:
messages.success(request,'...').
Tak utworzone komunikaty możemy odczytać w każdym szablonie ze zmiennej messages.
Na koniec przekierowujemy użytkownika na stronę główną (returnredirect(reverse('index')))
z żądaniem (typu GET) jej wyświetlenia.
Wylogowanie polega na użyciu funkcji logout(request) – wyloguje ona
użytkownika, którego dane zapisane są w przesłanym żądaniu.
Informacja
Warto zauważyć, że każdy widok otrzymuje jako pierwszy argument obiekt request
zawierający wszystkie dane przetwarzanego żądania, w tym dane przesłane z formularza
– request.POST.
Tworzymy i uzupełniamy szablon logowaniatemplates/czat/loguj.html kodem:
Plik loguj.html Kod nr
1<!-- templates/czat/loguj.html --> 2<html> 3<head></head> 4<body> 5<h1>Witaj w aplikacji Czat!</h1> 6 7<h2>Logowanie użytkownika</h2> 8 {% if not user.is_authenticated %}
9<formaction="."method="POST">10 {% csrf_token %}
11 {{ form.as_p }}
12<buttontype="submit">Zaloguj</button>13</form>14 {% else %}
15<p>Jesteś już zalogowany jako {{ user.username }}</p>16<ul>17<li><ahref="{% url 'czat:index'%}">Strona główna</a></li>18</ul>19 {% endif %}
2021</body>22</html>
W szablonach wykorzystujemy specjalne tagi dwóch rodzajów:
{%instrukcja%} – pozwalają używać instrukcji sterujących, np. warunkowych lub pętli,
{{zmienna}} – służą stawianiu wartości zmiennych lub wywoływaniu metod obiektów przekazanych do szablonu.
Zastosowanie:
{%ifnotuser.is_authenticated%} – instrukcja sprawdza, czy aktualny użytkownik nie jest zalogowany,
{%csrf_token%} – zabezpieczenie formularza przed atakiem typu csrf,
{{form.as_p}} – wyświetla pola formularza w akapitach,
{%url'czat:index'%} – generuje adres URL: w cudzysłowach podajemy przestrzeń nazw naszej aplikacji
(app_name), a później nazwę widoku (name) zdefiniowane w pliku czat/urls.py,
{{user.username}} – obiekt user zawiera dane zalogowanego użytkownika, m. in. jego nazwę, którą wyświetlamy.
Uzupełniamy szablon index.html. Po znaczniku <h1> wstawiamy poniższy kod:
Plik index.html Kod nr
7 {% if messages %}
8<ul> 9 {% for komunikat in messages %}
10<li>{{ komunikat|capfirst }}</li>11 {% endfor %}
12</ul>13 {% endif %}
1415 {% if not user.is_authenticated %}
16<ahref="{% url 'czat:loguj' %}">Zaloguj</a>17 {% else %}
18<formaction="{% url 'wyloguj' %}"method="post">19 {% csrf_token %}
20<inputtype="submit"value="Wyloguj się">21</form>22 {% endif %}
{%ifmessages%} – sprawdzamy, czy mamy jakieś komunikaty zwrotne,
{%forkomunikatinmessages%} – w pętli odczytujemy kolejne komunikaty i …
{{komunikat|capfirst}} – wyświetlamy przy użyciu filtra z dużej litery.
Jeżeli użytkownik został zalogowany, wyświetlamy formularz zawierający tylko przycisk „Wyloguj się”.
Po jego kliknięciu zostanie wysłane żądanie typu POST, które zostanie obsłużone przez omawiany
wyżej widok wyloguj().
Obsługa żądania typu GET, czyli wyświetlenie wiadomości wymaga odczytu (ang. Read) danych z bazy.
W języku SQL używamy do tego klauzuli SELECT. Z kolei żądania typu POST wiążą się z dodawaniem (ang. Create),
modyfikacją (ang. Update) i usuwaniem (ang. Delete) danych w bazie. W języku SQL służą do tego klauzule
INSERT, UPDATE i DELETE.
Django nie wymaga zapytań w języku SQL, chociaż są one obsługiwane. Zamiast tego oferuje niezależny od konkretnej
bazy danych interfejs programistyczny (API) działający jak system ORM. Jego podstawowym założeniem jest to,
że każdy model danych odpowiada tablicy w bazie danych, jego instancje (obiekty) reprezentują konkretne rekordy,
a właściwości modelu (obiektu) odpowiadają polom rekordu.
Do odczytu danych (ang. Read) używamy managera objects, który zwraca kolekcję obiektów QuerySet odpowiadającą
klauzuli SQL SELECT. Rekordy w kolekcji można filtrować, co odpowiada klauzulom SQL WHERE i LIMIT.
Przećwiczmy kilka przykładów. W terminalu w katalogu projekty_django/czat1 wydajemy polecenie:
Uruchomiona zostanie powłoka Pythona w trybie interaktywnym z zaimportowanymi obiektami. Dodatkowo importujemy
moduły potrzebne do operacji na datach i czasie.
Teraz możemy wpisywać po kolei poniższe przykłady kodu:
wiadomosci=Wiadomosc.objects.all() – za pomocą metody all() pobieramy wszystkie obiekty typu Wiadomosc
z bazy i zapisujemy pod nazwą wiadomosci,
wiadomosci – zobaczymy, że wiadomosci to typ QuerySet zawierający listę obiektów typu Wiadomosc,
wiadomosci.filter(autor_id=2) – metoda filter() z kolekcji rekordów wyodrębni te,
których autor ma identyfikator (pole id obiektu typu User) równy 2;
wiadomosci.filter(autor__username__exact='adam') – z kolekcji rekordów wyodrębniamy te,
których nazwa autora (właściwość username obiektu typu User) równa jest «adam»,
wiadomosci.exclude(tekst__contains='Pierwsza') – z kolekcji rekordów usunie te,
które zawierają ciąg znaków Pierwsza,
Wiadomosc.objects.filter(data_pub__lte=timezone.now()-datetime.timedelta(days=10)) –
zwróci wiadomości opublikowane przed 10 dniami,
w1=Wiadomosc.objects.get(pk=1) – metoda get() zwraca jeden obiekt, który spełnia podany jako argument warunek,
w tym przypadku identyfikator obiektu (ang. pk to skrót od primary key – klucz główny) musi być równy 1,
w1 – zobaczymy, że w1 zawiera obiekt typu Wiadomosc.
Informacja
Metody filter(**kwargs) i get(**kwargs) przyjmują argumenty w ogólnej postaci
field__lookuptype=value, gdzie:
field – to nazwa właściwości modelu odpowiadająca polu rekordu,
__ – podwójne podkreślenie,
lookuptype=value – rodzaj dopasowania podanej wartości,
tj. warunek, jaki ma spełniać dana właściwość (pole rekordu).
Często używane rodzaje dopasowań wartości sprawdzanego pola:
__exact='ciąg_znaków') – musi odpowiadać dokładnie ciągowi znaków,
__contains='ciąg_znaków' – musi zawierać ciąg znaków,
__in=obiekt_iterowalny – zawiera się w obiekcie iterowalnym, tj. tupli, liście, queryset lub ciągu znaków,
Dodawanie danych do bazy (ang. Create) polega na utworzeniu instancji wybranego modelu z wykorzystaniem
nazwanych argumentów i wywołaniu metody save(), która zapisze go w bazie, czyli wykona operację INSERT SQL.
Poniżej przykład, który wykonujemy w uruchomionej wcześniej powłoce:
user=User.objects.get(username__exact='adam') – utworzenie obiektu użytkownika o nazwie «adam»,
Wiadomosc.objects.filter(autor=user) – odczyt wiadomości danego użytkownika,
wiadomosc=Wiadomosc(tekst='TrzeciawiadomośćAdama',autor=user) – utworzenie instancji nowej wiadomości,
wiadomosc.save() – zapisanie wiadomości w bazie.
Inną metodą utworzenia i zapisania obiektu w bazie danych jest użycie metody create(), np.:
Wiadomosc.objects.create(tekst='CzwartawiadomośćAdama',autor=user) – tworzy i od razu zapisuje nową wiadomość
w bazie,
Wiadomosc.objects.filter(autor=user) – ponowny odczyt wiadomości danego użytkownika, powinniśmy zobaczyć dwie
nowe wiadomości.
Następnie dodajemy widok o nazwie wiadomosci(). W pliku views.py umieszczamy import i kod funkcji:
Plik views.pyKod nr
6from.modelsimportWiadomosc
Plik views.pyKod nr
32defwiadomosci(request):33"""Dodawanie i wyświetlanie wiadomości"""34ifrequest.method=='POST':35tekst=request.POST.get('tekst','')36ifnot0<len(tekst)<=250:37messages.error(38request,39"Wiadomość nie może być pusta, może mieć maks. 250 znaków!")40else:41wiadomosc=Wiadomosc(42tekst=tekst,43autor=request.user)44wiadomosc.save()45returnredirect(reverse('czat:wiadomosci'))4647wiadomosci=Wiadomosc.objects.all()48kontekst={'wiadomosci':wiadomosci}49returnrender(request,'czat/wiadomosci.html',kontekst)
returnrender(request,'czat/wiadomosci.html',kontekst) – zwracamy szablon,
do którego przekazujemy słownik kontekst zawierający wiadomości.
Obsługa żądania typu POST, czyli przetworzenie danych z przesłanego formularza:
tekst=request.POST.get('tekst','') – wiadomość pobieramy ze słownika
request.POST za pomocą metody get('tekst',''), pierwszy argument to
nazwa pola formularza, drugi argument to wartość domyślna,
jeśli pole będzie niedostępne.
ifnot0<len(tekst)<=250: – sprawdzenie minimalnej i maksymalnej
długości wiadomości,
Wiadomosc(tekst=tekst,autor=request.user) – utworzenie instancji wiadomości
za pomocą konstruktora modelu, któremu przekazujemy wartości wymaganych pól,
wiadomosc.save() – zapisanie nowej wiadomości w bazie.
W pliku templates/czat/wiadomosci.html przygotowujemy szablon, który
będzie wyświetlał komunikaty zwrotne, np. błędy, a także formularz dodawania i listę wiadomości: