recipes_api/docs/01_user_api.md
2023-10-12 14:14:02 -03:00

1053 lines
28 KiB
Markdown

# User Model
## Autenticación Django
- Sistema de autenticación *built-in*
- Framework para características básicas
- Registro
- Login
- Autorización
- Se integran con el panel de administración de Django
### Django user model
- Es la fundación del sistema de autenticación de Django
- Django incorpora por defecto un modelo de usuario
- Utiliza un nombre de usuario en vez de un email
- No es facil de personalizar
- Creación de modelo de usuario personalizado para nuevos proyectos
- Permite el uso de email en vz de nombre de usuario
- Asegura compatibilidad del proyecto con posibles cambios del modelo usuario
en versiones futuras
### Creación del modelo
- Basado en la clase `AbstractBaseUser` y `PermissionsMixin`
- Creación de administrador personalizado
- Se establece `AUTH_USER_MODEL` en `settings.py` para utlizar este modelo
- Creación y ejecución de las migraciones
### AbstractBaseUser
- Proporciona las características de autenticación
- No incluye campos
### PermissionsMixin
- Soporte para el sistema de permisos de Django
- Incuye todos los campos y métodos necesarios
### Problemas comunes
- Correr migraciones antes de crear el modelo personalizado
- Crear el modelo personalizado primero
- Tipeo
- Indentación
## User Model personalizado
### Campos de usuario
- email `EmailField`
- name `CharField`
- is_active `BooleanField`
- is_staff `BooleanField`
### User Model administrador
- Usado para administar objetos
- Lógica personalizada para crear objetos
- **Hash** passwords
- Metodos para el CLI de Django
- Create superuser
### BaseUserManager
- Clase base para administrar usuarios
- Métodos útliles de ayuda
- `normalize_email` para almacenar emails de forma consistente
- Métodos a definir
- `create_user` llamado al crear usuarios
- `create_superuser` usado por el CLI para crear un super usuario (**admin**)
### Agregando Unitetst para el modelo usuario personalizado
[test_models.py](../app/core/tests/tests_models.py)
```py
from django.test import TestCase
from django.contrib.auth import get_user_model
class ModelTests(TestCase):
def test_create_user_with_email_sucessfull(self):
email = 'test@example.com'
password = 'testpass123'
user = get_user_model().objects.create_user(
email=email,
password=password,
)
self.assertEqual(user.email, email)
self.assertTrue(user.check_password(password))
```
## Agregar usuario personalizado al proyecto
[models.py](../app/core/models.py)
```py
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
class UserManager(BaseUserManager):
"""Manager for users."""
def create_user(self, email, password=None, **extra_fields):
"""Create, save and return a new user."""
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""User in the system."""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
# Asignar el UserManager a esta clase User
objects = UserManager()
USERNAME_FIELD = 'email'
```
Actualizar `settings.py` para que Django utilize este modelo de autenticación
agregando al final del archivo lo sgte.
```py
...
AUTH_USER_MODEL = 'core.User'
```
Crear migraciones
`docker compose run --rm app sh -c "python manage.py makemigrations"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Running 0.0s
Migrations for 'core':
core/migrations/0001_initial.py
- Create model User
```
Codigo autogenerado [0001_initial.py](../app/core/migrations/0001_initial.py)
para app `core`
Aplicar migraciones
`docker compose run --rm app sh -c "python manage.py wait_for_db &&
python manage.py migrate"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Running 0.0s
Waiting for database...
Database available!
Operations to perform:
Apply all migrations: admin, auth, contenttypes, core, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying core.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying sessions.0001_initial... OK
```
En caso arrojar error por haber aplicado alguna migración previa se puede correr
`docker rm <db_volume>`, si *"esta en uso"* primero correr `docker compose down`.
Los nombres se pueden ver con `docker volume ls`
Al correr los tests nuevamente `docker compose run --rm app sh -c "python
manage.py test"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Running 0.0s
Found 5 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...Waiting for database...
Database unavailable, waiting 1 second...
Database unavailable, waiting 1 second...
Database unavailable, waiting 1 second...
Database unavailable, waiting 1 second...
Database unavailable, waiting 1 second...
Database available!
.Waiting for database...
Database available!
.
----------------------------------------------------------------------
Ran 5 tests in 0.675s
OK
Destroying test database for alias 'default'...
```
## Normalización de direcciones de email
### Test normalize email addresses
[test_models.py](../app/core/tests/test_models.py)
```py
def test_new_user_email_normalized(self):
"""Test email is normalized for new users."""
sample_emails = [
['test1@EXAMPLE.com', 'test1@example.com'],
['test2@Example.com', 'test2@example.com'],
['TEST3@EXAMPLE.COM', 'TEST3@example.com'],
['test4@example.COM', 'test4@example.com'],
]
for email, expected in sample_emails:
user = get_user_model().objects.create_user(email, 'sample123')
self.assertEqual(user.email, expected)
```
Modificar el `ceate_user` de [app/core/models.py](../app/core/models.py)
para utilizar el método `normalize_email` que provee la clase **BaseUserManager**
```diff
- user = self.model(email=email, **extra_fields)
+ user = self.model(email=self.normalize_email(email), **extra_fields)
```
## Requerir email
### Test requerir email
[test_models.py](../app/core/tests/test_models.py)
```py
def test_new_user_withouth_email_raises_error(self):
"""Test that creating a user withouth an email raises a ValueError."""
with self.assertRaises(ValueError):
get_user_model().objects.create_user('', 'test123')
```
Modificar el `ceate_user` de [app/core/models.py](../app/core/models.py)
y levantar excepción `ValueError` si usuario no ingresa un email
```py
def create_user(self, email, password=None, **extra_fields):
"""Create, save and return a new user."""
if not email:
raise ValueError('User must have an email address.')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
```
## Funcionalidad super usuario
### Test creación de super usuario
[test_models.py](../app/core/tests/test_models.py)
```py
def test_create_superuser(self):
"""Test creating a superuser."""
user = get_user_model().objects.create_superuser(
'test@example.com',
'test123',
)
self.assertTrue(user.is_superuser)
self.assertTrue(user.is_staff)
```
Creación del método `create_superuser` para la clase `UserManager` en
[app/core/models.py](../app/core/models.py)
```py
def create_superuser(self, email, password):
"""Create and return a new superuser."""
user = self.create_user(email, password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
```
## Probando el modelo de usuario
Correr `docker compose up` y en otra terminal
`docker compose run --rm app sh -c "python manage.py createsuperuser"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Running 0.0s
Email: admin@example.com
Password:
Password (again):
Superuser created successfully
```
## Django Admin
Es la interfáz gráfica para los modelos creados en el proyecto, permite
la administración basica C.R.U.D.
Requiere muy poco cóidgo para ser usado
![img](./imgs_readme/django_admin_00.png)
Se activa por modelo, en [`admin.py`](../app/core/admin.py)
### Personalización del administrador
Se crea una clase basada en `ModelAdmin` o `UserAdmin` donde se sobrescribe o
establecen variables de clase
ejemplo
```py
class UserAdmin(BaseUserAdmin):
"""Define de admin pages for users."""
ordering = ['id']
list_display = ['emial', 'name']
fieldsets = (
(None, {'fields': ('email', 'password')}),
)
readonly_files = ['last_login']
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': (
'email',
),
})
)
```
- `ordening` ![img](./imgs_readme/django_admin_01.png)
- `list_display` ![img](./imgs_readme/django_admin_02.png)
- `fieldsets` ![img](./imgs_readme/django_admin_03.png)
- `readonly_fields` ![img](./imgs_readme/django_admin_04.png)
- `add_fieldsets` ![img](./imgs_readme/django_admin_05.png)
### Creando test para el administrador
[`app/core/tests/test_models.py`](../app/core/tests/test_admin.py)
```py
class AdminSiteTests(TestCase):
"""Tests for Django admin."""
def setUp(self):
"""Create user and client."""
self.client = Client()
self.admin_user = get_user_model().objects.create_superuser(
email='admin@example.com',
password='testpass123',
)
self.client.force_login(self.admin_user)
self.user = get_user_model().objects.create_user(
email='user@example.com',
password='testpass123',
name='Test User'
)
def test_users_list(self):
"""Test that users are listed on page."""
url = reverse('admin:core_user_changelist')
res = self.client.get(url)
self.assertContains(res, self.user.name)
self.assertContains(res, self.user.email)
```
- Django docs
[reversing admin urls](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#reversing-admin-urls)
- Django docs
[testing tools](https://docs.djangoproject.com/en/4.2/topics/testing/tools/#overview-and-a-quick-example)
Correr test `docker compose run --rm app sh -c "python manage.py test"`
### Activar admin para core app
En [`admin.py`](../app/core/admin.py)
```py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from core import models
class UserAdmin(BaseUserAdmin):
"""Define the admin pages for users."""
ordering = ['id']
list_display = ['email', 'name']
admin.site.register(models.User, UserAdmin)
```
- **Admin**
![img](./imgs_readme/django_admin_06.png)
- **CORE** Section
![img](./imgs_readme/django_admin_07.png)
- **CORE** Usuarios, requiere modificar pues espera campos que el modelo no tiene
![img](./imgs_readme/django_admin_08.png)
### Modificar admin para que use los campos de usuario personalizado
```py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext_lazy as _
from core import models
class UserAdmin(BaseUserAdmin):
"""Define the admin pages for users."""
ordering = ['id']
list_display = ['email', 'name']
filedsets = (
(None, {'fields': ('email', 'password')}),
(
_('Permissions'),
{
'fields': (
'is_active',
'is_staff',
'is_superuser',
)
}
),
(_('Important dates', {'fields': ('last_login',)})),
)
readonly_fields = ['last_login']
admin.site.register(models.User, UserAdmin)
```
- Administrador de usuario personalizado
![img](./imgs_readme/django_admin_09.png)
- Cambio de lenguaje y timezone, y traducción `gettext_lazy`
![img](./imgs_readme/django_admin_10.png)
- Requiere modificar pues espera campos que el modelo no tiene
- ![img](./imgs_readme/django_admin_11.png)
### Test página de creación de usuario
```py
def test_create_user_page(self):
"""Test the create user page works."""
url = reverse('admin:core_user_add')
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
```
### Actualizar clase `UserAdmin` para que use los campos personalizados
[app/core/admin.py](../app/core/admin.py)
```py
class UserAdmin(BaseUserAdmin):
...
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': (
'email',
'password1',
'password2',
'name',
'is_active',
'is_staff',
'is_superuser',
)
}),
)
...
```
- Los test pasan
- Página para crear usuarios ![img](./imgs_readme/django_admin_12.png)
- Panel de usuarios del administrador ![img](./imgs_readme/django_admin_13.png)
## Documentación de la API
Es necesario tener acceso a una buena documentación para que los desarrolladores
puedan saber como usarla. Se documenta todo lo que sea necesario para usar la API
- Endopoints disponibles (paths)
- Métodos soportados `GET`, `POST`, `PUT`, `PATCH`, `DELETE`...
- Formateo de payloads (inputs). Parametros, Post en formato **JSON**
- Formateo de respuestas (outputs). Respuesta en formato **JSON**
- Proceso Autenticación
### Opiones de documentación
- Manual
- Documento de texto
- Markdown
- Automatizada
- Usa la metadata del código (comments)
- Genera páginas de documentación
## Autodocs de DRF
- Documentación Autogenerada (3rd party library)
- `drf-spectacular`
- Genera el "schema"
- Interfaz web navegable
- Test requests
- Maneja la autenticación
### Como funciona
1. Creación del archivo `schema`
2. Pasa el schema al GUI
### Open API Schema
- Estandar para describir APIs
- Popular en la industria
- Soportada por la mayoría de herramientas de documentación de API
### Ejemplo Schema
fragmento
```yml
/api/recipe/ingredients/:
get:
oprationId: recipe_ingredients_list
description: Manage ingredients in the database.
parameteres:
- in: query
name: assigned_only
schema:
type: integer
enum:
- 0
- 1
description: Filter by item assigned to recipies
tags:
- recipe
security:
- tokenAuth: []
responses:
'200':
content:
application/json
schema:
type: array
items:
$ref: '#/components/schemas/Ingredient'
description: ''
...
```
### Implementación DRF
Se agrega dependencia en `drf-spectacular>=0.16` en `requirements.txt`
Instalar app, en `settings.py`
```py
INSTALLED_APPS = [
...
'core',
'rest_framework',
'drf_spectacular',
]
...
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
```
### Activar las URLS
```py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
path('api/schema/', SpectacularAPIView.as_view(), name='api-schema'),
path(
'api/docs',
SpectacularSwaggerView.as_view(url_name='api-schema'),
name='api-docs'),
]
```
- `docker compose run`
- `127.0.0.1:8000/api/docs`
## User API
### Diseño
- Registro de usario
- Creación de token de autenticación
- Consultar y actualizar perfil
### Endpoins
| Endpoint | Method | Descripción |
| - | - | - |
| `user/create` | `POST` | Registrar nuevo usuario |
| `user/token` | `POST` | Crea un nuevo token |
| `user/me/` | `PUT/PATCH` | Actualizar perfíl |
### Creación user app
`docker compose run --rm app sh -c "python manage.py startapp user"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Created 0.0s
[+] Running 1/1
✔ Container recipes_api_django-db-1 Started 0.2s
```
Activar `user` app en `settings.py`
### Test User API
[`app/user/tests/test_user_api.py`](../app/user/tests/test_user_api.py)
```py
CREATE_USER_URL = reverse('user:create')
def create_user(**params):
"""Create and return a new user."""
return get_user_model().objects.crate_user(**params)
class PublicUserApiTest(TestCase):
"""Test the public features of the user API."""
def setUp(self):
self.client = APIClient()
def test_create_user_success(self):
"""Tests creating a user is successful."""
payload = {
'email': 'test@example.com',
'password':'testpass123',
'name': 'TestName',
}
res = self.client.post(CREATE_USER_URL, payload)
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
user = get_user_model().objects.get(email=payload['email'])
self.assertTrue(user.check_password(payload['password']))
self.assertNotIn('password', res.data)
def test_user_with_email_exists_error(self):
"""Test error returned if user with email exists."""
payload = {
'email': 'test@example.com',
'password':'testpass123',
'name': 'Test Name',
}
create_user(**payload)
res = self.client.post(CREATE_USER_URL, payload)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
def test_password_too_short_error(self):
"""Test an error is returned if password less than 5 chars."""
payload = {
'email': 'test@example.com',
'password':'pw',
'name': 'Test Name',
}
res = self.client.post(CREATE_USER_URL, payload)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
user_exists = get_user_model().objects.filter(
email=payload['email']
).exists()
self.assertFalse(user_exists)
```
### Creando funcionalidad de User API
- [serializers.py](../app/user/serializers.py)
```py
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
"""Seralizer for the model object."""
class Meta:
model = get_user_model()
fields = ['email', 'password', 'name']
extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}
def create(self, validated_data):
"""Create and return a user with encrypted password"""
return get_user_model().objects.create_user(**validated_data)
```
- [views.py](../app/user/views.py)
```py
from rest_framework import generics
from user.serializers import UserSerializer
class CreateUserView(generics.CreateAPIView):
"""Create a new user in the system."""
serializer_class = UserSerializer
```
- [urls.py](../app/user/urls.py)
```py
from django.urls import path
from user import views
app_name='user'
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create')
]
```
- [app/urls.py](../app/app/urls.py)
```py
...
from django.urls import include, path
urlpatterns = [
...
path('api/user/', include('user.urls')),
]
```
## Autenticación
| Tipo de autenticación | Descripción |
| - | - |
| **Básica** | Envía usuario y password en cada request |
| **Token** | Usa un token en el encabezado HTTP |
| **JSON Web Token (JWT)** | Usa un token de acceso |
| **Sesión** | Usa cookies |
En esta app se utilza **Token** por:
- Balance entre simplicidad y seguridad
- Soporte por defecto por **DRF**
- Bién soportada por la mayoria de clientes
<style>div.mermaid{text-align: center;}</style>
```mermaid
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'basis'}}}%%
flowchart
subgraph " "
CT["<b>Create token</b>
(Post username/password)"]
STOC["<b>Store token on client</b>"]
ITIH["<b>Include token in HTTP headers</b>"]
CT .-> STOC .-> ITIH
end
```
#### Pros del uso de Token
- Soporte por defecto
- Simple de usar
- Soportada por todos los clientes
- Evita enviar datos de usuario/password en cada request
#### Cons del uso de Token
- El token debe ser seguro
- Requiere hacer peticiones a la base de datos
### Login out
- Sucede en el lado del cliente
- Borra el token
### Test token API
Agregar tests en
[`app/user/tests/test_user_api.py`](../app/user/tests/test_user_api.py)
```py
...
TOKEN_URL = reverse('user:token')
...
class PublicUserApiTest(TestCase):
"""Test the public features of the user API."""
...
def test_create_token_for_user(self):
"""Test generate token for valid credentials."""
user_details = {
'name': 'Test Name',
'email': 'test@example.com',
'password':'test-user-password123',
}
create_user(**user_details)
payload = {
'email': user_details['email'],
'password': user_details['password'],
}
res = self.client.post(TOKEN_URL, payload)
self.assertIn('token', res.data)
self.assertEqual(res.status_code, status.HTTP_200_OK)
def test_create_token_bad_credentials(self):
"""Test returns error if credentials invalid."""
create_user(email='test@example.com', password='goodpass')
payload = {'email': 'test@example.com' ,'password': 'badpass'}
res = self.client.post(TOKEN_URL, payload)
self.assertNotIn('token', res.data)
self.assertNotIn(res.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_token_blank_password(self):
"""Test posting a blank password returns an error."""
payload = {'email': 'test@example.com' , 'password': ''}
res = self.client.post(TOKEN_URL, payload)
self.assertNotIn('token', res.data)
self.assertNotIn(res.status_code, status.HTTP_400_BAD_REQUEST)
```
## Implementación Token API
- Añadir app `rest_framework.authtoken` en [settings.py](../app/app/settings.py)
```py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken', # <---
'drf_spectacular',
'user',
]
```
### Creación del serlizador para token api
- [user/serializer.py](../app/user/serializers.py)
```py
...
class AuthTokenSerializer(serializers.Serializer):
"""Serializer for the user auth token."""
email = serializer.EmailField()
password = serializer.CharField(
style={'input_type': 'password'},
trim_whitespace=False,
)
def validate(self, attrs):
"""Validate and authenticate the user."""
email = attrs.get('email')
password = attrs.get('password')
user = authenticate(
request=self.context.get('request'),
username=email,
password=password,
)
if not user:
msg = _('Unable to authenticate with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
```
- vista [user/views.py](../app/user/views.py)
```py
...
class CreateTokenView(ObtainAuthToken):
"""Create a new auth token for user."""
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
```
- urls [user/urls.py](../app/user/urls.py)
```py
urlpatterns = [
...
path('token/', views.CreateTokenView.as_view(), name='token'),
]
```
### Test administrar usuario
- [test_user_api.py](../app/user/tests/test_user_api.py)
```py
...
ME_URL = reverse('user:me')
...
class PrivateUserApiTests(TestCase):
"""Test API requests that require authentication."""
def setUp(self):
self.user = create_user(
email='test@example.com',
password='testpass123',
name='Test Name',
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_retrive_profile_success(self):
"""Test retrieving profile for logged in user."""
res = self.client.get(ME_URL)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(
res.data, {
'name': self.user.name,
'email': self.user.email,
})
def test_post_me_not_allowed(self):
"""Test POST is not allowed for the 'me' endpoint."""
res = self.client.post(ME_URL, {})
self.assertAlmostEqual(
res.status_code,
status.HTTP_405_METHOD_NOT_ALLOWED
)
def test_update_user_profile(self):
"""Test updating the user profile for the autenticated user."""
payload = { 'name': 'Updated Name', 'password': 'newpassword123' }
res = self.client.patch(ME_URL, payload)
self.user.refresh_from_db()
self.assertEqual(self.user.name, payload['name'])
self.assertTrue(self.user.check_password(payload['password']))
self.assertEqual(res.status_code, status.HTTP_200_OK)
```
## Implementación API actualizar usuario
`me` endpoint
- creación (sobrescritura) del método update
[serializer.py](../app/user/serializers.py)
```py
...
class UserSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
"""Create and return a user with encrypted password"""
return get_user_model().objects.create_user(**validated_data)
def update(self, instance, validated_data):
"""Update and return user."""
password = validated_data.pop('password', None)
user = super().update(instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
...
```
- vistas [views.py](../app/user/views.py)
```py
from rest_framework import generics, authentication, permissions
...
class ManageUserView(generics.RetrieveUpdateAPIView):
"""Manage the autenticated user."""
serializer_class = UserSerializer
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get_object(self):
"""Retrieve and return the authenticated user."""
return self.request.user
```
- urls [urls.py](../app/user/urls.py)
```py
urlpatterns = [
...
path('me/', views.ManageUserView.as_view(), name='me'),
]
```
## Pruebas en navegador
Ruta `localhost:8000/api/docs`
![img](./imgs_readme/api_swagger_00.png)
----
- [Inicio](../README.md)
- [**User API**](./01_user_api.md)
- [Recipe API](./02_recipe_api.md)
- [Tag API](./03_tag_api.md)
- [Ingredient API](./04_ingredient_api.md)
- [Image API](./05_image_api.md)
- [Filters](./06_filters.md)