Creación del primer endpoint

- Modelo contacto en core/models.py
- Creación utils/model_abstracts para heredar y usar id con valor uuid
- Serializador en core/serializers hereda de rest_frameworks.serializers
- Vista contacto en core/views.py
- Registro de app en admin
- Migraciones
This commit is contained in:
devfzn 2023-03-29 18:35:23 -03:00
parent a36f93289f
commit ca2156ef37
Signed by: devfzn
GPG Key ID: E070ECF4A754FDB1
10 changed files with 227 additions and 9 deletions

119
README.md
View File

@ -26,20 +26,21 @@ pip install djangorestframework
```py ```py
pip install django-extensions djangorestframework djangorestframework-jsonapi \ 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 facilitar el desarrolo, en este proyecto se
utliza MariaDB, pero es opcional. utliza MariaDB, pero es opcional.
# MariaDB ```py
pip install mysqlclient pip install mysqlclient
``` ```
### Inicio del proyecto ### Inicio del proyecto
**Creación del proyecto Django**
```sh ```sh
mkdir backend mkdir backend
# Creación del proyecto Django
django-admin startproject drf_course backend django-admin startproject drf_course backend
``` ```
@ -58,7 +59,7 @@ python manage.py startapp core
Archivo [./backend/drf_course/settings.py](./backend/drf_course/settings.py). Archivo [./backend/drf_course/settings.py](./backend/drf_course/settings.py).
Importar `.env` usando *python-dotenv*. Importar variables de entorno usando *python-dotenv* del archivo en `.backend/.env`.
```py ```py
from dotenv import load_dotenv from dotenv import load_dotenv
@ -120,7 +121,7 @@ REST_FRAMEWORK = {
``` ```
En caso de utilizar MariaDB, cambiar la declaración de *DATABASES*, para usar En caso de utilizar MariaDB, cambiar la declaración de *DATABASES*, para usar
las variables de entorno declaradas en [./backend/env](./backend/.env) las variables de entorno declaradas en `./backend/.env`
```py ```py
DATABASES = { DATABASES = {
@ -151,3 +152,111 @@ urlpatterns += [
] ]
``` ```
#### Migrar y probar aplicación
```sh
python manage.py migrate
python manage.py runserver
```
### Creación del primer endoint
Creación de endpoint de ***contacto***, para que un usuario pueda enviar su
*nombre*, *email*, y *mensaje* al backend.
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/`.
#### Model
[./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
un campo *uuid* como campo id. Además de un campo *email* de Django. Y método de
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*.
#### Serializer
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)
en la app *core*. Este hereda de la clase *serializers* del modulo *rest_framework*
e implementa sus campos (*CharField* *EmailField*).
#### View
[./backend/core/views.py](./backend/core/views.py)
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.
#### Route & URL
[./drf_course/urls.py](./drf_course/urls.py)
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.
```py
python manage.py makemigrations
python manage.py migrate
```
Finalmente, crear **super usuario**.
```py
python manage.py createsuperuser
```
#### Probar API
```sh
http http://127.0.0.1:8000/contact/ name="DevFzn" message="prueba" email="devfzn@mail.com"
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"
}
}
```

View File

@ -1,3 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Contact
# Register your models here. @admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'description', 'email')

View File

@ -0,0 +1,33 @@
# Generated by Django 4.1.7 on 2023-03-29 19:55
from django.db import migrations, models
import django_extensions.db.fields
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Contact',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('title', models.CharField(max_length=255, verbose_name='title')),
('description', models.TextField(blank=True, null=True, verbose_name='description')),
('status', models.IntegerField(choices=[(0, 'Inactive'), (1, 'Active')], default=1, verbose_name='status')),
('activate_date', models.DateTimeField(blank=True, help_text='keep empty for an immediate activation', null=True)),
('deactivate_date', models.DateTimeField(blank=True, help_text='keep empty for indefinite activation', null=True)),
('email', models.EmailField(max_length=254, verbose_name='Email')),
],
options={
'verbose_name_plural': 'Contacts',
},
),
]

View File

@ -1,3 +1,17 @@
from django.db import models from django.db import models
from utils.model_abstracts import Model
from django_extensions.db.models import (
TimeStampedModel,
ActivatorModel,
TitleDescriptionModel
)
# Create your models here. class Contact(TimeStampedModel, ActivatorModel, TitleDescriptionModel, Model):
class Meta:
verbose_name_plural = "Contacts"
email = models.EmailField(verbose_name="Email")
def __str__(self):
return f'{self.title}'

View File

@ -0,0 +1,17 @@
from . import models
from rest_framework import serializers
from rest_framework.fields import CharField, EmailField
class ContactSerializer(serializers.ModelSerializer):
name = CharField(source="title", required=True)
message = CharField(source="description", required=True)
email = EmailField(required=True)
class Meta:
model = models.Contact
fields = (
'name',
'email',
'message'
)

View File

@ -1,3 +1,34 @@
from django.shortcuts import render from json import JSONDecodeError
from django.http import JsonResponse
from .serializers import ContactSerializer
from rest_framework.parsers import JSONParser
from rest_framework import views, status
from rest_framework.response import Response
# Create your views here. class ContactAPIView(views.APIView):
"""
Simple APIView para creación de entradas de contacto.
"""
serializer_class = ContactSerializer
def get_serializer_context(self):
return { 'request': self.request,
'format': self.format_kwarg,
'view': self }
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def post(self, request):
try:
data = JSONParser().parse(request)
serializer = ContactSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except JSONDecodeError:
return JsonResponse({"resutl": "error", "message": "Json decoding error"},
status=400)

View File

@ -1,6 +1,7 @@
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 rest_framework import routers
from core import views as core_views
router = routers.DefaultRouter() router = routers.DefaultRouter()
@ -8,4 +9,5 @@ urlpatterns = router.urls
urlpatterns += [ urlpatterns += [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('contact/', core_views.ContactAPIView.as_view()),
] ]

View File

View File

@ -0,0 +1,8 @@
import uuid
from django.db import models
class Model(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
class Meta:
abstract = True

View File

@ -7,6 +7,7 @@ djangorestframework==3.14.0
djangorestframework-jsonapi==6.0.0 djangorestframework-jsonapi==6.0.0
inflection==0.5.1 inflection==0.5.1
Markdown==3.4.3 Markdown==3.4.3
mysqlclient==2.1.1
Pygments==2.14.0 Pygments==2.14.0
python-dotenv==1.0.0 python-dotenv==1.0.0
pytz==2023.2 pytz==2023.2