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
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'rezerwacje.bookings', 'django.contrib.admin' )
from django.conf.urls.defaults import patterns, include, url import os.path from bookings.views import * from django.contrib import admin admin.autodiscover() media_dir = os.path.join(os.path.dirname(__file__),'media') urlpatterns = patterns('', (r'^$',main_page), (r'^media/(?P.*)$','django.views.static.serve',{'document_root': media_dir}), (r'^admin/$',include(admin.site.urls)) )
<!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.
urlpatterns = patterns('', (r'^$',main_page), (r'^media/(?POczywiście wiersz (r'^logout/$',logout_page) do samego logowania nie jest wymagany. Tym niemniej powinniśmy dać użytkownikowi wygodną metodę wylogowania..*)$','django.views.static.serve',{'document_root': media_dir}), (r'^login/$','django.contrib.auth.views.login'), (r'^logout/$',logout_page) )
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.
urlpatterns = patterns('', (r'^$',main_page), (r'^media/(?P.*)$','django.views.static.serve',{'document_root': media_dir}), (r'^login/$',login_page), (r'^logout/$',logout_page), )
# _*_ coding: utf-8 _*_ from django import forms from bookings.models import User from django.core.exceptions import ObjectDoesNotExist class FormularzLogowania(forms.Form): username = forms.CharField(label="Login:",max_length=30) password = forms.CharField(label="Hasło:",widget=forms.PasswordInput()) def clean_username(self): username = self.cleaned_data['username'] try: user = User.objects.get(username=username) return username except ObjectDoesNotExist: raise forms.ValidationError("Niepoprawny login") def clean_password(self): if self.cleaned_data.has_key('username'): username = self.cleaned_data['username'] password = self.cleaned_data['password'] user = User.objects.get(username=username) if user.check_password(password): return password raise forms.ValidationError("Niepoprawne hasło") raise forms.ValidationError("Niepoprawny login")Formularz dziedziczy po klasie forms, zawiera dwa elementy typu input do wprowadzania danych. Uwaga, domyślnie wymagane jest wypełnienie każdego pola. W formularzu logowania jest to naturalny wymóg, w opisanym dalej formularzu rejestracji już nie. By użytkownik mógł zostawić puste pole przekazujemy do konstruktora pola formularza dodatkowy parametr:
class FormularzRejestracji(forms.Form): ... phone = forms.CharField(label="Telefon",max_length=20,required=False)Metody clean_username oraz clean_password są częścią przyjętego w Django schematu walidacji danych. Jest on nastepujący:
# _*_ coding: utf-8 _*_ from django.http import HttpResponse,HttpResponseRedirect from django.template.loader import get_template from django.template import RequestContext from django.shortcuts import * from django.contrib.auth import login,authenticate,logout from bookings.models import Apartment,User from bookings.forms import * ... def login_page(request): if request.method == 'POST': form = FormularzLogowania(request.POST) if form.is_valid(): user = authenticate(username=form.cleaned_data['username'],password=form.cleaned_data['password']) login(request,user) template = get_template("main_page.html") variables = RequestContext(request,{'user':user}) output = template.render(variables) return HttpResponseRedirect("/") else: form = FormularzLogowania() template = get_template("registration/login.html") variables = RequestContext(request,{'form':form}) output = template.render(variables) return HttpResponse(output)Plik login.html zawiera tekst <form method="post" action=".">. W efekcie jeżeli użytkownik kliknie przycisk Zaloguj, to wróci na tę samą stronę. Sprawdzenie if request.method == 'POST': pozwala rozpoznać sposób wejścia na stronę logowania. Obiekt request.POST jest słownikiem, zawiera informacje o wszystkich parametrach żądania POST. Metoda form.is_valid() zwraca True tylko wtedy gdy formularz przeszedł pomyślnie walidację. W takim przypadku wywołujemy dwie funkcje standardowe:
MIDDLEWARE_CLASSES = ( ... 'django.contrib.sessions.middleware.SessionMiddleware', ... } ... INSTALLED_APPS = ( ... 'django.contrib.sessions', ... }W stworzonym poleceniem python django-admin.py startproject pensjonat pliku settings.py te zapisy są.
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.
urlpatterns = patterns('', (r'^$',main_page), (r'^register/$',register_page), (r'^media/(?P.*)$','django.views.static.serve',{'document_root': media_dir}), (r'^login/$','django.contrib.auth.views.login'), (r'^logout/$',logout_page), )
# _*_ coding: utf-8 _*_ from django import forms class FormularzRejestracji(forms.Form): username = forms.CharField(label="Login:",max_length=30) email = forms.EmailField(label="Email:") password1 = forms.CharField(label="Hasło:",widget=forms.PasswordInput()) password2 = forms.CharField(label="Powtórz hasło:",widget=forms.PasswordInput()) phone = forms.CharField(label="Telefon:",max_length=20,required=False) log_on = forms.BooleanField(label="Logowanie po rejestracji:",required=False)
{% extends "base.html"%} {% block title %} - rejestracja{% endblock %} {% block content %} <center> <h2>Rejestracja na stronie pensjonatu "Pod kwiatami"</h2> Rejetracja jest wymagana, jeżeli chcesz rezerwować noclegi. <p> <div style="margin-left:35%;margin-right:35%;"> <fieldset> <legend> Wszystkie pola oprócz numeru telefonu należy wypełnić </legend> <form method="post" action=".">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Rejestracja"> <input type="reset" value="Wartości początkowe"> </form> </fieldset> </div> </center> {% endblock %} {% block footer %} <p> <div style="text-align:center;"><a href="/">Strona główna</a></div> </p> {% endblock %}Formularz (o nazwie form) zostanie przekazany do funkcji renderujacej plik register.html za pośrednictwem funkcji register_page.
# _*_ coding: utf-8 _*_ from django.http import HttpResponse 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): template = get_template("register.html") form = FormularzRejestracji() variables = RequestContext(request,{'form':form}) output = template.render(variables) return HttpResponse(output)
python manage.py shellWpisujemy polecenia:
from bookings.forms import * form = FormularzRejestracji({"username":"jasiu","email":"aa@bb.cc","password1":"miod","password2":"miodek"}) form.is_valid() => TrueJak 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.
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.
# _*_ 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.
SELECT * FROM User WHERE username = @usernameBrak takiego użytkownika jest dobrą informacją.
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 UPDATEDomyślna tabela user nie zawiera żadnego pola do przechowywania numerów telefonicznych, dlatego wykorzystujemy pole last_name.