2023-03-29 14:06:11 -03:00
|
|
|
# Django REST API
|
|
|
|
|
|
|
|
[Django Rest Framework](https://www.django-rest-framework.org/)
|
|
|
|
|
|
|
|
### Requerimientos Rest Framework
|
|
|
|
|
|
|
|
- Python (>=3.10)
|
|
|
|
- Django
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">Opcionales</summary>
|
2023-03-29 14:06:11 -03:00
|
|
|
|
|
|
|
- PyYAML, uritemplate: Schema generation support.
|
|
|
|
- Markdown: Markdown support for the browsable API.
|
|
|
|
- Pygments: Add syntax highlighting to Markdown processing.
|
|
|
|
- django-filter: Filtering support.
|
|
|
|
- django-guardian: Object level permissions support.
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
</details></br>
|
2023-03-29 14:06:11 -03:00
|
|
|
|
2023-04-05 00:50:05 -04:00
|
|
|
### Instalación
|
2023-03-29 14:06:11 -03:00
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
Instalar [requerimientos](./requirements.txt) **en entorno virtual**
|
|
|
|
|
2023-04-05 00:50:05 -04:00
|
|
|
```sh
|
2023-03-31 02:36:20 -03:00
|
|
|
pip install -r requirements.txt
|
2023-03-29 18:35:23 -03:00
|
|
|
```
|
2023-03-29 14:06:11 -03:00
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
Django utiliza SQLite3 por defecto para simplificar el desarrolo, en este proyecto
|
2023-03-31 02:36:20 -03:00
|
|
|
se utliza MariaDB, pero es opcional. Si no requiere sacarlo de
|
|
|
|
[requirements.txt](./requirements.txt) o desinstalarlo `pip uninstall mysqlclient`
|
2023-03-29 14:06:11 -03:00
|
|
|
|
2023-03-29 21:15:04 -03:00
|
|
|
## Inicio del proyecto
|
2023-03-29 14:06:11 -03:00
|
|
|
|
2023-03-29 18:35:23 -03:00
|
|
|
**Creación del proyecto Django**
|
|
|
|
|
2023-03-29 14:06:11 -03:00
|
|
|
```sh
|
|
|
|
mkdir backend
|
|
|
|
django-admin startproject drf_course backend
|
|
|
|
```
|
|
|
|
|
|
|
|
Este proyecto consta de 2 aplicaciones.
|
|
|
|
|
2023-04-05 00:50:05 -04:00
|
|
|
La primera es el núcleo. Esta contendrá la lógica de **contacto** con el endpoint.
|
|
|
|
La segunda será **ecommerce**. Esta contendrá la ĺógica del endpoint de los
|
|
|
|
*items* y *ordenes*.
|
2023-03-29 14:06:11 -03:00
|
|
|
|
|
|
|
```sh
|
|
|
|
cd backend
|
2023-03-29 21:15:04 -03:00
|
|
|
./manage.py startapp core
|
2023-03-29 14:06:11 -03:00
|
|
|
```
|
|
|
|
|
|
|
|
### Editar configuración del proyecto
|
|
|
|
|
|
|
|
Archivo [./backend/drf_course/settings.py](./backend/drf_course/settings.py).
|
|
|
|
|
2023-03-29 18:35:23 -03:00
|
|
|
Importar variables de entorno usando *python-dotenv* del archivo en `.backend/.env`.
|
2023-03-29 14:06:11 -03:00
|
|
|
|
|
|
|
```py
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
import os
|
|
|
|
|
|
|
|
load_dotenv()
|
|
|
|
```
|
|
|
|
|
|
|
|
Reemplazar `ALLOWED_HOSTS`, `SECRET_KEY` y `DEBUG`.
|
|
|
|
|
|
|
|
```py
|
|
|
|
SECRET_KEY = os.environ.get("SECRET_KEY")
|
|
|
|
DEBUG = int(os.environ.get("DEBUG", default=0))
|
|
|
|
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
|
|
|
|
```
|
|
|
|
|
|
|
|
Añadir aplicaciones
|
2023-03-31 02:36:20 -03:00
|
|
|
|
2023-03-29 14:06:11 -03:00
|
|
|
```py
|
|
|
|
INSTALLED_APPS = [
|
|
|
|
'django.contrib.admin',
|
|
|
|
'django.contrib.auth',
|
|
|
|
'django.contrib.contenttypes',
|
|
|
|
'django.contrib.sessions',
|
|
|
|
'django.contrib.messages',
|
|
|
|
'django.contrib.staticfiles',
|
|
|
|
'django_extensions', # <---
|
|
|
|
'django_filters', # <---
|
|
|
|
'rest_framework', # <---
|
|
|
|
'core', # <---
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
Añadir variables del framework REST al final del arhivo.
|
|
|
|
|
|
|
|
```py
|
|
|
|
REST_FRAMEWORK = {
|
|
|
|
'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
|
|
|
|
'DEFAULT_PARSER_CLASSES': (
|
|
|
|
'rest_framework_json_api.parsers.JSONParser',
|
|
|
|
),
|
|
|
|
'DEFAULT_RENDERER_CLASSES': (
|
|
|
|
'rest_framework_json_api.renderers.JSONRenderer',
|
|
|
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
|
|
|
),
|
|
|
|
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
|
|
|
|
'DEFAULT_FILTER_BACKENDS': (
|
|
|
|
'rest_framework_json_api.filters.QueryParameterValidationFilter',
|
|
|
|
'rest_framework_json_api.filters.OrderingFilter',
|
|
|
|
'rest_framework_json_api.django_filters.DjangoFilterBackend',
|
|
|
|
'rest_framework_json_api.filters.SearchFilter',
|
|
|
|
),
|
|
|
|
'SEARCH_PARAM': 'filter[search]',
|
|
|
|
'TEST_REQUEST_RENDERER_CLASSES': (
|
|
|
|
'rest_framework_json_api.renderers.JSONRenderer',
|
|
|
|
),
|
|
|
|
'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
En caso de utilizar MariaDB, cambiar la declaración de *DATABASES*, para usar
|
2023-03-29 18:35:23 -03:00
|
|
|
las variables de entorno declaradas en `./backend/.env`
|
2023-03-29 14:06:11 -03:00
|
|
|
|
|
|
|
```py
|
|
|
|
DATABASES = {
|
|
|
|
'default': {
|
|
|
|
'ENGINE': 'django.db.backends.mysql',
|
|
|
|
'NAME': os.environ.get("MARIADB_DBASE"),
|
|
|
|
'USER': os.environ.get("MARIADB_USER"),
|
|
|
|
'PASSWORD': os.environ.get("MARIADB_PASS"),
|
|
|
|
'HOST': os.environ.get("MARIADB_HOST"),
|
|
|
|
'PORT': os.environ.get("MARIADB_PORT"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Modificar [./backend/drf_course/urls.py](./backend/drf_course/urls.py)
|
|
|
|
|
|
|
|
```py
|
|
|
|
from django.contrib import admin
|
|
|
|
from django.urls import path
|
|
|
|
from rest_framework import routers
|
|
|
|
|
|
|
|
router = routers.DefaultRouter()
|
|
|
|
|
|
|
|
urlpatterns = router.urls
|
|
|
|
|
|
|
|
urlpatterns += [
|
|
|
|
path('admin/', admin.site.urls),
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
2023-03-29 18:35:23 -03:00
|
|
|
#### Migrar y probar aplicación
|
|
|
|
|
|
|
|
```sh
|
2023-03-29 21:15:04 -03:00
|
|
|
./manage.py migrate
|
|
|
|
./manage.py runserver
|
2023-03-29 18:35:23 -03:00
|
|
|
```
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
## Creación del primer endoint
|
2023-03-29 18:35:23 -03:00
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
Creación de endpoint ***Contac***, para que un usuario pueda enviar su *nombre*,
|
|
|
|
*email*, y *mensaje* al backend.
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
Para ello se requiere:
|
|
|
|
|
|
|
|
- Un modelo que almacene la captura de los datos entrantes.
|
|
|
|
- Un serializador que procese los datos entrantes del usuario y envíe un
|
|
|
|
mensaje de respuesta.
|
|
|
|
- Una vista que encapsule las llamadas a los métodos REST HTTP comunes.
|
|
|
|
- Una ruta `/url` llamada `/contact/`.
|
|
|
|
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
### Model
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
[./backend/core/models.py](./backend/core/models.py)
|
|
|
|
|
|
|
|
Utiliza modelos abstractos del modulo
|
|
|
|
*django_extensions*; `TimeStampedModel` (campos como *created*), `ActivatorModel`
|
|
|
|
(campos *status*, *activated date*, *deactivated date* ), `TitleDescriptionModel`
|
|
|
|
(campos de texto *textfield* y *charfield*).
|
|
|
|
Clase **Contact** hereda de estos modelos. Todas las tablas del proyecto tendrán
|
2023-03-30 13:02:53 -03:00
|
|
|
un campo *uuid* como id. Además de un campo *email* de Django. Y método de
|
2023-03-29 18:35:23 -03:00
|
|
|
representación del modelo en cadena de texto.
|
|
|
|
|
|
|
|
**Modelo abstracto**
|
|
|
|
|
|
|
|
Para implementar *uuid* en vez de *id* como campo identificador en todos los
|
|
|
|
modelos, se crea el modulo [model_abstracts.py](./backend/utils/model_abstracts.py)
|
|
|
|
que hereda de *models* de *django.db* y utliza el campo *id*. Utiliza el campo *UUID*.
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
### Serializer
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
Para convertir los datos de entrada *json* en tipos de datos de python, y
|
2023-03-30 15:24:14 -03:00
|
|
|
viceversa, se crea el archivo [./backend/core/serializers.py](./backend/core/serializers.py)
|
2023-03-29 18:35:23 -03:00
|
|
|
en la app *core*. Este hereda de la clase *serializers* del modulo *rest_framework*
|
|
|
|
e implementa sus campos (*CharField* *EmailField*).
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
### View
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
[./backend/core/views.py](./backend/core/views.py)
|
2023-03-30 15:24:14 -03:00
|
|
|
backend
|
2023-03-29 18:35:23 -03:00
|
|
|
El uso de la clase *APIView* es muy similar al una vista regular, la petición
|
|
|
|
entrante es enviada a un manejador apropiado para el método, como `.get()` o
|
|
|
|
`.post()`. Además se pueden establecer otros atributos en la clase que controla
|
|
|
|
varios aspectos de las normas de la API.
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
### Route & URL
|
2023-03-29 18:35:23 -03:00
|
|
|
|
2023-03-30 15:24:14 -03:00
|
|
|
[./backend/drf_course/urls.py](./backend/drf_course/urls.py)
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
El framework REST añade soporte para ruteo automático de URLs a Django y provee
|
|
|
|
al programador de una simple, rápida y consistente forma de enlazar la lógica
|
|
|
|
de la vista a un conjunto de URLs.
|
|
|
|
|
|
|
|
#### Registrar app en panel de administración
|
|
|
|
|
|
|
|
Importar modelo y registrar en [./backend/core/admin.py](./backend/core/admin.py).
|
|
|
|
|
|
|
|
Crear las migraciones y migrar.
|
|
|
|
|
2023-04-05 00:50:05 -04:00
|
|
|
```sh
|
2023-03-29 21:15:04 -03:00
|
|
|
./manage.py makemigrations
|
|
|
|
./manage.py migrate
|
2023-03-29 18:35:23 -03:00
|
|
|
```
|
2023-03-31 02:36:20 -03:00
|
|
|
|
2023-03-29 18:35:23 -03:00
|
|
|
Finalmente, crear **super usuario**.
|
|
|
|
|
2023-04-05 00:50:05 -04:00
|
|
|
```sh
|
2023-03-29 21:15:04 -03:00
|
|
|
./manage.py createsuperuser
|
2023-03-29 18:35:23 -03:00
|
|
|
```
|
|
|
|
|
2023-03-29 21:15:04 -03:00
|
|
|
#### Prueba manual
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
**curl**
|
2023-03-29 21:15:04 -03:00
|
|
|
|
|
|
|
```sh
|
2023-04-05 00:50:05 -04:00
|
|
|
curl -X POST -H "Content-type: application/json" \
|
2023-03-29 21:15:04 -03:00
|
|
|
-d '{"name": "DevFzn", "message": "prueba", "email":"mail@mail.com"}' \
|
|
|
|
'http://127.0.0.1:8000/contact/'
|
|
|
|
```
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
o **HTTPie**
|
2023-03-29 18:35:23 -03:00
|
|
|
|
|
|
|
```sh
|
2023-03-29 21:15:04 -03:00
|
|
|
http post http://127.0.0.1:8000/contact/ name="DevFzn" message="prueba" \
|
|
|
|
email="devfzn@mail.com"
|
|
|
|
```
|
2023-03-29 18:35:23 -03:00
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">httpie output</summary>
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
```http
|
2023-03-29 18:35:23 -03:00
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Allow: POST, OPTIONS
|
|
|
|
Content-Length: 155
|
|
|
|
Content-Type: application/vnd.api+json
|
|
|
|
Cross-Origin-Opener-Policy: same-origin
|
|
|
|
Date: Wed, 29 Mar 2023 20:05:32 GMT
|
|
|
|
Referrer-Policy: same-origin
|
|
|
|
Server: WSGIServer/0.2 CPython/3.10.10
|
|
|
|
Vary: Accept, Cookie
|
|
|
|
X-Content-Type-Options: nosniff
|
|
|
|
X-Frame-Options: DENY
|
|
|
|
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"attributes": {
|
|
|
|
"email": "devfzn@mail.com",
|
|
|
|
"message": "prueba",
|
|
|
|
"name": "DevFzn"
|
|
|
|
},
|
|
|
|
"id": "bef5e90c-821a-4d04-98ef-c0a0adde5ec1",
|
|
|
|
"type": "ContactAPIView"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2023-03-29 21:15:04 -03:00
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
</details>
|
|
|
|
|
|
|
|
Se puede utilizar la shell de Django para chequear la nueva entrada en Contact
|
2023-03-29 21:15:04 -03:00
|
|
|
|
|
|
|
`./manage.py shell`
|
|
|
|
|
|
|
|
```py
|
|
|
|
>>> from core.models import Contact
|
|
|
|
>>> c = Contact.objects.last()
|
|
|
|
>>> c.title
|
|
|
|
'DevFzn'
|
|
|
|
```
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
Creación de pruebas en [./backend/core/tests.py](./backend/core/tests.py).
|
|
|
|
Utilizando las clases `APIClient` que proporciona un cliente incorporado y
|
2023-03-30 13:02:53 -03:00
|
|
|
`APITestCase`, similar al *TestCase* de Django.
|
2023-03-29 21:15:04 -03:00
|
|
|
|
|
|
|
#### Test suite para Contact
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
0. test setup
|
2023-03-30 16:21:22 -03:00
|
|
|
1. test ContactViewSet para método create
|
|
|
|
2. test ContactViewSet para método create cuando nombre no está en los datos
|
|
|
|
3. test ContactViewSet para método create cuando nombre está en blanco
|
|
|
|
4. test ContactViewSet para método create cuando mensaje no está en los datos
|
|
|
|
5. test ContactViewSet para método create cuando mensaje está en blanco
|
|
|
|
6. test ContactViewSet para método create cuando email no está en los datos
|
|
|
|
7. test ContactViewSet para método create cuando email está en blanco
|
|
|
|
8. test ContactViewSet para método create cuando email no es un email
|
2023-03-29 21:15:04 -03:00
|
|
|
|
|
|
|
Correr test `./manage.py test`
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">tests output</summary>
|
|
|
|
|
2023-03-29 21:15:04 -03:00
|
|
|
```py
|
|
|
|
Found 8 test(s).
|
|
|
|
Creating test database for alias 'default'...
|
|
|
|
System check identified no issues (0 silenced).
|
|
|
|
........
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Ran 8 tests in 0.028s
|
|
|
|
|
|
|
|
OK
|
|
|
|
Destroying test database for alias 'default'...
|
|
|
|
```
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
</details>
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
## Ecommerce endpoint
|
|
|
|
|
|
|
|
Este se compone de 2 endpoints, **items** y **order**. La primera se encarga
|
|
|
|
de retornar todos los items de la tienda al llamarla, además, si se llama a
|
|
|
|
este endpoint con una llave primaria, retorna solo ese item. Y cuando se requiera
|
|
|
|
comprar, se utiliza el *order* endpoint, donde se pasa el item en particular.
|
|
|
|
Se implementan validaciones para asegurar que el item esten en stock, también
|
|
|
|
asegurar que solo usuarios autentificado puedan llamar estos endpoints.
|
|
|
|
Esta app usa *token authentication*. Django Rest Framework facilita esta tarea.
|
|
|
|
|
|
|
|
Creación de app *ecommerce*
|
|
|
|
|
|
|
|
```py
|
|
|
|
./manage.py startapp ecommerce
|
|
|
|
```
|
|
|
|
|
2023-03-30 15:24:14 -03:00
|
|
|
Modificar [settings](./backend/drf_course/settings.py) del sitio, editando
|
2023-03-30 13:02:53 -03:00
|
|
|
`REST_FRAMEWORK` con el siguiente código.
|
|
|
|
> notar el nuevo `DEFAULT_AUTHENTICATION_CLASSES`.
|
|
|
|
|
|
|
|
```py
|
|
|
|
REST_FRAMEWORK = {
|
|
|
|
'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
|
|
|
|
'DEFAULT_PARSER_CLASSES': (
|
|
|
|
'rest_framework_json_api.parsers.JSONParser',
|
|
|
|
),
|
|
|
|
'DEFAULT_AUTHENTICATION_CLASSES': [ # <---
|
|
|
|
'rest_framework.authentication.TokenAuthentication', # <---
|
|
|
|
],
|
|
|
|
...
|
|
|
|
```
|
|
|
|
|
|
|
|
Agregar las siguientes apps en `INSTALLED_APPS`.
|
|
|
|
|
|
|
|
```py
|
|
|
|
INSTALLED_APPS = [
|
|
|
|
...
|
|
|
|
'rest_framework.authtoken', # <---
|
|
|
|
'core',
|
|
|
|
'ecommerce', # <---
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
### URLs
|
|
|
|
|
|
|
|
Agregar los nuevos endpoints en [urls.py](./backend/drf_course/urls.py) del sitio.
|
|
|
|
|
|
|
|
```py
|
|
|
|
# importar authtoken
|
|
|
|
from rest_framework.authtoken.views import obtain_auth_token
|
|
|
|
|
|
|
|
# agregar urls
|
|
|
|
urlpatterns += [
|
|
|
|
...
|
|
|
|
path('api-token-auth', obtain_auth_token),
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
Cuando un usuario visite este endpoint y pase un nombre de usuario y un password
|
|
|
|
validos, este recibirá de vuelta un *token* de autentificación. Este token esta
|
|
|
|
enlazado con el usuario especifico.
|
|
|
|
|
|
|
|
Realizar migraciones
|
|
|
|
|
|
|
|
```sh
|
|
|
|
./manage.py makemigrations
|
|
|
|
./manage.py migrate
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Signals
|
|
|
|
|
|
|
|
Se requiere un mecanismo (signal) que cree un token para cada usuario que este registrado
|
|
|
|
en la app. Este token es el que será devuelto cada vez que se llame al nuevo endopint.
|
|
|
|
|
|
|
|
Para ello, crear el archivo [./backend/ecommerce/signals.py](./backend/ecommerce/signals.py).
|
|
|
|
|
|
|
|
```py
|
|
|
|
from django.db.models.signals import post_save
|
|
|
|
from django.dispatch import receiver
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
from rest_framework.authtoken.models import Token
|
|
|
|
|
|
|
|
@receiver(post_save, sender=User, weak=False)
|
|
|
|
def report_uploaded(sender, instance, created, **kwargs):
|
|
|
|
if created:
|
|
|
|
Token.objects.create(user=instance)
|
|
|
|
```
|
|
|
|
|
|
|
|
> En Django se le llama señal al código que recibe una señal de cierta tabla,
|
|
|
|
basado en una acción (***pre-save*** o ***post-save***). Cuando se crea un
|
|
|
|
registro en la tabla, esta envía una señal, y es interceptada por *signals*.
|
|
|
|
En este caso, se utiliza para generar un nuevo token.
|
|
|
|
|
|
|
|
|
|
|
|
Agregar el siguiente método en la clase `EcommerceConfig` del archivo
|
|
|
|
[apps.py](./backend/ecommerce/apps.py) de la app.
|
|
|
|
|
|
|
|
```py
|
|
|
|
class EcommerceConfig(AppConfig):
|
|
|
|
...
|
|
|
|
|
|
|
|
def ready(self):
|
|
|
|
import ecommerce.signals
|
|
|
|
```
|
|
|
|
|
|
|
|
Creamos otro superusuario, para que se active el disparador de la señal, y se
|
|
|
|
cree un nuevo token para este.
|
|
|
|
|
|
|
|
```sh
|
|
|
|
./manage.py runserver
|
|
|
|
./manage.py createsuperuser
|
|
|
|
```
|
|
|
|
|
2023-03-30 15:24:14 -03:00
|
|
|
Visitar [http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin), y verificar
|
|
|
|
creación del token.
|
2023-03-30 13:02:53 -03:00
|
|
|
|
|
|
|
Probar retorno del token a traves de la API.
|
|
|
|
|
|
|
|
ej. **curl**
|
|
|
|
|
|
|
|
```sh
|
|
|
|
curl -XPOST -F 'username=<tu-usuario>' -F 'password=<tu-password>' \
|
2023-03-30 15:24:14 -03:00
|
|
|
'http://127.0.0.1:8000/api-token-auth/'
|
2023-03-30 13:02:53 -03:00
|
|
|
```
|
|
|
|
|
|
|
|
ej. **HTTPie**
|
|
|
|
|
|
|
|
```sh
|
2023-03-30 15:24:14 -03:00
|
|
|
http post http://127.0.0.1:8000/api-token-auth/ username=<tu-usuario> \
|
2023-03-30 15:49:52 -03:00
|
|
|
password=<tu-password>
|
2023-03-30 13:02:53 -03:00
|
|
|
```
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">httpie output</summary>
|
|
|
|
|
2023-03-30 13:02:53 -03:00
|
|
|
```http
|
|
|
|
HTTP/1.1 200 OK
|
|
|
|
Allow: POST, OPTIONS
|
|
|
|
Content-Length: 52
|
|
|
|
Content-Type: application/json
|
|
|
|
Cross-Origin-Opener-Policy: same-origin
|
|
|
|
Date: Thu, 29 Mar 2023 11:57:43 GMT
|
|
|
|
Referrer-Policy: same-origin
|
|
|
|
Server: WSGIServer/0.2 CPython/3.10.10
|
|
|
|
X-Content-Type-Options: nosniff
|
|
|
|
X-Frame-Options: DENY
|
|
|
|
|
|
|
|
{
|
|
|
|
"token": "2f076a6310a244283c6902a73e07a0febc59649c"
|
|
|
|
}
|
|
|
|
```
|
2023-03-30 15:24:14 -03:00
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
</details>
|
|
|
|
|
2023-03-30 15:49:52 -03:00
|
|
|
### Ecommerce Model
|
2023-03-30 15:24:14 -03:00
|
|
|
|
|
|
|
Esta app hace uso obligatorio del token de autentificación. Solo usuarios
|
|
|
|
autentificados pueden acceder a este endpoint.
|
|
|
|
|
|
|
|
La app ecommerce se construye con un endpoint **item** y otro **order**. Los
|
|
|
|
usuarios podrán recuperar elementos de la base de datos, hacer un pedido y
|
|
|
|
recuperar la información del pedido.
|
|
|
|
|
|
|
|
Se necesitan modelos, enrutadores, serializadores y vistas. (models, routers,
|
|
|
|
serializers & view/sets api/view).
|
|
|
|
|
|
|
|
Creación de [modelos](./backend/ecommerce/models.py) Item y Order. Creación
|
|
|
|
de [serializers](./backend/ecommerce/serializers.py).
|
|
|
|
|
|
|
|
Registro de app en el panel de [administración](./backend/ecommerce/admin.py).
|
|
|
|
|
|
|
|
Migraciones `./manage.py makemigrations` y `./manage.py migrate`.
|
|
|
|
|
2023-03-30 15:49:52 -03:00
|
|
|
### Ecommerce Views y URLs
|
|
|
|
|
|
|
|
Creación de [views](./backend/ecommerce/views.py) de la app, se utiliza
|
|
|
|
`IsAuthenticated` del framework.
|
|
|
|
Se agrega conjunto de vistas genericas de la app en [urls.py](./backend/drf_course/urls.py)
|
|
|
|
del sitio, registrandolas en el `router`.
|
|
|
|
|
|
|
|
```py
|
|
|
|
...
|
|
|
|
from ecommerce import views as ecommerce_views
|
|
|
|
...
|
|
|
|
|
|
|
|
router = routers.DefaultRouter()
|
|
|
|
router.register(r'item', ecommerce_views.ItemViewSet, basename='item')
|
|
|
|
router.register(r'order', ecommerce_views.OrderViewSet, basename='order')
|
|
|
|
|
|
|
|
...
|
|
|
|
```
|
|
|
|
|
2023-03-30 16:21:22 -03:00
|
|
|
### Ecommerce Tests
|
|
|
|
|
|
|
|
Creación de [test](./backend/ecommerce/tests.py) unitarios para la aplicación.
|
|
|
|
|
|
|
|
0. test setup
|
|
|
|
1. test ItemsViewSet método list
|
|
|
|
2. test ItemsViewSet método retrieve
|
|
|
|
3. test Item.check_stock cuando order.quantity > item.stock
|
|
|
|
4. test Item.check_stock cuando order.quantity == item.stock
|
|
|
|
5. test Item.check_stock cuando order.quantity < item.stock
|
|
|
|
6. test OrdersViewSet método create cuando order.quantity > item.stock
|
|
|
|
7. test OrdersViewSet método create cuando order.quantity < item.stock
|
|
|
|
8. test OrdersViewSet método create cuando order.quantity == item.stock
|
|
|
|
9. test OrdersViewSet método list
|
|
|
|
10. test OrdersViewSet método retrieve
|
|
|
|
|
|
|
|
Correr tests `./manage.py test`.
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">tests output</summary>
|
|
|
|
|
2023-03-30 16:21:22 -03:00
|
|
|
```py
|
|
|
|
Found 18 test(s).
|
|
|
|
Creating test database for alias 'default'...
|
|
|
|
System check identified no issues (0 silenced).
|
|
|
|
..................
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Ran 18 tests in 2.248s
|
|
|
|
|
|
|
|
OK
|
|
|
|
Destroying test database for alias 'default'...
|
|
|
|
```
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
</details>
|
|
|
|
|
|
|
|
</br>
|
|
|
|
|
|
|
|
[Bash script](./api_calls.sh) con llamadas a la API utilizando curl
|
|
|
|
|
2023-03-30 16:21:22 -03:00
|
|
|
----
|
|
|
|
|
2023-03-31 02:36:20 -03:00
|
|
|
<details><summary markdown="span">Jerarquía de directorios</summary>
|
2023-03-30 16:21:22 -03:00
|
|
|
|
|
|
|
```txt
|
|
|
|
📂️ .
|
|
|
|
├── 📂️ backend
|
|
|
|
│ ├── 📂️ core
|
|
|
|
│ │ ├── 📂️ migrations
|
|
|
|
│ │ │ ├── 0001_initial.py
|
|
|
|
│ │ │ └── __init__.py
|
|
|
|
│ │ ├── __init__.py
|
|
|
|
│ │ ├── admin.py
|
|
|
|
│ │ ├── apps.py
|
|
|
|
│ │ ├── models.py
|
|
|
|
│ │ ├── serializers.py
|
|
|
|
│ │ ├── tests.py
|
|
|
|
│ │ └── views.py
|
|
|
|
│ ├── 📂️ drf_course
|
|
|
|
│ │ ├── __init__.py
|
|
|
|
│ │ ├── asgi.py
|
|
|
|
│ │ ├── settings.py
|
|
|
|
│ │ ├── urls.py
|
|
|
|
│ │ └── wsgi.py
|
|
|
|
│ ├── 📂️ ecommerce
|
|
|
|
│ │ ├── 📂️ migrations
|
|
|
|
│ │ │ ├── 0001_initial.py
|
|
|
|
│ │ │ └── __init__.py
|
|
|
|
│ │ ├── __init__.py
|
|
|
|
│ │ ├── admin.py
|
|
|
|
│ │ ├── apps.py
|
|
|
|
│ │ ├── models.py
|
|
|
|
│ │ ├── serializers.py
|
|
|
|
│ │ ├── signals.py
|
|
|
|
│ │ ├── tests.py
|
|
|
|
│ │ └── views.py
|
|
|
|
│ ├── 📂️ utils
|
|
|
|
│ │ ├── __init__.py
|
|
|
|
│ │ └── model_abstracts.py
|
|
|
|
│ ├── .env
|
|
|
|
│ └── manage.py
|
|
|
|
├── .gitignore
|
2023-03-31 02:36:20 -03:00
|
|
|
├── api_calls.sh
|
2023-03-30 16:21:22 -03:00
|
|
|
├── env.template
|
|
|
|
├── README.md
|
|
|
|
└── requirements.txt
|
|
|
|
```
|
2023-03-31 02:36:20 -03:00
|
|
|
|
|
|
|
</details>
|