Django: Autentizace a autorizace

Občas potřebujeme nějaký obsah zpřístupnit pouze určité sortě návštěvníků. Abychom návštěvníky odlišili, musí se autentizovat pomocí identifikačních údajů, což může být například uživatelského jméno a heslo. Django má v sobě zabudovanou podporu takovéto autentizace a také máme možnost autorizovat přístup pomocí přístupových práv a skupin.
Seriál: Hrajeme si s Djangem (16 dílů)
- Django: Úvod a instalace 14. 8. 2009
- Django: Nastavení projektu a první pokusy 21. 8. 2009
- Django: Databázový model 28. 8. 2009
- Django: Databázový model podruhé 4. 9. 2009
- Django: Administrace 11. 9. 2009
- Django: Prezentace dat 18. 9. 2009
- Django: Prezentace dat podruhé 25. 9. 2009
- Django: Zpracovávání formulářů 2. 10. 2009
- Django: Autentizace a autorizace 9. 10. 2009
- Django: Nahrávání souborů 16. 10. 2009
- Django: Zabudované aplikace 23. 10. 2009
- Django: Rozšiřování možností Djanga 30. 10. 2009
- Django: Internacionalizace 6. 11. 2009
- Django: Nasazování projektu 13. 11. 2009
- Django: Kešování a škálování 20. 11. 2009
- Django: Závěr 27. 11. 2009
Na začátek si neodpustím terminologickou poznámku: autentizace (někdy psáno též jako autentikace, případně autentifikace) je proces ověření identity uživatele. Pomocí ní tedy zjistíme, že u počítač pravděpodobně sedí Alice a ne třeba Bob, protože je aktuální návštěvník přihlášen pomocí Aliciných uživatelských údajů. Autorizace se oproti tomu stará o kontrolu oprávnění, tedy aby přístup měli jenom ti uživatelé, kterým jsme ho povolili.
Modul django.contrib.auth
Aplikaci zajišťující autentizaci a autorizaci můžeme nalézt v modulu Djanga django.contrib.auth
. Hlavní součástí je model User
, který představuje přihlášeného uživatele. Nepřihlášení uživatelé jsou reprezentováni odvozeným modelem AnonymousUser
. Ať už je uživatel přihlášen nebo ne, můžeme k jeho modelu přistupovat v pohledech pomocí objektu request.user
a v šablonách přes proměnnou {{ user }}
. Instance modelu uživatele obsahuje několik zajímavých atributů:
user.username
: uživatelské jménouser.first_name
: křestní jménouser.last_name
: příjmeníuser.email
: e-mailuser.is_staff
: příznak, zda má uživatel povolený vstup do administraceuser.is_active
: příznak, zda je uživatel aktivní
Kromě atributů máme k dispozici několik důležitých metod:
user.is_authenticated()
: zkontroluje, zda je uživatel přihlášenuser.get_full_name()
: celé jméno uživateleuser.set_password()
: nastaví uživatelovo heslouser.check_password()
: ověří, jestli se zadané heslo shoduje s heslem uživatele
Toto není vyčerpávající seznam, popis mnoha dalších atributů a metod lze najít v dokumentaci a některé si ještě představíme později.
Protože je uživatel reprezentován modelem, zacházíme s ním podobně jako s ostatními databázovými modely. Můžeme přidávat nové uživatelské účty, odstraňovat je a upravovat uživatelské údaje. Vytváření nového uživatelského účtu nám může usnadnit funkce create_user
:
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('honza', 'honza@example.com', 'honzovoheslo')
>>> user.first_name = 'Jan'
>>> user.last_name = 'Novák'
>>> user.save()
>>> user
<User: honza>
>>> user.is_staff
False
>>> user.password
'sha1$32f0e$c445ff000ed85dff88f32f874d7021bee2fe1cc4'
>>> user.check_password('honzovoheslo')
True
>>> user.check_password('jinéheslo')
False
set_password
nebo check_password
.Přihlašování a odhlašování
Identita návštěvníka se dá ověřit pomocí funkce authenticate
, která vrátí objekt uživatele v případě úspěšného ověření nebo prázdný objekt pokud se ověření nezdaří. Poté uživatele můžeme přihlásit pomocí metody login
a kdykoliv později odhlásit pomocí metody logout
. Místo toho, abychom se pustili do vytváření pohledů pro přihlašování a odhlašování, využijeme generické pohledy, které byly pro tento účel vytvořeny. Otevřeme si tedy soubor urls.py
a připíšeme na konec několik řádků kódu:
urlpatterns += patterns('', # ... (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), (r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'template_name': 'logout.html'}), )
Cesty /accounts/login/
a /accounts/logout/
jsou výchozí a dají se přenastavit v souboru settings.py
pomocí konstant LOGIN_URL a LOGOUT_URL. Klíčem template_name
jsem si vybral jiné umístění šablony, ve výchozím nastavení jsou umístěny v podadresáři registration
. Šablona pro přihlašování (soubor templates/login.html
) může vypadat třeba takto:
{% extends "base.html" %} {% block content %} <h1>{% block title %}Přihlášení{% endblock %}</h1> {% if form.errors %} <p>Špatně zadané uživatelské jméno nebo heslo. Zkuste se prosím přihlásit znovu.</p> {% endif %} <form action="." method="POST"> <table> {% include "form.html" %} <tr> <td></td> <td> <input type="submit" value="Přihlásit se"> <input type="hidden" name="next" value="{{ next }}"> </td> </tr> </table> </form> {% endblock %}
V případě neúspěšného přihlášení vypíšeme na sedmém řádku chybovou hlášku. Na dvanáctém řádku je využita šablona pro výpis formuláře z minulého dílu. Sedmnáctý řádek obsahuje skrytou hodnotu formuláře next
, což je adresa stránky, na kterou budeme po přihlášení přesměrováni. Pokud není hodnota next
zadána, standardně se přejde na /accounts/profile/
(dá se přenastavit pomocí konstanty LOGIN_REDIRECT_URL).
Odhlašovací šablona (templates/logout.html
) bude oproti té přihlašovací jednodušší:
{% extends "base.html" %} {% block content %} <h1>{% block title %}Odhlášeno{% endblock %}</h1> <p>Byl(a) jste odhlášen(a). Nashle příště!</p> {% endblock %}
Uživatelský profil
Na začátku jsme se dozvěděli, že model uživatele obsahuje jeho jméno, příjmení a e-mail. Ale co když chceme k uživatelskému účtu připojit dodatečné informace? Stačí rozšířit jeho model pomocí tzv. profilu. Otevřeme si soubor video_store/models.py
a přidáme tento kód:
from django.contrib.auth.models import User class Profile(models.Model): user = models.OneToOneField(User, verbose_name='Uživatel') info = models.TextField('Informace', blank=True) def __unicode__(self): return self.user.username class Meta: verbose_name = 'profil' verbose_name_plural = 'profily'
Založili jsme si nový model, který k modelu uživatele připojí textové pole, kam můžeme poznamenat jakékoliv další údaje. Na čtvrtém řádku je použit atribut typu OneToOneField
, což je obdoba unikátního cizího klíče (ForeignKey
s parametrem unique=True
). Takováto vazba nám zaručí, že jeden uživatel bude mít právě jeden profil (ne více).
Když máme definovaný model, sesynchronizujeme ho s databází (python manage.py syncdb
) a vytvoříme pro něj formulář. V tomto případě ho zčásti můžeme vygenerovat. Otevřeme si soubor video_store/forms.py
a vložíme do něj následující kód:
from django.contrib.auth.models import User class ProfileForm(forms.ModelForm): info = forms.CharField(label='Informace', widget=forms.Textarea, required=False) password1 = forms.CharField(label='Heslo', widget=forms.PasswordInput, required=False) password2 = forms.CharField(label='Heslo znovu', widget=forms.PasswordInput, required=False) class Meta: model = User fields = ('first_name', 'last_name', 'email') def clean_password2(self): if self.cleaned_data.get('password1') != self.cleaned_data['password2']: raise forms.ValidationError('Zadaná hesla se liší.') return self.cleaned_data['password2']
Můžeme zde vidět dvě nové věci. První je, že formulář neodvozujeme od třídy forms.Form
, ale forms.ModelForm
. Tato třída slouží pro generování formulářů z modelů. V podtřídě Meta
specifikujeme atributem model
, ze kterého modelu se má formulář generovat. Pokud neuvedeme výčet atributů modelu, které se mají použít, pomocí meta atributů fields
nebo exclude
, jsou pro generování použity všechny atributy. Druhá nová věc je metoda clean_password2
. Máme možnost si definovat vlastní validaci určitého formulářového políčka pomocí metody pojmenované clean_políčko
. Pokud uživatelský vstup nevyhovuje, vyhodí se výjimka forms.ValidationError
a u daného políčka se vypíše uživateli chybová hláška. V tomto případě kontrolujeme, zda jsou zadaná hesla stejná.
Ještě musíme provázat uživatelský model s profilem. Na to má Django vyhrazenou konstantu AUTH_PROFILE_MODULE
, která se tradičně nachází v souboru settings.py
. Stačí do něj připsat tento řádek:
AUTH_PROFILE_MODULE = 'video_store.Profile'
Jakmile má tato konstanta přiřazenou cestu k našemu modelu, stačí u objektu uživatele zavolat metodu get_profile
. Tato metoda vrací profil daného uživatele nebo vyvolává výjimku Profile.DoesNotExist
. Vytvoříme si v souboru video_store/views.py
pohled, ve kterém využijeme tohoto propojení:
from models import Profile from forms import ProfileForm from django.contrib.auth.decorators import login_required @login_required def profile(request, saved): try: profile = request.user.get_profile() except Profile.DoesNotExist: profile = Profile(user=request.user) if request.method == 'POST': form = ProfileForm(request.POST) if form.is_valid(): request.user.first_name = form.cleaned_data['first_name'] request.user.last_name = form.cleaned_data['last_name'] request.user.email = form.cleaned_data['email'] if len(form.cleaned_data['password1']) > 0: request.user.set_password(form.cleaned_data['password1']) request.user.save() profile.info = form.cleaned_data['info'] profile.save() return HttpResponseRedirect('/accounts/profile/saved/') else: form = ProfileForm(initial={'first_name': request.user.first_name, 'last_name': request.user.last_name, 'email': request.user.email, 'info': profile.info, }) return render_to_response('profile.html', {'form': form, 'saved': saved}, context_instance=RequestContext(request))
Před samotnou definicí pohledu je podivný kód @login_required
, kterému se říká dekorátor. Je to vlastně funkce, která obalí jinou funkci, jenom je zde použit zkrácený zápis, který se používá v Pythonu 2.4 a novějším. Tento dekorátor zamezí přístup všem nepřihlášeným uživatelům a přesměruje je na stránku s přihlášením. V samotném pohledu zavoláme již zmíněnou metodu get_profile
(řádek 8) a v případě, že uživatel nemá přiřazený profil, založíme mu nový (řádek 10). Do databáze ukládáme zvlášť objekt uživatele a zvlášť jeho profil. Pokud uživatel vyplnil kolonku s heslem (řádek 20), změníme mu ho. Všimněte si, že zobrazený formulář (řádek 30) má některá políčka již předvyplněná.
K pohledu je potřeba vytvořit odpovídající šablonu. V ní stačí zobrazit formulář a v případě, že došlo k jeho úspěšnému uložení, vypsat potvrzující hlášku. Šablonu uložíme do souboru templates/profile.html
:
{% extends "base.html" %} {% block content %} <h1>{% block title %}Uživatelský profil{% endblock %}</h1> {% if saved %} <p>Profil byl upraven.</p> {% endif %} <form action="." method="POST"> <table> {% include "form.html" %} <tr> <td></td> <td><input type="submit" value="Upravit"></td> </tr> </table> </form> {% endblock %}
A nakonec si můžeme v souboru templates/base.html
rozšířit část <div id="menu">
o zobrazování informací o aktuálně přihlášeném uživateli:
{% if user.is_authenticated %} <ul id="user"> <li>Přihlášen: <a href="/accounts/profile/">{{ user }}</a></li> {% if user.is_staff %} <li><a href="/admin/">Administrace</a></li> {% endif %} <li><a href="/accounts/logout/">Odhlásit</a></li> </ul> {% endif %}
Související odkazy
- Autentizace na Djangoproject.com
- Čtrnáctá kapitola v The Definitive Guide to Django
- Ukázkový příklad ke stažení.
V dalším díle se budeme zabývat nahráváním souborů do aplikace.
Nebylo by lepsi pouzit pro uzivatelsky profil dedicnost modelu? Mam s tim lepsi zkusenosti, nezli s vazbou pres OneToOneField(). S danym modelem se pak jednoduseji pracuje. Priklad:
from django.contrib.auth.models import User
from django.contrib.auth.models import UserManager
class Profile(AuthUser):
info = models.TextField(_(‚Informace‘), blank=True)
objects = UserManager()
Je to také možné, ale tohle je doporučené řešení. Je to hlavně z toho důvodu, že
request.user
je objektUser
, neProfile
, takže bychom museli přepisovat autentizační rozhraní.V případě šikovného použití dědičnosti nikoliv. V čem mám v Djangu s dědičností modelů problém je vazba přez GenericForeignKey, tam mi to trochu drhlo, ale vyřešilo se to také docela snadno.
Je ale pravda, že zrovna uživatelský profil je radno dělat v Djangu tak jak v článku, v podstatě to už je „konvence“.
Uvedeny kus kodu vyhodi vyjimku pokud pro password1 neprojde validace – nebude naplnen v cleaned_data a dostanete KeyError.
Bylo by take pekne nepouzivat v prikladech (obzvlast pro zacatecniky) natvrdo zadane URL adresy, ale pouzit tag {% url %} a metodu reverse ve views.
ModelForm neni jen trida ke generovani formularu, obsahuje hlavne logiku, ktera umoznuje i praci s modely – napriklad neni nutne pracne konstruovat initial argument, ale lze do formulare rovnou predat instanci pres parametr instance. Zavolani .save() na tom formulari pak provede patricny update toho modelu aniz byste se museli starat o rucni pridelovani jednotlivych fieldu (bez predani instance do __init__ .save() vytvori novou instanci modelu).
Pardon, prvni bod neplati, priklad je napsany spravne, moc se omlouvam, prekoukl jsem se.
Díky za připomínky. Napadlo mě, že bych poslední díl seriálu mohl věnovat tipům a trikům, jak si usnadnit práci. Rozhodně tam zahrnu i značku
{% url %}
a předávání modelu do formuláře a naopak.Mozna byste mohl v dalsim dile udelat navod na rest hesla. Jedna se o lehky proces, ktery vsak neni nikde poradne popsan.
Tohle je naprosto jednoduché, stačí použít generický pohled password_reset.