Poprzedni wykład     Lista wykładów     Następny wykład

Rejestracja i logowanie

Django zawiera moduł zarządzania użytkownikami, jeżeli zachowaliśmy domyślne zapisy w pliku settings.py, to moduł ten jest dostępny dla naszej aplikacji.

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'pensjonat.bookings'
)

Administrowanie aplikacją "Dziedziczenie" stron Logowanie Strona rejestracji

Administrowanie aplikacją

Django zawiera moduł administrowania aplikacją. Domyślnie moduł ten nie jest zainstalowany. Chociaż nie będziemy z niego korzystać, to opiszę co trzeba zrobić by był on dostępny. Strona administratora po pomyślnym logowaniu i po dopisaniu przez administratora grupy o nazwie admini wygląda tak:

Spis treści

"Dziedziczenie" stron

Django dostarcza wygodny mechanizm "dziedziczenia" stron pozwalający uniknąć powtarzania kodu. Ogólny schemat jest taki: Przerobimy teraz na mechanizm dziedziczenia (i trochę rozbudujemy) stronę główną tworzonej aplikacji.
Strona bazowa znajduje się w pliku templates/base.html i wygląda tak:
<!doctype html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <meta content="text/html; charset=utf-8" http-equiv="content-type">
    <title>Pensjonat "Pod kwiatami" {% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="/media/style.css" type="text/css" />
    <script type="text/javascript" src="/media/jquery.js"></script>
    {% block external %}{% endblock %}
  </head>
  <body>
    {% block header %}{% endblock %}
    {% block content %}{% endblock %}
    {% block footer %}{% endblock %}
  </body>
</html>
Strona bazowa zawiera pięć bloków (title, external, header, content i footer). Bloki te mogą być opisane na stronach pochodnych, ale nie muszą. Wiersz <script type="text/javascript" src="media/jquery.js"></script> pozwala każdej stronie pochodnej na korzystanie z z biblioteki jQuery. Dzięki tej bibliotece można uzyskać interesujące efekty animacji, dodać dynamiczne zmiany strony, wykonać zapytania AJAX.

Plik main_page.html wykorzystujący dziedziczenie wygląda tak:

{% extends "base.html"%}
{% block header %}
  <div id="nav">
    {% if user.is_authenticated %}
      Zalogowany użytkownik: <b>{{user.name}}</b>    <a href="/logout/">wyloguj się</a>    
    {% else %}
      <a href="/login/">zaloguj się</a>    <a href="/register/">rejestracja</a>
    {% endif %}
  </div>
{% endblock %}
{% block content %}
<p>
<div style="text-align:center;">
<h2>Strona domowa pensjonatu "Pod kwiatami"</h2>
<div><img src="media/bamberg.png" /></div>
<p>{{podpis}}</p></div>
<center>
<h2>Lista apartamentów</h2>
<table border="0px" width="70%">
  {% for apartment in apartments %}
  <tr style="text-align:center;">
    <td style="text-align:left;font-size:14pt;">
       <p class="br">{{apartment.name}}
       <p class="br">Sypialnie: {{apartment.bedrooms}}
       <p class="br">Łóżka: {{apartment.beds}}
       <p class="br">{% if apartment.kitchenette %} Posiada aneks kuchenny {% else %} Nie posiada aneksu {% endif %}
       <p class="br"><a href="">Cennik</a>
       <p class="br"><a href="">Rezerwacja</a>
    </td>
    <td width="50%">{% if apartment.pathToPicture %} <img src="media/{{apartment.pathToPicture}}" width="600px" />{% endif %}
    </td>
  </tr>
  {% endfor %}
</table>
</center>
{% endblock %}
{% block footer %}
<div style="text-align:center;">
<a href="Galery.html"><img class="middle" src="media/back.gif"/>Galeria</a>
</div>
{% endblock %}
Blok external nie został opisany, będzie więc na stronie wynikowej pusty.
Warto zwrócić uwagę na warunek if user.is_authenticated. Na obecnym rozwoju naszej aplikacji, zmienna user nie istnieje, a mimo to nie ma błędu wykonania. Warunek if zm jest interpretowany tak: jeżeli zmienna zm istnieje i jest "niepusta", to coś zrób.

Spis treści

Strona logowania

Strona główna pensjonatu jest ogólnie dostępna. Rezerwacji natomiast mogą dokonywać tylko zarejestrowane i zalogowane osoby. Mechanizm rejestracji opiszemy póżniej. Obecnie istnieje najwyżej jeden zarejestrowany uzykownik (superuser) Django dostarcza wygodny mechanizm logowania, jeżeli chcemy z niego skorzystać to musimy:

Mapowanie (r'^logout/$',logout_page), to wywołanie funkcji logout_page znajdującej sie w pliku views.py:

...
from django.contrib.auth import logout
...
def logout_page(request):
    logout(request)
    return HttpResponseRedirect("/")
Po wylogowaniu użytkownik wraca na stronę główną.

Mapowanie (r'^login/$','django.contrib.auth.views.login'), to wywołanie standardowej funkcji login zawartej w pakiecie django.contrib.auth.views. Funkcja ta wymaga by istniał plik templates/registration/login.html, przekazuje do niego domyślny formularz logowania i oczekuje na informację gdzie przekierować zalogowanego użytkownika.
Plik login.html:

{% extends "base.html"%}
{% block title %} - logowanie{% endblock %}
{% block content%}
<center>
<h2>Logowanie na stronę pensjonatu "Pod kwiatami"</h2>
Logowanie jest wymagane, jeżeli chcesz rezerwować noclegi.
<p>
<div style="margin-left:35%;margin-right:35%;">
<fieldset>
    <form method="post" action=".">{% csrf_token %}
    <div style="text-align:left";>{{ form.as_p }}</div>
    <input type="hidden" name="next" value="/" />
    <input type="submit" value="Logowanie"/>    <input type="reset" value="Wartości domyślne"/>
  </form>
</fieldset>
</div>
</center>  
{% endblock %}
{% block footer %}
<p>
<div style="text-align:center;"><a href="/">Strona główna</a></div>
</p>
{% endblock %}
Blok {{ form.as_p }}, to polecenie by wyświetlić każdą parę (etykieta, pole) formularza jako akapit (paragraf) (<p>etykieta pole</p>). Ukryte pole name="next" value="/", to żądanie by zalogowanego użytkownika przekierować na główną stronę. Blok {% csrf_token %} nie jest wymagany by strona logowania się wyświetliła, jest konieczny by wysłanie formularza nie skończyło sie błędem 403, blok ten chroni przed atakiem CSRF.

Jeżeli nie korzystamy z domyślnego mechanizmu logowania, to czeka nas więcej pracy.

Spis treści

Strona rejestracji

Utworzymy specjalną stronę do rejestracji, na stronie umieścimy formularz pobierający dane od użytkownika i wysyłający je na serwer, który będzie sprawdzał poprawność wprowadzonych danych.

Wartość parametru action = "." w pliku register.html powoduje, że wysłanie formularza wywołuje ponownie funkcję register_page, nie istnieje zatem możliwość zarejestrowania się.
Nim rozbudujemy funkcję register_page, to sprawdzimy w konsoli możliwości formularzy w Django. Uruchamiamy konsolę:
python manage.py shell
Wpisujemy polecenia:
from bookings.forms import *
form = FormularzRejestracji({"username":"jasiu","email":"aa@bb.cc","password1":"miod","password2":"miodek"})
form.is_valid() => True
Jak widać wbudowana w Django kontrola poprawności nie wymaga by oba hasła były identyczne.
form = FormularzRejestracji({"username":"","email":"aa.bb.cc","password1":"miod","password2":"miodek"})
form.is_valid() => False
form.errors => {'username': [u'This field is required.'], 'email': [u'Enter a valid e-mail address.']}
Sprawdzony został format adresu pocztowego oraz wypełnienie wymaganych pól. W tworzonej aplikacji wypełnienie pierwszych czterech pól jest wymagane. Pierwsza rozbudowa funkcji register_page:
# _*_ coding: utf-8 _*_
from django.http import HttpResponse,HttpResponseRedirect
from django.template.loader import get_template
from django.template import RequestContext
from bookings.models import Apartment
from bookings.forms import *

def main_page(request):
    ...

def register_page(request):
    if request.method == 'POST':
        form = FormularzRejestracji(request.POST)
        if form.is_valid():
            return HttpResponseRedirect("/")
    else:
        form = FormularzRejestracji()    
    template = get_template("register.html")
    variables = RequestContext(request,{'form':form})
    output = template.render(variables)
    return HttpResponse(output)
Obiekt request.POST jest słownikiem, zawiera informacje o wszystkich parametrach żądania POST. Jeżeli wchodzimy na stronę register.html poprzez wysłanie formularza (if request.method == 'POST') oraz domyślna funkcja is_valid() zwraca True (wymagane pola są wypełnione, adres emailowy ma poprawny format), to wracamy na główna stronę. W przeciwnym razie wracamy na stronę rejestracji, każde pole z błędami jest poprzedzane informacją o popełnionym błędzie.
    
Rozbudujemy teraz klasę FormularzRejestracji o dodatkowe kontrole: czy obie wersje hasła są identyczne i czy username jest nowy. Jeśli jest nowy to dopiszemy nowego użytkownika. W klasie FormularzRejestracji jest sześć pól: username, email, password1, password2, phone i log_on. Będziemy sprawdzać poprawność wypełnienia dwóch z nich: username i password2.
Musimy zatem dopisać do klasy FormularzRejestracji metody clean_password2 i clean_username.
Metoda clean_password2 jest prosta:
def clean_password2(self):
    password1=self.cleaned_data['password1']
    password2=self.cleaned_data['password2']
        if password1==password2:
            return password2
        else:    
            raise forms.ValidationError("Hasła się różnią")
Metoda nie wykonuje dodatkowego "oczyszczania", zatem zwraca to co odczytała ze słownika cleaned_data.
Metoda clean_username jest trochę bardziej skomplikowana. Musimy zdecydować jakie znaki są dopuszczalne w loginie. Nie musimy natomiast dodawać do bazy danych aplikacji tabeli użytkowników. Django tworzy automatycznie tabelę django.contrib.auth.models.User. Przy pomocy wyrażeń regularnych łatwo sprawdzić, czy wszystkie wprowadzone znaki są alfanumeryczne (cyfry i litery angielskie) lub są znakiem podkreślenia, zatem takie znaki dopuścimy. Plik forms.py z metodą clean_username wygląda zatem tak:
# _*_ coding: utf-8 _*_
import re
from django import forms
from bookings.models import User
from django.core.exceptions import ObjectDoesNotExist

class FormularzRejestracji(forms.Form):
...
     def clean_username(self):
        username = self.cleaned_data['username']
        if not re.search(r'^\w+$',username):
            raise forms.ValidationError("Dopuszczalne są tylko cyfry, litery angielskie i _")
        try:
            User.objects.get(username=username)
        except ObjectDoesNotExist:
            return username
        raise forms.ValidationError("Taki użytkownik już istnieje")
Dodane zostały trzy polecenia importu, w tym import re. Moduł re (re = regular expression) obsługuje wyrażenia regularne. Ostateczna wersja funkcji register_page w pliku views.py:

def register_page(request):
    if request.method == 'POST':
        form = FormularzRejestracji(request.POST)
        if form.is_valid():
            user = User.objects.create_user(
              username=form.cleaned_data['username'],
              password=form.cleaned_data['password1'],
              email=form.cleaned_data['email']
            )
            user.last_name = form.cleaned_data['phone']
            user.save()
            if form.cleaned_data['log_on']:
                user = authenticate(username=form.cleaned_data['username'],password=form.cleaned_data['password1'])
                login(request,user)
                template = get_template("main_page.html")
                variables = RequestContext(request,{'user':user})
                output = template.render(variables)
                return HttpResponseRedirect("/") 
            else:    
                template = get_template("registration/register_success.html")
                variables = RequestContext(request,{'username':form.cleaned_data['username']})
                output = template.render(variables)
                return HttpResponse(output)            
    else:
        form = FormularzRejestracji()
    template = get_template("registration/register.html")    
    variables = RequestContext(request,{'form':form})
    output = template.render(variables)
    return HttpResponse(output)
Jeżeli formularz rejestracji nie zawiera błędów, to dopisujemy do bazy nowego użytkownika. W zależności od wyboru użytkownika (pole log_on) wyświetlamy stronę register_success.html potwierdzającą rejestrację:
{% extends "base.html"%}
{% block title %} - rejestracja zakończona pomyśnie{% endblock %}
{% block header %}<h2><center>Zostałeś zarejestrowany, twój login to: {{username}}</center></h2>{% endblock %}
{% block content%}
 <center><h3>Dziękujemy za rejestrację. Możesz się teraz <a href="/login/">zalogować</a> lub wrócić na <a href="/">stronę głównę</a>.</h3></center>
{% endblock %}
lub wracamy na stronę główną, użytkownik jest zalogowany.

Tworzenie nowego użytkownika. Do każdej tabeli można dopisywać nowy rekord w następujący sposób:

nazwa_tabeli.objects.create(nazwa_pola1=wartosc1, nazwa_pola2=wartosc2,...)
(Dopisanie może sie nie udać, bo podane wartości pól mogą nie spełniać narzuconych ograniczeń constraints). W tabeli Users ten sposób jest niewłaściwy - do pola password zostanie wpisane niezaszyfrowane hasło. Musimy użyć metody create_user. Metoda ta ma tylko trzy argumenty (username, password i email). Dlatego dopisanie odbywa się w dwóch etapach:
user = User.objects.create_user(...) #obiektowy odpowiednik INSERT
user.last_name = form.cleaned_data['phone']
user.save()                          #obiektowy odpowiednik UPDATE
Domyślna tabela user nie zawiera żadnego pola do przechowywania numerów telefonicznych, dlatego wykorzystujemy pole last_name.
Spis treści
Poprzedni wykład     Lista wykładów     Następny wykład