creación de app ecommerce + token authentication
Editados settings.py y urls.py del sitio para app y uso de token. Implementación de token para app ecommerce en ruta /api-token-auth, uso de clase Token del rest_framework y signals de Django, para creación de token post-save.
This commit is contained in:
parent
cf3d6f49c4
commit
fa10284b72
193
README.md
193
README.md
@ -28,8 +28,8 @@ pip install django-extensions djangorestframework djangorestframework-jsonapi \
|
|||||||
inflection python-dotenv sqlparse
|
inflection python-dotenv sqlparse
|
||||||
```
|
```
|
||||||
|
|
||||||
Django utiliza SQLite3 por defecto facilitar el desarrolo, en este proyecto se
|
Django utiliza SQLite3 por defecto para simplificar el desarrolo, en este proyecto
|
||||||
utliza MariaDB, pero es opcional.
|
se utliza MariaDB, pero es opcional.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
pip install mysqlclient
|
pip install mysqlclient
|
||||||
@ -159,10 +159,10 @@ urlpatterns += [
|
|||||||
./manage.py runserver
|
./manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creación del primer endoint
|
## Creación del primer endoint
|
||||||
|
|
||||||
Creación de endpoint de ***contacto***, para que un usuario pueda enviar su
|
Creación de endpoint ***Contac***, para que un usuario pueda enviar su *nombre*,
|
||||||
*nombre*, *email*, y *mensaje* al backend.
|
*email*, y *mensaje* al backend.
|
||||||
|
|
||||||
Para ello se requiere:
|
Para ello se requiere:
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ mensaje de respuesta.
|
|||||||
- Una ruta `/url` llamada `/contact/`.
|
- Una ruta `/url` llamada `/contact/`.
|
||||||
|
|
||||||
|
|
||||||
#### Model
|
### Model
|
||||||
|
|
||||||
[./backend/core/models.py](./backend/core/models.py)
|
[./backend/core/models.py](./backend/core/models.py)
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ Utiliza modelos abstractos del modulo
|
|||||||
(campos *status*, *activated date*, *deactivated date* ), `TitleDescriptionModel`
|
(campos *status*, *activated date*, *deactivated date* ), `TitleDescriptionModel`
|
||||||
(campos de texto *textfield* y *charfield*).
|
(campos de texto *textfield* y *charfield*).
|
||||||
Clase **Contact** hereda de estos modelos. Todas las tablas del proyecto tendrán
|
Clase **Contact** hereda de estos modelos. Todas las tablas del proyecto tendrán
|
||||||
un campo *uuid* como campo id. Además de un campo *email* de Django. Y método de
|
un campo *uuid* como id. Además de un campo *email* de Django. Y método de
|
||||||
representación del modelo en cadena de texto.
|
representación del modelo en cadena de texto.
|
||||||
|
|
||||||
**Modelo abstracto**
|
**Modelo abstracto**
|
||||||
@ -191,14 +191,14 @@ 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)
|
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*.
|
que hereda de *models* de *django.db* y utliza el campo *id*. Utiliza el campo *UUID*.
|
||||||
|
|
||||||
#### Serializer
|
### Serializer
|
||||||
|
|
||||||
Para convertir los datos de entrada *json* en tipos de datos de python, y
|
Para convertir los datos de entrada *json* en tipos de datos de python, y
|
||||||
viceversa, se crea el archivo [./backend/core/serializer.py](./backend/core/serializer.py)
|
viceversa, se crea el archivo [./backend/core/serializer.py](./backend/core/serializer.py)
|
||||||
en la app *core*. Este hereda de la clase *serializers* del modulo *rest_framework*
|
en la app *core*. Este hereda de la clase *serializers* del modulo *rest_framework*
|
||||||
e implementa sus campos (*CharField* *EmailField*).
|
e implementa sus campos (*CharField* *EmailField*).
|
||||||
|
|
||||||
#### View
|
### View
|
||||||
|
|
||||||
[./backend/core/views.py](./backend/core/views.py)
|
[./backend/core/views.py](./backend/core/views.py)
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ 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
|
`.post()`. Además se pueden establecer otros atributos en la clase que controla
|
||||||
varios aspectos de las normas de la API.
|
varios aspectos de las normas de la API.
|
||||||
|
|
||||||
#### Route & URL
|
### Route & URL
|
||||||
|
|
||||||
[./drf_course/urls.py](./drf_course/urls.py)
|
[./drf_course/urls.py](./drf_course/urls.py)
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ Finalmente, crear **super usuario**.
|
|||||||
|
|
||||||
#### Prueba manual
|
#### Prueba manual
|
||||||
|
|
||||||
**Curl**
|
**curl**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -XPOST -H "Content-type: application/json" \
|
curl -XPOST -H "Content-type: application/json" \
|
||||||
@ -241,14 +241,14 @@ curl -XPOST -H "Content-type: application/json" \
|
|||||||
'http://127.0.0.1:8000/contact/'
|
'http://127.0.0.1:8000/contact/'
|
||||||
```
|
```
|
||||||
|
|
||||||
o **Httpie**
|
o **HTTPie**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
http post http://127.0.0.1:8000/contact/ name="DevFzn" message="prueba" \
|
http post http://127.0.0.1:8000/contact/ name="DevFzn" message="prueba" \
|
||||||
email="devfzn@mail.com"
|
email="devfzn@mail.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```http
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Allow: POST, OPTIONS
|
Allow: POST, OPTIONS
|
||||||
Content-Length: 155
|
Content-Length: 155
|
||||||
@ -322,19 +322,19 @@ Se puede utilizar la shell de Django para chequear la nueva entrada en Contacto
|
|||||||
|
|
||||||
Creación de pruebas en [./backend/core/tests.py](./backend/core/tests.py).
|
Creación de pruebas en [./backend/core/tests.py](./backend/core/tests.py).
|
||||||
Utilizando las clases `APIClient` que proporciona un cliente incorporado y
|
Utilizando las clases `APIClient` que proporciona un cliente incorporado y
|
||||||
`APITestCase`, similar al *TestCase* de Django
|
`APITestCase`, similar al *TestCase* de Django.
|
||||||
|
|
||||||
#### Test suite para Contact
|
#### Test suite para Contact
|
||||||
|
|
||||||
0. SetUp de los test
|
0. test setup
|
||||||
1. test ContactViewSet método create
|
1. test para método create
|
||||||
2. test ContactViewSet método create cuando nombre no está en los datos
|
2. test para método create cuando nombre no está en los datos
|
||||||
3. test ContactViewSet método create cuando nombre está en blanco
|
3. test para método create cuando nombre está en blanco
|
||||||
4. test ContactViewSet método create cuando mensaje no está en los datos
|
4. test para método create cuando mensaje no está en los datos
|
||||||
5. test ContactViewSet método create cuando mensaje está en blanco
|
5. test para método create cuando mensaje está en blanco
|
||||||
6. test ContactViewSet método create cuando email no está en los datos
|
6. test para método create cuando email no está en los datos
|
||||||
7. test ContactViewSet método create cuando email está en blanco
|
7. test para método create cuando email está en blanco
|
||||||
8. test ContactViewSet método create cuando email no es un email
|
8. test para método create cuando email no es un email
|
||||||
|
|
||||||
Correr test `./manage.py test`
|
Correr test `./manage.py test`
|
||||||
|
|
||||||
@ -350,3 +350,150 @@ OK
|
|||||||
Destroying test database for alias 'default'...
|
Destroying test database for alias 'default'...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Modificar [settings](./drf_course/settings.py) del sitio, reemplazando el
|
||||||
|
`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
|
||||||
|
```
|
||||||
|
|
||||||
|
Visitar [http:127.0.0.8/admin](http:127.0.0.8/admin), y verificar creación del token.
|
||||||
|
|
||||||
|
Probar retorno del token a traves de la API.
|
||||||
|
|
||||||
|
ej. **curl**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -XPOST -F 'username=<tu-usuario>' -F 'password=<tu-password>' \
|
||||||
|
'http://192.168.0.9:8000/api-token-auth/'
|
||||||
|
```
|
||||||
|
|
||||||
|
ej. **HTTPie**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http post http://192.168.0.9:8000/api-token-auth/ username=<tu-usuario> \
|
||||||
|
password=<tu-password>
|
||||||
|
```
|
||||||
|
|
||||||
|
```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"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -23,7 +23,9 @@ INSTALLED_APPS = [
|
|||||||
'django_extensions',
|
'django_extensions',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
'core',
|
'core',
|
||||||
|
'ecommerce',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@ -116,6 +118,9 @@ REST_FRAMEWORK = {
|
|||||||
'DEFAULT_PARSER_CLASSES': (
|
'DEFAULT_PARSER_CLASSES': (
|
||||||
'rest_framework_json_api.parsers.JSONParser',
|
'rest_framework_json_api.parsers.JSONParser',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
],
|
||||||
'DEFAULT_RENDERER_CLASSES': (
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
'rest_framework_json_api.renderers.JSONRenderer',
|
'rest_framework_json_api.renderers.JSONRenderer',
|
||||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework import routers
|
|
||||||
from core import views as core_views
|
from core import views as core_views
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
|
||||||
@ -10,4 +11,5 @@ urlpatterns = router.urls
|
|||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('contact/', core_views.ContactAPIView.as_view()),
|
path('contact/', core_views.ContactAPIView.as_view()),
|
||||||
|
path('api-token-auth/', obtain_auth_token),
|
||||||
]
|
]
|
||||||
|
0
backend/ecommerce/__init__.py
Normal file
0
backend/ecommerce/__init__.py
Normal file
3
backend/ecommerce/admin.py
Normal file
3
backend/ecommerce/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
8
backend/ecommerce/apps.py
Normal file
8
backend/ecommerce/apps.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class EcommerceConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'ecommerce'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import ecommerce.signals
|
0
backend/ecommerce/migrations/__init__.py
Normal file
0
backend/ecommerce/migrations/__init__.py
Normal file
3
backend/ecommerce/models.py
Normal file
3
backend/ecommerce/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
9
backend/ecommerce/signals.py
Normal file
9
backend/ecommerce/signals.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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)
|
3
backend/ecommerce/tests.py
Normal file
3
backend/ecommerce/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
backend/ecommerce/views.py
Normal file
3
backend/ecommerce/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in New Issue
Block a user