Skip to content

Commit b085654

Browse files
committed
Complete existent documentation on some sections
1 parent b26bb9f commit b085654

File tree

5 files changed

+296
-140
lines changed

5 files changed

+296
-140
lines changed

docs/third-party/webdev/django/auth.md

Lines changed: 127 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ icon: material/key-chain-variant
66

77
<span class="djversion intermediate">:simple-django: Intermedio :material-tag-multiple-outline:</span>
88

9-
Django proporciona un [sistema de autenticación](https://docs.djangoproject.com/en/stable/topics/auth/default/) incorporado en el propio framework — que facilita en gran medida la gestión de accesos y de usuarios.
9+
Django proporciona un [sistema de autenticación](https://docs.djangoproject.com/en/stable/topics/auth/default/) —incorporado en el propio framework— que facilita en gran medida la gestión de accesos y de usuarios.
1010

1111
## Usuario { #user }
1212

@@ -20,6 +20,17 @@ Existe un modelo [`User`](https://docs.djangoproject.com/en/stable/ref/contrib/a
2020
| [`last_name`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django.contrib.auth.models.User.last_name) | Apellido(s) |
2121
| [`email`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django.contrib.auth.models.User.last_name) | Correo electrónico |
2222

23+
??? tip "Password"
24+
25+
El **password** se almacena [«hasheado»](https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-management-in-django) en la base de datos. Django utiliza —por defecto— el algoritmo [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) con una función «hash» [SHA256](https://es.wikipedia.org/wiki/SHA-2), aunque se podría cambiar. El formato utilizado es: `<algorithm>$<iterations>$<salt>$<hash>`
26+
27+
Un <span class="example">ejemplo:material-flash:</span> de «password» en la base de datos sería:
28+
29+
```pycon
30+
>>> user.password
31+
'pbkdf2_sha256$1000000$Y9opN6ZKNLMm9E1vAPrBzi$dNDnlbmN+phNora6ZUnok05NTH7BEgWjpg/mqulk3Yw='
32+
```
33+
2334
### Acceso al modelo { #user-model }
2435

2536
Este modelo se encuentra en la clase `#!python django.contrib.auth.models.User`, pero es posible «suplantarla» con un modelo propio de usuario. Es por ello que ^^no se recomienda^^ acceder directamente a esta clase a través de un `import` sino utilizar otros «atajos» más genéricos.
@@ -28,25 +39,74 @@ Para **acceder al modelo de usuario** disponemos de dos vías:
2839

2940
| Para uso en... | Importación | Acceso | Descripción | Valor por defecto |
3041
| --- | --- | --- | --- | --- |
31-
| Claves ajenas | `#!python from django.conf import settings` | `settings.AUTH_USER_MODEL` | Cadena de texto cualificada | `#!python 'auth.User'` |
32-
| Vistas y/o formularios | `#!python from django.contrib.auth import get_user_model` | `get_user_model()` | Función que devuelve el modelo | `django.contrib.auth.models.User` |
42+
| [Claves ajenas](models.md#foreign-keys) | `#!python from django.conf import settings` | `settings.AUTH_USER_MODEL` | Cadena de texto cualificada | `#!python 'auth.User'` |
43+
| [Vistas](views.md) y/o [formularios](forms.md) | `#!python from django.contrib.auth import get_user_model` | `get_user_model()` | Función que devuelve el modelo | `django.contrib.auth.models.User` |
3344

3445
### Acceso a la instancia { #user-instance }
3546

3647
Para **acceder a la instancia del usuario logeado** en Django disponemos de dos vías:
3748

3849
| Para uso en... | Acceso |
3950
| --- | --- |
40-
| Vistas | `request.user` |
41-
| Plantillas | `#!htmldjango {{ user }}` |
51+
| [Vistas](views.md) | `request.user` |
52+
| [Plantillas](templates.md) | `#!htmldjango {{ user }}` |
4253

43-
## Login { #login }
54+
Veamos un <span class="example">ejemplo:material-flash:</span> en el que accedemos a la instancia de un usuario:
55+
56+
```pycon
57+
>>> from django.contrib.auth import get_user_model
58+
>>> User = get_user_model()
59+
60+
>>> guido = User.objects.get(username='guido')
61+
62+
>>> guido
63+
<User: guido>
64+
>>> guido.first_name
65+
'Guido'
66+
>>> guido.last_name
67+
'van Rossum'
68+
```
69+
70+
!!! info "Usuario anónimo"
71+
72+
Cuando el usuario que interactúa con la web aún no está autenticado, Django lo identifica como [`AnonymousUser`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#anonymoususer-object).
4473

45-
Para implementar el procedimiento de **inicio de sesión** debemos llevar a cabo distintas tareas.
74+
## Aplicación { #app }
4675

47-
??? tip "accounts"
76+
Se recomienda [crear una aplicación](apps.md#creation) `accounts` (o similar) donde implementar todos los artefactos necesarios para la autenticación de usuarios.
4877

49-
Se recomienda crear una aplicación `accounts` donde implementar todos los artefactos necesarios de cara al inicio de sesión «login».
78+
En las URLs de primer nivel deberíamos incluir algo así:
79+
80+
```python title="main/urls.py" hl_lines="7"
81+
from django.contrib import admin
82+
from django.urls import include, path
83+
84+
urlpatterns = [
85+
path('admin/', admin.site.urls),
86+
path('posts/', include('posts.urls')),
87+
path('', include('accounts.urls')),
88+
]
89+
```
90+
91+
De tal forma que tendremos acceso «directo» a las funcionalidades de autenticación desde la raíz de la URL:
92+
93+
- `/login/`
94+
- `/logout/`
95+
- `/signup/`
96+
97+
!!! warning "Nombre"
98+
99+
No podemos llamar `auth` a esta aplicación porque entraría en conflicto con la aplicación [`django.contrib.auth`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django-contrib-auth) de Django.
100+
101+
## Login { #login }
102+
103+
Para implementar el procedimiento de **inicio de sesión** debemos desarrollar varios elementos:
104+
105+
- [x] Formulario.
106+
- [x] Plantilla.
107+
- [x] Vista.
108+
- [x] URL.
109+
- [x] Enlace.
50110

51111
### Formulario de login { #login-form }
52112

@@ -56,13 +116,16 @@ from django import forms
56116

57117
class LoginForm(forms.Form):
58118
username = forms.CharField()
59-
password = forms.CharField(widget=forms.PasswordInput)
119+
password = forms.CharField(widget=forms.PasswordInput)#(1)!
60120
```
121+
{ .annotate }
122+
123+
1. El [«widget»](forms.md/#widgets) nos permite cambiar el elemento HTML al renderizar el campo de formulario.
61124

62125
### Plantilla de login { #login-template }
63126

64127
```htmldjango title="accounts/templates/accounts/login.html"
65-
<form method="post" action="{% url 'login' %}">
128+
<form method="post">
66129
{% csrf_token %}
67130
{{ form }}
68131
<input type="submit" value="Login">
@@ -78,58 +141,49 @@ from django.urls import reverse
78141

79142
from .forms import LoginForm
80143

81-
82144
def user_login(request):#(1)!
83-
FALLBACK_REDIRECT = reverse('index')
145+
FALLBACK_REDIRECT = 'index'#(2)!
84146

85-
if request.user.is_authenticated:#(2)!
86-
return redirect(FALLBACK_REDIRECT)
147+
if request.user.is_authenticated:#(3)!
148+
return redirect(reverse(FALLBACK_REDIRECT))
87149
if request.method == 'POST':
88-
if (form := LoginForm(request.POST)).is_valid():#(3)!
150+
if (form := LoginForm(request.POST)).is_valid():#(4)!
89151
username = form.cleaned_data['username']
90152
password = form.cleaned_data['password']
91-
if user := authenticate(request, username=username, password=password):#(4)!
92-
login(request, user)#(5)!
93-
return redirect(request.GET.get('next', FALLBACK_REDIRECT))#(6)!
153+
if user := authenticate(request, username=username, password=password):#(5)!
154+
login(request, user)#(6)!
155+
return redirect(request.GET.get('next', reverse(FALLBACK_REDIRECT)))#(7)!
94156
else:
95-
form.add_error(None, 'Incorrect username or password.')#(7)!
157+
form.add_error(None, 'Incorrect username or password.')#(8)!
96158
else:
97159
form = LoginForm()
98-
return render(
99-
request,
100-
'accounts/login.html',
101-
dict(form=form),
102-
)
160+
return render( request, 'accounts/login.html', {'form': form})
103161
```
104162
{ .annotate }
105163

106164
1. No podemos llamar `login` a nuestra vista ya que entraría en conflicto con la función [`login`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.login) de Django.
107-
2. Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
108-
3. El formulario debe estar validado para poder continuar.
109-
4. La función [`authenticate`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.authenticate) de Django verifica si las credenciales de usuario son correctas. En tal caso devuelve el usuario en cuestión. En otro caso devuelve `#!python None`.
110-
5. La función [`login`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.login) de Django se encarga de «logear» (iniciar sesión) al usuario.
111-
6. Si todo ha ido bien redirigimos a la siguiente URL `next` (si es que existe) o a la URL por defecto `FALLBACK_REDIRECT`, en otro caso.
112-
7. - Añadimos un error al formulario indicando que las credenciales son incorrectas.
165+
2. Indica aquí un nombre de URL a la que redirigir (por defecto).
166+
3. Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
167+
4. El formulario debe estar validado para poder continuar.
168+
5. La función [`authenticate`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.authenticate) de Django verifica si las credenciales de usuario son correctas. En tal caso devuelve el usuario en cuestión. En otro caso devuelve `#!python None`.
169+
6. La función [`login`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.login) de Django se encarga de «logear» (iniciar sesión) al usuario.
170+
7. Si todo ha ido bien redirigimos a la siguiente URL `next` (si es que existe) o a la URL por defecto `FALLBACK_REDIRECT`, en otro caso.
171+
8. - Añadimos un error al formulario indicando que las credenciales son incorrectas.
113172
- El primer parámetro de la función `add_error()` es el campo al que queremos añadir el error, y el segundo parámetro es el mensaje de error en sí mismo.
114173
- Como estos errores son «generales» a todo el formulario, indicamos `#!python None` en el campo.
115174

116175
### URL de login { #login-url }
117176

118-
Debemos añadir en las [URLs de primer nivel](urls.md#main-urls) la ruta (y la vista) para gestionar el inicio de sesión:
119-
120-
```python title="main/urls.py"
177+
```python title="accounts/urls.py" hl_lines="6"
121178
from django.urls import path
122179

123-
import accounts.views
180+
from . import views
124181

125182
urlpatterns = [
126-
path('login/', accounts.views.user_login, name='login'),#(1)!
183+
path('login/', views.user_login, name='login'),
127184
]
128185
```
129-
{ .annotate }
130186

131-
1. - Puede ser interesante crear una aplicación `accounts` para «empaquetar» toda la lógica de negocio que tiene que ver con autenticación.
132-
- :fontawesome-solid-triangle-exclamation:{ .yellow } No podemos llamar `auth` a la aplicación porque entraría en conflicto con la aplicación [`django.contrib.auth`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django-contrib-auth) de Django.
133187

134188
!!! info "LOGIN_URL"
135189

@@ -157,52 +211,43 @@ En algún punto de nuestras plantillas deberemos añadir un enlace para iniciar
157211

158212
## Logout { #logout }
159213

160-
Para implementar el procedimiento de **cierre de sesión** debemos llevar a cabo distintas tareas.
161-
162-
??? tip "accounts"
163-
164-
Se recomienda crear una aplicación `accounts` donde implementar todos los artefactos necesarios de cara al cierre de sesión «logout».
165-
166-
### Formulario de logout { #logout-form }
167-
168-
No es necesario implementar un formulario para cierre de sesión.
214+
Para implementar el procedimiento de **cierre de sesión** debemos desarrollar varios elementos:
169215

170-
### Plantilla de logout { #logout-template }
171-
172-
No es necesario implementar una plantilla para cierre de sesión.
216+
- [ ] Formulario.
217+
- [ ] Plantilla.
218+
- [x] Vista.
219+
- [x] URL.
220+
- [x] Enlace.
173221

174222
### Vista de logout { #logout-view }
175223

176224
```python title="accounts/views.py"
177225
from django.contrib.auth import logout
178226
from django.shortcuts import redirect
227+
from django.urls import reverse
179228

180229

181230
def user_logout(request):
231+
FALLBACK_REDIRECT = 'index'
232+
182233
logout(request)#(1)!
183-
return redirect('home')
234+
return redirect(reverse(FALLBACK_REDIRECT))
184235
```
185236
{ .annotate }
186237

187238
1. La función [`logout`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.logout) de Django se encarga de cerrar la sesión del usuario actualmente «logeado».
188239

189240
### URL de logout { #logout-url }
190241

191-
Debemos añadir en las [URLs de primer nivel](urls.md#main-urls) la ruta (y la vista) para gestionar el cierre de sesión:
192-
193-
```python title="main/urls.py"
242+
```python title="accounts/urls.py" hl_lines="6"
194243
from django.urls import path
195244

196-
import accounts.views
245+
from . import views
197246

198247
urlpatterns = [
199-
path('logout/', accounts.views.user_logout, name='logout'),#(1)!
248+
path('logout/', views.user_logout, name='logout'),
200249
]
201250
```
202-
{ .annotate }
203-
204-
1. - Puede ser interesante crear una aplicación `accounts` para «empaquetar» toda la lógica de negocio que tiene que ver con autenticación.
205-
- :fontawesome-solid-triangle-exclamation:{ .yellow } No podemos llamar `auth` a la aplicación porque entraría en conflicto con la aplicación [`django.contrib.auth`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django-contrib-auth) de Django.
206251

207252
### Enlace de logout { #logout-link }
208253

@@ -217,11 +262,13 @@ En algún punto de nuestras plantillas deberemos añadir un enlace para cerrar s
217262

218263
## Registro { #signup }
219264

220-
Para implementar el procedimiento de **registro de usuario** debemos llevar a cabo distintas tareas.
265+
Para implementar el procedimiento de **registro de usuario** debemos desarrollar varios elementos:
221266

222-
??? tip "accounts"
223-
224-
Se recomienda crear una aplicación `accounts` donde implementar todos los artefactos necesarios de cara al registro «signup».
267+
- [x] Formulario.
268+
- [x] Plantilla.
269+
- [x] Vista.
270+
- [x] URL.
271+
- [x] Enlace.
225272

226273
### Formulario de registro { #signup-form }
227274

@@ -288,7 +335,7 @@ class SignupForm(forms.ModelForm):
288335
### Plantilla de registro { #signup-template }
289336

290337
```htmldjango title="accounts/templates/accounts/signup.html"
291-
<form method="post" action="{% url 'signup' %}">
338+
<form method="post">
292339
{% csrf_token %}
293340
{{ form }}
294341
<input type="submit" value="Sign up">
@@ -305,37 +352,36 @@ from .forms import SignupForm
305352

306353

307354
def user_signup(request):
355+
FALLBACK_REDIRECT = 'index'
356+
357+
if request.user.is_authenticated:#(1)!
358+
return redirect(reverse(FALLBACK_REDIRECT))
308359
if request.method == 'POST':
309360
if (form := SignupForm(request.POST)).is_valid():
310-
user = form.save()#(1)!
311-
login(request, user)#(2)!
312-
return redirect('home')
361+
user = form.save()#(2)!
362+
login(request, user)#(3)!
363+
return redirect(FALLBACK_REDIRECT)
313364
else:
314365
form = SignupForm()
315-
return render(request, 'accounts/signup.html', dict(form=form))
366+
return render(request, 'accounts/signup.html', {'form': form})
316367
```
317368
{ .annotate }
318369

319-
1. Guardamos el formulario de modelo con lo que obtenemos una instancia de usuario.
320-
2. «Logear» al usuario tras el registro es algo opcional. Depende del contexto.
370+
1. Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
371+
2. Guardamos el formulario de modelo con lo que obtenemos una instancia de usuario.
372+
3. «Logear» al usuario tras el registro es algo opcional. Depende del contexto.
321373

322374
### URL de registro { #signup-url }
323375

324-
Debemos añadir en las [URLs de primer nivel](urls.md#main-urls) la ruta (y la vista) para gestionar el registro de usuario:
325-
326-
```python title="main/urls.py"
376+
```python title="main/urls.py" hl_lines="6"
327377
from django.urls import path
328378

329-
import accounts.views
379+
from . import views
330380

331381
urlpatterns = [
332-
path('signup/', accounts.views.user_signup, name='signup'),#(1)!
382+
path('signup/', views.user_signup, name='signup')
333383
]
334384
```
335-
{ .annotate }
336-
337-
1. - Puede ser interesante crear una aplicación `accounts` para «empaquetar» toda la lógica de negocio que tiene que ver con autenticación.
338-
- :fontawesome-solid-triangle-exclamation:{ .yellow } No podemos llamar `auth` a la aplicación porque entraría en conflicto con la aplicación [`django.contrib.auth`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django-contrib-auth) de Django.
339385

340386
### Enlace de registro { #signup-link }
341387

0 commit comments

Comments
 (0)