7.5. 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 programistyczną pracę eliminując potencjalne błędy.

Będziemy rozwijać kod uzyskany po zrealizowaniu punktów 5.4.1 – 5.4.4 scenariusza Czat (cz. 1). Pobierz więc archiwum z potrzebnymi plikami i rozpakuj w katalogu domowym użytkownika. Utworzony zostanie katalog czatpro2, w którym będziemy pracować.

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

7.5.1. Rejestrowanie

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

Kod nr
6
7
from django.contrib.auth.forms import UserCreationForm
from django.views.generic.edit import CreateView

Następnie do listy paterns dopisujemy:

Plik urls.py. Kod nr
17
18
19
20
    url(r'^rejestruj/', CreateView.as_view(
        template_name='czat/rejestruj.html',
        form_class=UserCreationForm,
        success_url='/'), name='rejestruj'),

Powyższy kod wiąże 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 czatpro2/czat/templates/czat/rejestruj.html:

Plik rejestruj.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- czatpro2/czat/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.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- czatpro2/czat/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>

Zwróć uwagę na sposób tworzenia linków w szablonie: {% url 'czat:rejestruj' %}. czat to nazwa przestrzeni nazw zdefiniowanej w pliku adresów projektu czatpro2/czatpro/urls.py (namespace='czat'). Link rejestruj definiowany jest w parametrze name w pliku czatpro2/czat/urls.py aplikacji.

Ć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/czatpro2_02.png

7.5.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.py. Kod nr
21
22
23
24
25
26
    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.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- czatpro2/czat/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 czatpro/czatpro/settings.py definiujemy wartość zmiennej LOGIN_REDIRECT_URL:

Kod nr
# czatpro2/czatpro/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/czatpro2_03.png

7.5.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 wspomnianych widoków wbudowanych.

Note

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
27
28
29
30
31
32
33
    url(r'^wiadomosci/', login_required(
        ListView.as_view(
            model=Wiadomosc,
            context_object_name='wiadomosci',
            paginate_by=10),
        login_url='/loguj'),
        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 wywołaniu ListView.as_view() wykorzystujemy 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 ustawić ilość obiektów wyświetlanych na stronie.

Parametr login_url określa adres, na który przekierowany zostanie niezalogowany użytkownik.


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

Plik wiadomosc_list.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- czatpro2/czat/templates/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>

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

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

../../_images/czatpro2_04.png

7.5.4. Dodawanie wiadomości

Zadanie to zrealizujemy wykorzystując widok CreateView. Aby ułatwić dodawanie wiadomości dostosujemy klasę widoku tak, aby użytkownik nie musiał wprowadzać pola autor.

Na początek dopiszemy w pliku urls.py skojarzenie adresu URL wiadomosc/ z wywołaniem klasy CreateView jako funkcji:

Kod nr
34
35
36
37
    url(r'^wiadomosc/$', login_required(
        views.UtworzWiadomosc.as_view(),
        login_url='/loguj'),
        name='wiadomosc'),

Dalej kodujemy w pliku views.py. Na początku dodajemy importy:

Kod nr
6
7
8
9
from django.views.generic.edit import CreateView
from czat.models import Wiadomosc
from django.utils import timezone
from django.contrib import messages
Kod nr
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class UtworzWiadomosc(CreateView):
    model = Wiadomosc
    fields = ['tekst', 'data_pub']
    context_object_name = 'wiadomosci'
    success_url = '/wiadomosc'

    def get_initial(self):
        initial = super(UtworzWiadomosc, self).get_initial()
        initial['data_pub'] = timezone.now()
        return initial

    def get_context_data(self, **kwargs):
        context = super(UtworzWiadomosc, self).get_context_data(**kwargs)
        context['wiadomosci'] = Wiadomosc.objects.all()
        return context

    def form_valid(self, form):
        wiadomosc = form.save(commit=False)
        wiadomosc.autor = self.request.user
        wiadomosc.save()
        messages.success(self.request, "Dodano wiadomość!")
        return super(UtworzWiadomosc, self).form_valid(form)

Dostosowując widok ogólny, tworzymy opartą na nim klasę class UtworzWiadomosc(CreateView). Nieomówiona dotąd właściwość fields pozwala wskazać pola, które mają znaleźć się na formularzu. Jak widać, pomijamy pole autor.

Pole to jest jednak wymagane. Aby je uzupełnić, nadpisujemy metodę form_valid(), która sprawdza poprawność przesłanych danych i zapisuje je w bazie:

  • wiadomosc = form.save(commit=False) – tworzymy obiekt wiadomości, ale go nie zapisujemy;
  • wiadomosc.autor = self.request.user – uzupełniamy dane autora;
  • wiadomosc.save() – zapisujemy obiekt;
  • messages.success(self.request, "Dodano wiadomość!") – przygotowujemy komunikat, który wyświetlony zostanie po dodaniu wiadomości.

Metoda get_initial() pozwala ustawić domyślne wartości dla wybranych pól. Wykorzystujemy ją do zainicjowania pola data_pub aktualna datą (initial['data_pub'] = timezone.now()).

Metoda get_context_data() z punktu widzenia dodawania wiadomości nie jest potrzebna. Pozwala natomiast przekazać do szablonu dodatkowe dane, w tym wypadku jest to lista wszystkich wiadomości: context['wiadomosci'] = Wiadomosc.objects.all(). Wyświetlimy je poniżej formularza dodawania nowej wiadomości.


Domyślny szablon dodawania danych nazywa się <nazwa modelu>_form.html. Możemy go utworzyć na podstawie szablonu wiadomosc_list.html. Otwórz go i zapisz pod nazwą wiadomosc_form.html. Przed listą wiadomości umieść kod wyświetlający komunikaty i formularz:

Plik wiadomosc_form.html. Kod nr
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    {% if messages %}
      <ul>
        {% for komunikat in messages %}
          <li>{{ komunikat|capfirst }}</li>
        {% endfor %}
      </ul>
    {% endif %}

    <h2>Dodaj wiadomość:</h2>
    <form method="POST">
      {% csrf_token %}
      {{ form.as_p }}
      <button type="submit">Zapisz</button>
    </form>

Ćwiczenie: Jak zwykle, umieść link do dodawanie wiadomości na stronie głównej.

../../_images/czatpro2_05.png

7.5.5. Edycja wiadomości

Widok pozwalający na edycję wiadomości i jej aktualizację dostępny będzie pod adresem /edytuj/id_wiadomości, gdzie id_wiadomosci będzie identyfikatorem obiektu do zaktualizowania. Zaczniemy od uzupełnienia pliku urls.py:

Kod nr
38
39
40
41
    url(r'^edytuj/(?P<pk>\d+)/', login_required(
        views.EdytujWiadomosc.as_view(),
        login_url='/loguj'),
        name='edytuj'),

Nowością w powyższym kodzie są wyrażenia regularne definiujące adresy z dodatkowym parametrem, np. r'^edytuj/(?P<pk>\d+)/'. Część /(?P<pk>\d+) oznacza, że oczekujemy 1 lub więcej cyfr (\d+), które zostaną zapisane w zmiennej o nazwie pk (?P<pk>) – nazwa jest tu skrótem od ang. wyrażenia primary key, co znaczy “klucz główny”. Zmienna ta zawierać będzie identyfikator wiadomości i dostępna będzie w klasie widoku, który obsłuży edycję wiadomości.

Na początku pliku views.py importujemy więc potrzebny widok:

Kod nr
10
from django.views.generic.edit import UpdateView

Dalej tworzymy klasę EdytujWiadomosc, która dziedziczy, czyli dostosowuje wbudowany widok UpdateView:

Kod nr
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class EdytujWiadomosc(UpdateView):
    model = Wiadomosc
    from czat.forms import EdytujWiadomoscForm
    form_class = EdytujWiadomoscForm
    context_object_name = 'wiadomosci'
    template_name = 'czat/wiadomosc_form.html'
    success_url = '/wiadomosci'

    def get_context_data(self, **kwargs):
        context = super(EdytujWiadomosc, self).get_context_data(**kwargs)
        context['wiadomosci'] = Wiadomosc.objects.filter(
            autor=self.request.user)
        return context

    def get_object(self, queryset=None):
        wiadomosc = Wiadomosc.objects.get(id=self.kwargs['pk'])
        return wiadomosc

Najważniejsza jest tu metoda get_object(), która pobiera i zwraca wskazaną przez identyfikator w zmiennej pk wiadomość: wiadomosc = Wiadomosc.objects.get(id=self.kwargs['pk']). Omawianą już metodę get_context_data() wykorzystujemy, aby przekazać do szablonu listę wiadomości, ale tylko zalogowanego użytkownika (context['wiadomosci'] = Wiadomosc.objects.filter(autor=self.request.user)).

Właściwości model, context_object_name, template_name i success_url wyjaśniliśmy wcześniej. Jak widać, do edycji wiadomości można wykorzystać ten sam szablon, którego użyliśmy podczas dodawania.

Formularz jednak dostosujemy. Wykorzystamy właściwość form_class, której przypisujemy utworzoną w nowym pliku forms.py klasę zmieniającą domyślne ustawienia:

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

from django.forms import ModelForm, TextInput
from czat.models import Wiadomosc


class EdytujWiadomoscForm(ModelForm):
    class Meta:
        model = Wiadomosc
        fields = ['tekst', 'data_pub']
        exclude = ['autor']
        widgets = {'tekst': TextInput(attrs={'size': 60})}

Klasa EdytujWiadomoscForm oparta jest na wbudowanej klasie ModelForm. Właściwości formularza określamy w podklasie Meta:

  • model – oznacza to samo co w widokach, czyli model, dla którego tworzony jest formularz;
  • fields – to samo co w widokach, lista pól do wyświetlenia;
  • exclude – opcjonalnie lista pól do pominięcia;
  • widgets – słownik, którego klucze oznaczają pola danych, a ich wartości odpowiadające im w formularzach HTML typy pól i ich właściwości, np. rozmiar.

Żeby przetestować aktualizowanie wiadomości, w szablonie wiadomosc_list.html trzeba wygenerować linki Edytuj dla wiadomości utworzonych przez zalogowanego użytkownika. Wstaw w odpowiednie miejsce szablonu, tzn po tagu wyświetlającym tekst wiadomości ({{ wiadomosc.tekst }}) poniższy kod:

Plik wiadomosc_lista.html nr
12
13
14
        {% if wiadomosc.autor.username == user.username %}
          &bull; <a href="{% url 'czat:edytuj' wiadomosc.id %}">Edytuj</a>
        {% endif %}

Ćwiczenie: Ten sam link “Edytuj” umieść również w szablonie dodawania.

../../_images/czatpro2_06.png
../../_images/czatpro2_06a.png

7.5.6. Usuwanie wiadomości

Usuwanie danych realizujemy za pomocą widoku DeleteView, który importujemy na początku pliku urls.py:

Kod nr
13
from django.views.generic import DeleteView

Podobnie, jak w przypadku edycji, usuwanie powiążemy z adresem URL zawierającym identyfikator wiadomości */usun/id_wiadomości*. W pliku urls.py dopisujemy:

Kod nr
42
43
44
45
46
47
48
    url(r'^usun/(?P<pk>\d+)/', login_required(
        DeleteView.as_view(
            model=Wiadomosc,
            template_name='czat/wiadomosc_usun.html',
            success_url='/wiadomosci'),
        login_url='/loguj'),
        name='usun'),

Warto zwrócić uwagę, że podobnie jak w przypadku listy wiadomości, o ile wystarcza nam domyślna funkcjonalność widoku wbudowanego, nie musimy niczego implementować w pliku views.py.

Domyślny szablon dla tego widoku przyjmuje nazwę <nazwa-modelu>_confirm_delete.html, dlatego uprościliśmy jego nazwę we właściwości template_name. Tworzymy więc plik wiadomosc_usun.html:

Plik wiadomosc_usun.html. Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- czatpro2/czat/templates/czat/wiadomosc_usun.html -->
<html>
  <body>
    <h1>Wiadomości</h1>

    <h2>Usuń wiadomość</h2>
    <form method="POST">
      {% csrf_token %}
      <p>Czy na pewno chcesz usunąć wiadomość:<br /><i>{{ object }}</i>?</p>
      <button type="submit">Usuń</button>
    </form>

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

Tag {{ object }} zostanie zastąpiony treścią wiadomości zwróconą przez funkcję “autoprezentacji” __unicode__() modelu.

Ćwiczenie: Wstaw link “Usuń” (&bull; <a href="{% url 'czat:usun' wiadomosc.id %}">Usuń</a>) za linkiem “Edytuj” w szablonach wyświetlających listę wiadomości.

../../_images/czatpro2_07.png
../../_images/czatpro2_07a.png

7.5.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-09-08 o 19:38 w Sphinx 1.4.5
Autorzy:Patrz plik “Autorzy”