Recipe API, tests, model & create

This commit is contained in:
devfzn 2023-10-09 01:25:27 -03:00
parent f8ac495c2d
commit f4c53c0718
Signed by: devfzn
GPG Key ID: E070ECF4A754FDB1
9 changed files with 197 additions and 13 deletions

117
README2.md Normal file
View File

@ -0,0 +1,117 @@
# Recipe API
#### Caracteristicas
- Crear
- Listar
- Ver detalles
- Actualizar
- Borrar
#### Endpoints
- `/recipes/`
- `GET` Listar todas las recetas
- `POST` Crea recetas
- `/recipes/<recipe_id>`/
- `GET` Ver detalles de receta
- `PUT/PATCH` Actualizar receta
- `DELETE` Borrar receta
### APIView vs Viewsets
Una vista maneja un request a una URL, DRF usa clases con lógica reutilizable.
DRF además soporta decoradores.
`APIView` y `Viewsets` son clases base falicitadas por DRF.
### APIView
- Concentradas alrededor de los metodos HTTP
- Métodos de clase para los métodos HTTP `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
- Ofrece flexibilidad sobre toda la estuctura de las URL y la lógica usada para
procesar estas peticiones
- Util para APIs sin CRUD. Lógica a la medida, ej. auth, jobs, apis externas
### Viewsets
- Concentradas alrededor de aciones
- Retrive, list, update, partial update, destroy
- Mapea los modelos de Django
- Usa rutas para generar URLs
- Genial para operaciones CRUD en los modelos
## Test Create Recipe
[core/tests/test_models.py](./app/core/tests/test_models.py)
```py
from decimal import Decimal
...
from core import models
class ModelTests(TestCase):
...
def test_create_recipe(self):
"""Test creating a recipe is successful."""
user = get_user_model().objects.create_user(
'test@example.com',
'test123',
)
recipe = models.Recipe.objects.create(
user=user,
title='Nombre receta ejemplo',
time_minutes=5,
price=Decimal('5.50'),
decription='Descripción de la receta de ejemplo'
)
self.assertEqual(str(recipe), recipe.title)
```
## Creación del modelo
[`core/models.py`](./app/core/models.py)
```py
...
class Recipe(models.Model):
"""Recipe object."""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
time_minutes = models.IntegerField()
price = models.DecimalField(max_digits=5,decimal_places=2, blank=True)
link = models.CharField(max_length=255, blank=True)
def __str__(self):
return self.title
```
### Agregar al panel de administración
[`core/admin.py`](./app/core/admin.py)
```py
...
admin.site.register(models.Recipe)
```
### Crear migraciones
`docker compose run --rm app sh -c "python manage.py makemigrations"`
```sh
[+] Creating 1/0
✔ Container recipes_api_django-db-1 Running 0.0s
Migrations for 'core':
core/migrations/0002_recipe.py
- Create model Recipe
```

View File

@ -44,3 +44,4 @@ class UserAdmin(BaseUserAdmin):
admin.site.register(models.User, UserAdmin)
admin.site.register(models.Recipe)

View File

@ -0,0 +1,27 @@
# Generated by Django 4.2.5 on 2023-10-09 04:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Recipe',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('time_minutes', models.IntegerField()),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=5)),
('link', models.CharField(blank=True, max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,6 +1,7 @@
"""
Databse models.
"""
from django.conf import settings
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
@ -42,3 +43,19 @@ class User(AbstractBaseUser, PermissionsMixin):
objects = UserManager()
USERNAME_FIELD = 'email'
class Recipe(models.Model):
"""Recipe object."""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
time_minutes = models.IntegerField()
price = models.DecimalField(max_digits=5,decimal_places=2, blank=True)
link = models.CharField(max_length=255, blank=True)
def __str__(self):
return self.title

View File

@ -1,9 +1,12 @@
"""
Test for models.
"""
from decimal import Decimal
from django.test import TestCase
from django.contrib.auth import get_user_model
from core import models
class ModelTests(TestCase):
"""Test models."""
@ -44,3 +47,19 @@ class ModelTests(TestCase):
)
self.assertTrue(user.is_superuser)
self.assertTrue(user.is_staff)
def test_create_recipe(self):
"""Test creating a recipe is successful."""
user = get_user_model().objects.create_user(
'test@example.com',
'test123',
)
recipe = models.Recipe.objects.create(
user=user,
title='Nombre receta ejemplo',
time_minutes=5,
price=Decimal('5.50'),
description='Descripción de la receta de ejemplo'
)
self.assertEqual(str(recipe), recipe.title)

View File

@ -2,7 +2,7 @@
Seralizers for the user API View
"""
from django.contrib.auth import get_user_model, authenticate
from django.utils.translation import gettext as _, trim_whitespace
from django.utils.translation import gettext as _ # , trim_whitespace
from rest_framework import serializers

View File

@ -12,6 +12,7 @@ CREATE_USER_URL = reverse('user:create')
TOKEN_URL = reverse('user:token')
ME_URL = reverse('user:me')
def create_user(**params):
"""Create and return a new user."""
return get_user_model().objects.create_user(**params)
@ -128,7 +129,8 @@ class PrivateUserApiTests(TestCase):
res.data, {
'name': self.user.name,
'email': self.user.email,
})
}
)
def test_post_me_not_allowed(self):
"""Test POST is not allowed for the 'me' endpoint."""

View File

@ -18,6 +18,7 @@ class CreateTokenView(ObtainAuthToken):
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
class ManageUserView(generics.RetrieveUpdateAPIView):
"""Manage the autenticated user."""
serializer_class = UserSerializer