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:
parent
a36f93289f
commit
ca2156ef37
119
README.md
119
README.md
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -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')
|
||||||
|
33
backend/core/migrations/0001_initial.py
Normal file
33
backend/core/migrations/0001_initial.py
Normal 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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -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}'
|
||||||
|
17
backend/core/serializers.py
Normal file
17
backend/core/serializers.py
Normal 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'
|
||||||
|
)
|
@ -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)
|
||||||
|
@ -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()),
|
||||||
]
|
]
|
||||||
|
0
backend/utils/__init__.py
Normal file
0
backend/utils/__init__.py
Normal file
8
backend/utils/model_abstracts.py
Normal file
8
backend/utils/model_abstracts.py
Normal 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
|
@ -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
|
Loading…
Reference in New Issue
Block a user