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:
devfzn 2023-03-30 13:02:53 -03:00
parent cf3d6f49c4
commit fa10284b72
Signed by: devfzn
GPG Key ID: E070ECF4A754FDB1
11 changed files with 207 additions and 24 deletions

193
README.md
View File

@ -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"
}
```

View File

@ -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',

View File

@ -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),
] ]

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View 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

View File

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View 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)

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.