7.4. Czat (cz. 1)

Zastosowanie Pythona i frameworka Django do stworzenia aplikacji internetowej Czat; prostego czata, w którym zarejestrowani użytkownicy będą mogli wymieniać się krótkimi wiadomościami.

Uwaga

Wymagane oprogramowanie:

  • Python v. 2.7.x
  • Django v. 1.10.x
  • Interpreter bazy SQLite3

7.4.1. Projekt i aplikacja

Tworzymy nowy projekt Django oraz szkielet naszej aplikacji. W katalogu domowym wydajemy polecenia w terminalu:

Terminal nr
~$ django-admin.py startproject czatpro
~$ cd czatpro
~/czatpro$ python manage.py migrate
~/czatpro$ django-admin.py startapp czat

Powstanie katalog projektu czatpro z podkatalogiem ustawień o takiej samej nazwie czatpro. Utworzona zostanie również inicjalna baza danych z tabelami wykorzystywanymi przez Django.

Dostosowujemy ustawienia projektu: rejestrujemy naszą aplikację w projekcie, ustawiamy polską wersję językową oraz lokalizujemy datę i czas. Edytujemy plik czatpro/settings.py:

Kod nr
# czatpro/czatpro/settings.py

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'czat',  # rejestrujemy aplikację
)

LANGUAGE_CODE = 'pl'  # ustawienie języka

TIME_ZONE = 'Europe/Warsaw'  # ustawienie strefy czasowej

Informacja

Jeżeli w jakimkolwiek pliku, np. settings.py chcemy używać polskich znaków, musimy na początku wstawić deklarację kodowania: # -*- coding: utf-8 -*-

Teraz uruchomimy serwer deweloperski, wydając polecenie:

Terminal nr
~/czatpro$ python manage.py runserver

Po wpisaniu w przeglądarce adresu 127.0.0.1:8000 zobaczymy stronę powitalną.

../../_images/czat01.png

Informacja

  • Domyślnie serwer nasłuchuje na porcie 8000, można to zmienić, podając port w poleceniu: python manage.py runserver 127.0.0.1:8080.
  • Lokalny serwer deweloperski zatrzymujemy za pomocą skrótu Ctrl+C.

Budowanie aplikacji w Django nawiązuje do wzorca projektowego MVC, czyli Model-Widok-Kontroler. Więcej informacji na ten temat umieściliśmy w osobnym materiale MVC.

7.4.2. Model danych

Budując aplikację, zaczynamy od zdefiniowania modelu (zob. model), czyli klasy opisującej tabelę zawierającą wiadomości. Atrybuty klasy odpowiadają polom tabeli. Instancje tej klasy będą reprezentować wiadomości utworzone przez użytkowników, czyli rekordy tabeli. Każda wiadomość będzie zwierała treść, datę dodania oraz wskazanie autora (użytkownika).

W pliku ~/czatpro/czat/models.py wpisujemy:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# -*- coding: utf-8 -*-
# czatpro/czat/models.py

from django.db import models
from django.contrib.auth.models import User


class Wiadomosc(models.Model):

    """Klasa reprezentująca wiadomość w systemie"""
    tekst = models.CharField(max_length=250)
    data_pub = models.DateTimeField()
    autor = models.ForeignKey(User)

Opisując klasę Wiadomosc podajemy nazwy poszczególnych właściwości (pól) oraz typy przechowywanych w nich danych. Po zdefiniowaniu przynajmniej jednego modelu możemy zaktualizować bazę danych, czyli zmienić/dodać potrzebne tabele:

Terminal nr
~/czatpro$ python manage.py makemigrations czat
~/czatpro$ python manage.py migrate
../../_images/czat02.png

Informacja

Domyślnie Django korzysta z bazy SQLite zapisanej w pliku db.sqlite3. Warto zobaczyć, jak wygląda. W terminalu wydajemy polecenie python manage.py dbshell, które otworzy bazę w interpreterze sqlite3. Następnie: * .tables - pokaże listę tabel; * .schema czat_wiadomosc - pokaże instrukcje SQL-a użyte do utworzenia podanej tabeli * .quit - wyjście z interpretera.

../../_images/czat03.png

7.4.3. Panel administracyjny

Utworzymy panel administratora dla projektu, dzięki czemu będziemy mogli zacząć dodawać użytkowników i wprowadzać dane. Otwieramy więc plik ~/czat/czat/admin.py i rejestrujemy w nim nasz model jako element panelu:

Kod nr
1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
# czatpro/czat/admin.py

from django.contrib import admin
from czat.models import Wiadomosc  # importujemy nasz model

# rejestrujemy model Wiadomosc w panelu administracyjnym
admin.site.register(Wiadomosc)

Informacja

Warto zapamiętać, że każdy model, funkcję, formularz czy widok, których chcemy użyć, musimy najpierw zaimportować za pomocą klauzuli typu from <skąd> import <co>.

Do celów administracyjnych potrzebne nam będzie odpowiednie konto. Tworzymy je, wydając w terminalu poniższe polecenie. Django zapyta o nazwę, email i hasło administratora. Podajemy: “admin”, “”, “admin”.

Terminal nr
~/czatpro$ python manage.py createsuperuser

Po ewentualnym ponownym uruchomieniu serwera wchodzimy na adres 127.0.0.1:8000/admin/. Logujemy się podając dane wprowadzone podczas tworzenia bazy. Otrzymamy dostęp do panelu administracyjnego, w którym możemy dodawać nowych użytkowników i wiadomości [1].

[1]Bezpieczna aplikacja powinna dysponować osobnym mechanizmem rejestracji użytkowników i dodawania wiadomości, tak by nie trzeba było udostępniać panelu administracyjnego osobom postronnym.
../../_images/czat04.png

7.4.3.1. Ćwiczenie 1

Po zalogowaniu na konto administratora dodaj użytkownika “adam”. Na stronie szczegółów, która wyświetli się po jego utworzeniu, zaznacz opcję “W zespole”, następnie w panelu “Dostępne uprawnienia” zaznacz opcje dodawania (add), zmieniania (change) oraz usuwania (del) wiadomości (wpisy typu: “czat | wiadomosc | Can add wiadomosc”) i przypisz je użytkownikowi naciskając strzałkę w prawo.

../../_images/czat06.png

Przeloguj się na konto “adam” i dodaj dwie przykładowe wiadomości. Następnie utwórz w opisany wyżej sposób kolejnego użytkownika o nazwie “ewa” i po przelogowaniu się dodaj co najmniej 1 wiadomość.

../../_images/czat05.png

7.4.3.2. Model w panelu

W formularzu dodawania wiadomości widać, że etykiety nie są spolszczone, z kolei dodane wiadomości wyświetlają się na liście jako “Wiadomosc object”. Aby poprawić te niedoskonałości, uzupełniamy plik models.py:

Kod nr
10
11
12
13
14
15
16
17
18
19
20
21
    """Klasa reprezentująca wiadomość w systemie"""
    tekst = models.CharField(u'wiadomość', max_length=250)
    data_pub = models.DateTimeField(u'data publikacji')
    autor = models.ForeignKey(User)

    class Meta:  # ustawienia dodatkowe
        verbose_name = u'wiadomość'  # nazwa obiektu w języku polskim
        verbose_name_plural = u'wiadomości'  # nazwa obiektów w l.m.
        ordering = ['data_pub']  # domyślne porządkowanie danych

    def __unicode__(self):
        return self.tekst  # "autoprezentacja"

W definicji każdego pola jako pierwszy argument dopisujemy spolszczoną etykietę, np. u'data publikacji'. W podklasie Meta podajemy nazwy modelu w liczbie pojedynczej i mnogiej. Dodajemy też funkcję __unicode__, której zadaniem jest “autoprezentacja” klasy, czyli wyświetlenie treści wiadomości. Po odświeżeniu panelu administracyjnego (np. klawiszem F5) nazwy zostaną spolszczone.

Informacja

Prefiks u wymagany w Pythonie v.2 przed łańcuchami znaków oznacza kodowanie w unikodzie (ang. unicode) umożliwiające wyświetlanie m.in. znaków narodowych.

Wskazówka

W Pythonie v.3 zamiast nazwy funkcji _unicode__ należy użyć str.

../../_images/czat09.png

7.4.4. Widoki i szablony

Panel administracyjny już mamy, ale po wejściu na stronę główną zwykły użytkownik niczego poza standardowym powitaniem Django nie widzi. Zajmiemy się teraz stronami po stronie (:-)) użytkownika.

Aby utworzyć stronę główną, zakodujemy pierwszy widok (zob. więcej »»»), czyli funkcję o przykładowej nazwie index(), którą powiążemy z adresem URL głównej strony (/). Najprostszy widok zwraca jakiś tekst: return HttpResponse("Witaj w aplikacji Czat!"). W pliku views.py umieszczamy:

Kod nr
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
# czatpro/czat/views.py

from django.http import HttpResponse


def index(request):
    """Strona główna aplikacji."""
    return HttpResponse("Witaj w aplikacji Czat!")

Teraz musimy powiązać widok z adresem url. Na początku do pliku projektu czatpro/urls.py dopiszemy import ustawień z naszej aplikacji:

Kod nr
19
20
21
22
23
urlpatterns = [
    url(r'^', include('czat.urls', namespace='czat')),
    url(r'^czat/', include('czat.urls', namespace='czat')),
    url(r'^admin/', include(admin.site.urls)),
]

Parametr namespace='czat' definiuje przestrzeń nazw, w której dostępne będą zdefiniowane dla naszej aplikacji mapowania między adresami url a widokami.

Następnie tworzymy (!) plik czat/urls.py o następującej treści:

Kod nr
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
# czatpro/czat/urls.py

from django.conf.urls import url
from czat import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

Podstawową funkcją wiążącą adres z widokiem jest url(). Jako pierwszy parametr przyjmuje wyrażenie regularne oznaczane r przed łańcuchem dopasowania. Symbol ^ to początek, $ – koniec łańcucha. Zapis r'^$' to adres główny serwera. Drugi parametr wskazuje widok (funkcję), która ma obsłużyć dany adres. Trzeci parametr name pozwala zapamiętać skojarzenie url-a i widoku pod nazwą, której będzie można użyć np. do wygenerowania adresu linku.

Przetestujmy nasz widok wywołując adres 127.0.0.1:8000. Powinniśmy zobaczyć tekst podany jako argument funkcji HttpResponse():

../../_images/czat10.png

Zazwyczaj odpowiedzią na wywołanie jakiegoś adresu URL będzie jednak jakaś strona zapisana w języku HTML. Szablony takich stron umieszczamy w podkatalogu templates/nazwa aplikacji. Tworzymy więc katalog:

Terminal nr
~/czatpro$ mkdir -p czat/templates/czat

Następnie tworzymy szablon ~/czatpro/czat/templates/czat/index.html, który zawiera:

Plik index.html. Kod nr
1
2
3
4
5
6
7
<!-- czatpro/czat/templates/czat/index.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>
  </body>
</html>

W pliku views.py zmieniamy instrukcje odpowiedzi:

Kod nr
 4
 5
 6
 7
 8
 9
10
11
# from django.http import HttpResponse
from django.shortcuts import render


def index(request):
    """Strona główna aplikacji."""
    # return HttpResponse("Witaj w aplikacji Czat!")
    return render(request, 'czat/index.html')

Po zaimportowaniu funkcji render() używamy jej do zwrócenia szablonu. Jako pierwszy argument podajemy obiekt typu HttpRequest zawierający informacje o żądaniu, a jako drugi nazwę szablonu z katalogiem nadrzędnym.

Po uruchomieniu serwera i wpisaniu adresu 127.0.0.1:8000 zobaczymy tekst, który umieściliśmy w szablonie:

../../_images/czat11.png

7.4.5. (Wy)logowanie

Udostępnimy użytkownikom możliwość logowania i wylogowywania się, aby mogli dodawać i przeglądać wiadomości.

Na początku w pliku views.py, jak zawsze, dopisujemy importy wymaganych obiektów, później dodajemy widoki loguj() i wyloguj():

Kod nr
6
7
8
9
from django.contrib.auth import login, logout
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.contrib import messages
Kod nr
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def loguj(request):
    """Logowanie użytkownika"""
    from django.contrib.auth.forms import AuthenticationForm
    if request.method == 'POST':
        form = AuthenticationForm(request, request.POST)
        if form.is_valid():
            login(request, form.get_user())
            messages.success(request, "Zostałeś zalogowany!")
            return redirect(reverse('czat:index'))

    kontekst = {'form': AuthenticationForm()}
    return render(request, 'czat/loguj.html', kontekst)


def wyloguj(request):
    """Wylogowanie użytkownika"""
    logout(request)
    messages.info(request, "Zostałeś wylogowany!")
    return redirect(reverse('czat:index'))

Widoki mogą obsługiwać zarówno żądania typu GET, kiedy użytkownik chce tylko zobaczyć jakieś dane na stronie, oraz POST, gdy wysyła informacje poprzez formularz, aby np. zostały zapisane. Typ żądania rozpoznajemy w instrukcji warunkowej if request.method == 'POST':.

W widoku logowania korzystamy z wbudowanego w Django formularza AuthenticationForm, dzięki temu nie musimy “ręcznie” sprawdzać poprawności przesłanych danych. Po wypełnieniu formularza przesłanymi danymi (form = AuthenticationForm(request, request.POST)) robi to metoda is_valid(). Jeżeli nie zwróci ona błędu, możemy zalogować użytkownika za pomocą funkcji login(), której przekazujemy żądanie (obiekt typu HttpRequest) i informację o użytkowniku zwrócone przez metodę get_user() formularza.

Tworzymy również informację zwrotną dla użytkownika, wykorzystując system komunikatów: messages.error(request, "..."). Tak utworzone komunikaty możemy odczytać w każdym szablonie ze zmiennej messages.

Na żądanie wyświetlenia strony (typu GET), widok logowania zwraca szablon loguj.html, któremu w słowniku kontekst udostępniamy pusty formularz logowania: return render(request, 'czat/loguj.html', kontekst).

Wylogowanie polega na użyciu funkcji logout(request) – wyloguje ona użytkownika, którego dane zapisane są w przesłanym żądaniu. Po utworzeniu informacji zwrotnej podobnie jak po udanym logowaniu przekierowujemy użytkownika na stronę główną (return redirect(reverse('index'))) z żądaniem jej wyświetlenia (typu GET).

Dalej potrzebny nam szablon logowania ~/czatpro/czat/templates/czat/loguj.html:

Plik loguj.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- czatpro/czat/templates/czat/loguj.html -->
<html>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>

    <h2>Logowanie użytkownika</h2>
    {% if not user.is_authenticated %}
      <form action="." method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Zaloguj</button>
      </form>
    {% else %}
      <p>Jesteś już zalogowany jako {{ user.username }}</p>
      <ul>
        <li><a href="{% url 'czat:index'%}">Strona główna</a></li>
      </ul>
    {% endif %}

  </body>
</html>

Na początku widzimy, jak sprawdzić, czy użytkownik jest zalogowany ({% if not user.is_authenticated %}), co pozwala różnicować wyświetlaną treść. Użytkownikom niezalogowanym wyświetlamy formularz. W tym celu musimy ręcznie wstawić znacznik <form>, zabezpieczenie formularza {% csrf_token %} oraz przycisk typu submit. Natomiast przekazany do szablonu formularz Django potrafi wyświetlić automatycznie, np. używając znaczników akapitów: {{ form.as_p }}.

Trzeba również zapamiętać, jak wstawiamy odnośniki do zdefiniowanych widoków. Służy do tego kod typu {% url 'czat:index' %} – w cudzysłowach podajemy na początku przestrzeń nazw przypisaną do aplikacji w pliku projektu czatpro/urls.py (namespace='czat'), a później nazwę widoku zdefiniowaną w pliku aplikacji czat/urls.py (name='index').

Komunikaty zwrotne przygotowane dla użytkownika w widokach wyświetlimy po uzupełnieniu szablonu index.html. Po znaczniku <h1> wstawiamy poniższy kod:

Plik index.html. Kod nr
 7
 8
 9
10
11
12
13
      {% if messages %}
        <ul>
        {% for komunikat in messages %}
           <li>{{ komunikat|capfirst }}</li>
        {% endfor %}
        </ul>
      {% endif %}

Jak widać na przykładach, w szablonach używamy tagów {% %} pozwalających korzystać z instrukcji warunkowych if, pętli for, czy instrukcji generujących linki url. Tagi {{ }} umożliwiają wyświetlanie wartości przekazanych zmiennych, np. {{ komunikat }} lub wywoływanie metod obiektów, np. {{ form.as_p }}. Zwracany tekst można dodatkowo formatować za pomocą filtrów, np. wyświetlać go z dużej litery {{ komunikat|capfirst }}.

Pozostaje skojarzenie widoków z adresami URL. W pliku czat/urls.py dopisujemy reguły:

Kod nr
 9
10
    url(r'^loguj/$', views.loguj, name='loguj'),
    url(r'^wyloguj/$', views.wyloguj, name='wyloguj'),

Możesz przetestować działanie dodanych funkcji wywołując w przeglądarce adresy: 127.0.0.1:8000/loguj i 127.0.0.1:8000/wyloguj. Przykładowy formularz wygląda tak:

../../_images/czat12.png

7.4.5.1. Ćwiczenie 2

Adresów logowania i wylogowywania nikt w serwisach nie wpisuje ręcznie. Wstaw zatem odpowiednie linki do szablonu strony głównej po bloku wyświetlającym komunikaty. Użytkownik niezalogowany powinien zobaczyć odnośnik Zaloguj, użytkownik zalogowany – Wyloguj. Przykładowe działanie stron może wyglądać tak:

../../_images/czat13.png
../../_images/czat14.png

7.4.6. Dodawanie wiadomości

Chcemy, by zalogowani użytkownicy mogli dodawać wiadomości, a także przeglądać wiadomości innych.

Jak zwykle, zaczynamy od widoku o nazwie np. wiadomosci() powiązanego z adresem /wiadomosci, który zwróci szablon wiadomosci.html. W odpowiedzi na żądanie GET wyświetlimy formularz dodawania oraz listę wiadomości. Kiedy dostaniemy żądanie typu POST (tzn. kiedy użytkownik wyśle formularz), spróbujemy zapisać nową wiadomość w bazie. Do pliku views.py dodajemy importy i kod funkcji:

Kod nr
10
11
from czat.models import Wiadomosc
from django.utils import timezone
Kod nr
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def wiadomosci(request):
    """Dodawanie i wyświetlanie wiadomości"""
    if request.method == 'POST':
        tekst = request.POST.get('tekst', '')
        if not 0 < len(tekst) <= 250:
            messages.error(
                request,
                "Wiadomość nie może być pusta, może mieć maks. 250 znaków!")
        else:
            wiadomosc = Wiadomosc(
                tekst=tekst,
                data_pub=timezone.now(),
                autor=request.user)
            wiadomosc.save()
            return redirect(reverse('wiadomosci'))

    wiadomosci = Wiadomosc.objects.all()
    kontekst = {'wiadomosci': wiadomosci}
    return render(request, 'czat/wiadomosci.html', kontekst)

Po sprawdzeniu typu żądania wydobywamy treść przesłanej wiadomości ze słownika request.POST za pomocą metody get('tekst', ''). Jej pierwszy argument to nazwa pola formularza użytego w szablonie, które chcemy odczytać. Drugi argument oznacza wartość domyślną, przydatną, jeśli pole będzie niedostępne.

Po sprawdzeniu długości wiadomości (if not 0 < len(tekst) <= 250:), możemy ją utworzyć wykorzystując konstruktor naszego modelu, podając jako nazwane argumenty wartości kolejnych pól: Wiadomosc(tekst=tekst, data_pub=timezone.now(), autor=request.user). Zapisanie nowej wiadomości w bazie sprowadza się do polecenia wiadomosc.save().

Pobranie wszystkich wiadomości z bazy realizuje kod: Wiadomosc.objects.all(). Widać tu, że używamy systemu ORM, a nie “surowego” SQL-a. Zwrócony obiekt umieszczamy w słowniku kontekst i przekazujemy do szablonu.

Zadaniem szablonu zapisanego w pliku ~/czat/czat/templates/wiadomosci.html będzie wyświetlenie komunikatów zwrotnych, np. błędów, formularza dodawania i listy wiadomości.

Plik wiadomosci.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- czatpro/czat/templates/czat/wiadomosci.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>

    {% if messages %}
      <ul>
        {% for komunikat in messages %}
           <li>{{ komunikat|capfirst }}</li>
        {% endfor %}
        </ul>
    {% endif %}

    <h2>Dodaj wiadomość</h2>
    <form action="." method="POST">
      {% csrf_token %}
      <input type="text" name="tekst" />
      <input type="submit" value="Zapisz" />
    </form>

    <h2>Lista wiadomości:</h2>
    <ol>
      {% for wiadomosc in wiadomosci %}
        <li>
          <strong>{{ wiadomosc.autor.username }}</strong> ({{ wiadomosc.data_pub }}):
          <br /> {{ wiadomosc.tekst }}
        </li>
      {% endfor %}
    </ol>

  </body>
</html>

Powyżej widać, że inaczej niż w szablonie logowania formularz przygotowaliśmy ręcznie (<input type="text" name="tekst" />), dalej pokażemy, jak można sprawić, aby framework robił to za nas. Widać również, że możemy wyświetlać atrybuty przekazanych w kontekście obiektów reprezentujących dane pobrane z bazy, np. {{ wiadomosc.tekst }}.

Widok wiadomosci() wiążemy z adresem /wiadomosci w pliku czat/urls.py, nadając mu nazwę wiadomosci:

Kod nr
11
    url(r'^wiadomosci/$', views.wiadomosci, name='wiadomosci'),

7.4.6.1. Ćwiczenie 3

  • W szablonie widoku strony głównej dodaj link do wiadomości dla zalogowanych użytkowników.
  • W szablonie wiadomości dodaj link do strony głównej.
  • Zaloguj się i przetestuj wyświetlanie [2] i dodawanie wiadomości pod adresem 127.0.0.1:8000/wiadomosci/. Sprawdź, co się stanie po wysłaniu pustej wiadomości.
[2]Jeżeli nie dodałeś do tej pory żadnej wiadomości, lista na początku będzie pusta.

Poniższe zrzuty prezentują efekty naszej pracy:

../../_images/czat15.png
../../_images/czat16.png

Przetestuj działanie aplikacji.

7.4.7. Materiały

  1. O Django http://pl.wikipedia.org/wiki/Django_(informatyka)
  2. Strona projektu Django https://www.djangoproject.com/
  3. Co to jest framework? http://pl.wikipedia.org/wiki/Framework
  4. Co nieco o HTTP i żądaniach GET i POST http://pl.wikipedia.org/wiki/Http

Źródła:


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:2017-06-19 o 06:04 w Sphinx 1.4.5
Autorzy:Patrz plik “Autorzy”