8.2. Czat (cz. 2)

Dodawanie, edycja, usuwanie czy przeglądanie danych zgromadzonych w bazie są typowymi czynnościami w aplikacjach internetowych. Utworzony w scenariuszu Czat (cz. 1) kod ilustruje “ręczną” obsługę żądań GET i POST, w tym tworzenie formularzy, walidację danych itp. Django zawiera jednak gotowe mechanizmy, których użycie skraca i ulepsza pracę programisty eliminując potencjalne błędy.

Będziemy rozwijać kod uzyskany po zrealizowaniu punktów 8.1.1 – 8.1.10 scenariusza Czat (cz. 1). Pobierz więc archiwum i rozpakuj w katalogu domowym użytkownika. Następnie wydaj polecenia:

Terminal nr
~$ source pve3/bin/activate
(pve3) ~$ cd czat2
(pve3) ~/czat2$ python manage.py check

Ostrzeżenie

Przypominamy, że pracujemy w wirtualnym środowisku Pythona z zainstalowanym frameworkiem Django, które powinno znajdować się w katalogu pve3. Zobacz w scenariuszu Czat (cz. 1), jak utworzyć takie środowisko.

8.2.1. Rejestrowanie

Na początku zajmiemy się obsługą użytkowników. Umożliwimy im samodzielne zakładanie kont w serwisie, logowanie i wylogowywanie się. Inaczej niż w cz. 1 zadania te zrealizujemy za pomocą tzw. widoków wbudowanych opartych na klasach (ang. class-based generic views).

Na początku pliku czat2/czat/urls.py importujemy formularz tworzenia użytkownika (UserCreationForm) oraz wbudowany widok przeznaczony do dodawania danych (CreateView):

Plik urls.pyKod nr
6
7
from django.contrib.auth.forms import UserCreationForm
from django.views.generic.edit import CreateView

Następnie do listy urlpatterns dopisujemy:

Plik urls.pyKod nr
18
19
20
    url(r'^rejestruj/', CreateView.as_view(
        template_name='czat/rejestruj.html',
        form_class=UserCreationForm,

Powyższy kod łączy adres URL /rejestruj z wywołaniem widoku wbudowanego jako funkcji CreateView.as_view(). Przekazujemy jej trzy parametry:

  • template_name – szablon, który zostanie użyty do zwrócenia odpowiedzi;
  • form_class – formularz, który zostanie przekazany do szablonu;
  • success_url – adres, na który nastąpi przekierowanie w przypadku braku błędów (np. po udanej rejestracji).

Teraz tworzymy szablon formularza rejestracji, który zapisać należy w pliku templates/czat/rejestruj.html:

Plik rejestruj.htmlKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- templates/czat/rejestruj.html -->
<html>
  <body>
    <h1>Rejestracja użytkownika</h1>
    {% if user.is_authenticated %}
      <p>Jesteś już zarejestrowany jako {{ user.username }}.
      <br /><a href="/">Strona główna</a></p>
    {% else %}
    <form method="POST">
      {% csrf_token %}
      {{ form.as_p }}
      <button type="submit">Zarejestruj</button>
    </form>
    {% endif %}
  </body>
</html>

Na koniec wstawimy link na stronie głównej, a więc uzupełniamy plik index.html:

Plik index.htmlKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- templates/czat/index.html -->
<html>
  <head></head>
  <body>
    <h1>Witaj w aplikacji Czat!</h1>

    {% if user.is_authenticated %}
      <p>Jesteś zalogowany jako {{ user.username }}.</p>
    {% else %}
      <p><a href="{% url 'czat:rejestruj' %}">Zarejestruj się</a></p>
    {% endif %}

  </body>
</html>

Ćwiczenie: dodaj link do strony głównej w szablonie rejestruj.html.

Uruchom aplikację (python manage.py runserver) i przetestuj dodawanie użytkowników: spróbuj wysłać niepełne dane, np. bez hasła; spróbuj dodać dwa razy tego samego użytkownika.

../../_images/django_rejestracja.png

8.2.2. Wy(logowanie)

Na początku pliku urls.py aplikacji dopisujemy wymagany import:

Kod nr
8
9
from django.core.urlresolvers import reverse_lazy
from django.contrib.auth import views as auth_views

– a następnie:

Plik urls.pyKod nr
22
23
24
25
26
27
    url(r'^loguj/', auth_views.login,
        {'template_name': 'czat/loguj.html'},
        name='loguj'),
    url(r'^wyloguj/', auth_views.logout,
        {'next_page': reverse_lazy('czat:index')},
        name='wyloguj'),

Widać, że z adresami /loguj i /wyloguj wiążemy wbudowane w Django widoki login i logout importowane z modułu django.contrib.auth.views. Jedynym nowym parametrem jest next_page, za pomocą którego wskazujemy stronę wyświetlaną po wylogowaniu (reverse_lazy('czat:index')).

Logowanie wymaga szablonu loguj.html, który tworzymy i zapisujemy w podkatalogu templates/czat:

Plik loguj.htmlKod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- templates/czat/loguj.html -->
<html>
  <body>
    <h1>Logowanie użytkownika</h1>
    {% if user.is_authenticated %}
      <p>Jesteś już zalogowany jako {{ user.username }}.
      <br /><a href="/">Strona główna</a></p>
    {% else %}
    <form method="POST">
      {% csrf_token %}
      {{ form.as_p }}
      <button type="submit">Zaloguj</button>
    </form>
    {% endif %}
  </body>
</html>

Musimy jeszcze określić stronę, na którą powinien zostać przekierowany użytkownik po udanym zalogowaniu. W tym wypadku na końcu pliku czat2/settings.py definiujemy wartość zmiennej LOGIN_REDIRECT_URL:

Plik settings.py. Kod nr
# czat2/settings.py

from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('czat:index')

Ćwiczenie: Uzupełnij plik index.html o linki służące do logowania i wylogowania.

../../_images/django_logowanie.png

8.2.3. Lista wiadomości

Chcemy, by zalogowani użytkownicy mogli przeglądać wiadomości wszystkich użytkowników, zmieniać, usuwać i dodawać własne. Najprostszy sposób to skorzystanie z widoków wbudowanych.

Informacja

Django oferuje wbudowane widoki przeznaczone do typowych operacji:

  • DetailView i ListView – (ang. generic display view) widoki przeznaczone do prezentowania szczegółów i listy danych;
  • FormView, CreateView, UpdateView i DeleteView – (ang. generic editing views) widoki przeznaczone do wyświetlania formularzy ogólnych, w szczególności służących dodawaniu, uaktualnianiu, usuwaniu obiektów (danych).

Do wyświetlania listy wiadomości użyjemy klasy ListView. Do pliku urls.py dopisujemy importy:

Kod nr
10
11
12
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView
from czat.models import Wiadomosc

– i wiążemy adres /wiadomosci z wywołaniem widoku:

Kod nr
28
29
30
31
32
33
    url(r'^wiadomosci/', login_required(
        ListView.as_view(
            model=Wiadomosc,
            context_object_name='wiadomosci',
            paginate_by=2)),
        name='wiadomosci'),

Zakładamy, że wiadomości mogą oglądać tylko użytkownicy zalogowani. Dlatego całe wywołanie widoku umieszczamy w funkcji login_required().

W funkcji ListView.as_view() podajemy kolejne parametry modyfikujące działanie widoków:

  • model – podajemy model, którego dane zostaną pobrane z bazy;
  • context_object_name – pozwala zmienić domyślną nazwę (object_list) listy obiektów przekazanych do szablonu;
  • paginate_by– pozwala określić ilość obiektów wyświetlanych na stronie.

Na końcu pliku czat2/settings.py określamy adres logowania, na który przekierowani zostaną niezalogowani użytkownicy, którzy próbowaliby zobaczyć listę wiadomości:

Kod nr
# czat2/settings.py

LOGIN_URL = reverse_lazy('czat:loguj')

Potrzebujemy szablonu, którego Django szuka pod domyślną nazwą <nazwa modelu>_list.html, czyli w naszym przypadku tworzymy plik czat/wiadomosc_list.html:

Plik wiadomosc_list.htmlKod 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
<!-- czat/wiadomosc_list.html -->
<html>
  <body>
    <h1>Wiadomości</h1>

    <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>

    {% if is_paginated %}
      <p>
      {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">Poprzednie</a>
      {% endif %}
        Strona {{ page_obj.number }} z {{ page_obj.paginator.num_pages }}.
      </span>
      {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">Następne</a>
      {% endif %}
      </p>
    {% endif %}

    <p><a href="{% url 'czat:index' %}">Strona główna</a></p>

  </body>
</html>

Kolejne wiadomości odczytujemy i wyświetlamy w pętli przy użyciu tagu {% for %}. Dostęp do właściwości obiektów umożliwia operator kropki, np.: {{ wiadomosc.autor.username }}.

Linki nawigacyjne tworzymy w instrukcji warunkowej {% if is_paginated %}. Obiekt page_obj zawiera następujące właściwości:

  • has_previous – zwraca True, jeżeli jest poprzednia strona;
  • previous_page_number – numer poprzedniej strony;
  • next_page_number – numer następnej strony;
  • number – numer aktualnej strony;
  • paginator.num_pages – ilość wszystkich stron.

Numer strony do wyświetlenia przekazujemy w zmiennej page adresu URL.

Ćwiczenie: Dodaj link do strony wyświetlającej wiadomości na stronie głównej dla zalogowanych użytkowników.

../../_images/django_index.png
../../_images/django_wiadomosci.png