Creación modelos Item y Order para app Ecommerce
Clase Item con atributos stock y precio; metodos amount, manage_stock, check_stock, place_order. Clase Order con atributos user, item, quantity. Registro en panel de administración. Serializadores de Item y Order + excepción APIException del framework.
This commit is contained in:
parent
fa10284b72
commit
e3b750bd83
35
README.md
35
README.md
@ -194,14 +194,14 @@ que hereda de *models* de *django.db* y utliza el campo *id*. Utiliza el campo *
|
||||
### 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)
|
||||
viceversa, se crea el archivo [./backend/core/serializers.py](./backend/core/serializers.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)
|
||||
|
||||
backend
|
||||
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
|
||||
@ -209,7 +209,7 @@ varios aspectos de las normas de la API.
|
||||
|
||||
### Route & URL
|
||||
|
||||
[./drf_course/urls.py](./drf_course/urls.py)
|
||||
[./backend/drf_course/urls.py](./backend/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
|
||||
@ -366,7 +366,7 @@ Creación de app *ecommerce*
|
||||
./manage.py startapp ecommerce
|
||||
```
|
||||
|
||||
Modificar [settings](./drf_course/settings.py) del sitio, reemplazando el
|
||||
Modificar [settings](./backend/drf_course/settings.py) del sitio, editando
|
||||
`REST_FRAMEWORK` con el siguiente código.
|
||||
> notar el nuevo `DEFAULT_AUTHENTICATION_CLASSES`.
|
||||
|
||||
@ -463,7 +463,8 @@ cree un nuevo token para este.
|
||||
./manage.py createsuperuser
|
||||
```
|
||||
|
||||
Visitar [http:127.0.0.8/admin](http:127.0.0.8/admin), y verificar creación del token.
|
||||
Visitar [http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin), y verificar
|
||||
creación del token.
|
||||
|
||||
Probar retorno del token a traves de la API.
|
||||
|
||||
@ -471,13 +472,13 @@ ej. **curl**
|
||||
|
||||
```sh
|
||||
curl -XPOST -F 'username=<tu-usuario>' -F 'password=<tu-password>' \
|
||||
'http://192.168.0.9:8000/api-token-auth/'
|
||||
'http://127.0.0.1:8000/api-token-auth/'
|
||||
```
|
||||
|
||||
ej. **HTTPie**
|
||||
|
||||
```sh
|
||||
http post http://192.168.0.9:8000/api-token-auth/ username=<tu-usuario> \
|
||||
http post http://127.0.0.1:8000/api-token-auth/ username=<tu-usuario> \
|
||||
password=<tu-password>
|
||||
```
|
||||
|
||||
@ -497,3 +498,23 @@ X-Frame-Options: DENY
|
||||
"token": "2f076a6310a244283c6902a73e07a0febc59649c"
|
||||
}
|
||||
```
|
||||
|
||||
## Ecommerce Model
|
||||
|
||||
Esta app hace uso obligatorio del token de autentificación. Solo usuarios
|
||||
autentificados pueden acceder a este endpoint.
|
||||
|
||||
La app ecommerce se construye con un endpoint **item** y otro **order**. Los
|
||||
usuarios podrán recuperar elementos de la base de datos, hacer un pedido y
|
||||
recuperar la información del pedido.
|
||||
|
||||
Se necesitan modelos, enrutadores, serializadores y vistas. (models, routers,
|
||||
serializers & view/sets api/view).
|
||||
|
||||
Creación de [modelos](./backend/ecommerce/models.py) Item y Order. Creación
|
||||
de [serializers](./backend/ecommerce/serializers.py).
|
||||
|
||||
Registro de app en el panel de [administración](./backend/ecommerce/admin.py).
|
||||
|
||||
Migraciones `./manage.py makemigrations` y `./manage.py migrate`.
|
||||
|
||||
|
@ -1,3 +1,12 @@
|
||||
from django.contrib import admin
|
||||
from . import models
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@admin.register(models.Item)
|
||||
class ItemAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'title')
|
||||
|
||||
|
||||
@admin.register(models.Order)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'item')
|
||||
|
59
backend/ecommerce/migrations/0001_initial.py
Normal file
59
backend/ecommerce/migrations/0001_initial.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Generated by Django 4.1.7 on 2023-03-30 18:07
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
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')),
|
||||
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from='title', verbose_name='slug')),
|
||||
('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)),
|
||||
('stock', models.IntegerField(default=1)),
|
||||
('price', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Item',
|
||||
'verbose_name_plural': 'Items',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
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')),
|
||||
('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)),
|
||||
('quantity', models.IntegerField(default=0)),
|
||||
('item', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ecommerce.item')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Order',
|
||||
'verbose_name_plural': 'Orders',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
]
|
@ -1,3 +1,85 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from utils.model_abstracts import Model
|
||||
from django_extensions.db.models import (
|
||||
TimeStampedModel,
|
||||
ActivatorModel,
|
||||
TitleSlugDescriptionModel
|
||||
)
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Item(TimeStampedModel, ActivatorModel, TitleSlugDescriptionModel, Model):
|
||||
"""
|
||||
ecommerce.Item
|
||||
Almacena una entrada de item para la tienda
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = 'Item'
|
||||
verbose_name_plural = 'Items'
|
||||
ordering = ["id"]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
stock = models.IntegerField(default=1)
|
||||
price = models.IntegerField(default=0)
|
||||
|
||||
def amount(self):
|
||||
"""
|
||||
Converte el precio de centavos a libras
|
||||
"""
|
||||
amount = float(self.price / 100)
|
||||
return amount
|
||||
|
||||
def manage_stock(self, qty):
|
||||
"""
|
||||
Reduce stock según `qty`
|
||||
"""
|
||||
new_stock = self.stock - int(qty)
|
||||
self.stock = new_stock
|
||||
self.save()
|
||||
|
||||
def check_stock(self, qty):
|
||||
"""
|
||||
Comprueba si la cantidad en order excede el stock
|
||||
"""
|
||||
if int(qty) > self.stock:
|
||||
return False
|
||||
return True
|
||||
|
||||
def place_order(self, user, qty):
|
||||
"""
|
||||
Realiza un order (pedido)
|
||||
"""
|
||||
if self.check_stock(qty):
|
||||
order = Order.objects.create(
|
||||
item = self,
|
||||
quantity = qty,
|
||||
user= user)
|
||||
self.manage_stock(qty)
|
||||
return order
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class Order(TimeStampedModel, ActivatorModel, Model):
|
||||
"""
|
||||
ecommerce.Order
|
||||
Almacena una entrada de order, relacionada con :model:`ecommerce.Item` y
|
||||
:model:`auth.User`.
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = 'Order'
|
||||
verbose_name_plural = 'Orders'
|
||||
ordering = ["id"]
|
||||
|
||||
user = models.ForeignKey( User, on_delete=models.CASCADE,
|
||||
null=True, blank=True )
|
||||
item = models.ForeignKey( Item,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE )
|
||||
quantity = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.item.title}'
|
||||
|
44
backend/ecommerce/serializers.py
Normal file
44
backend/ecommerce/serializers.py
Normal file
@ -0,0 +1,44 @@
|
||||
from collections import OrderedDict
|
||||
from .models import Item, Order
|
||||
from rest_framework_json_api import serializers
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
class NotEnoughStockException(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = 'Sin stock suficiente'
|
||||
default_code = 'invalido'
|
||||
|
||||
|
||||
class ItemSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
fields = (
|
||||
'title',
|
||||
'stock',
|
||||
'price',
|
||||
)
|
||||
|
||||
|
||||
class OrderSerializer(serializers.ModelSerializer):
|
||||
|
||||
item = serializers.PrimaryKeyRelatedField(queryset = Item.objects.all(), many=False)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = (
|
||||
'item',
|
||||
'quantity',
|
||||
)
|
||||
|
||||
def validate(self, res: OrderedDict):
|
||||
'''
|
||||
Utilizado para validar niveles de stock del Item
|
||||
'''
|
||||
item = res.get("item")
|
||||
quantity = res.get("quantity")
|
||||
if not item.check_stock(quantity):
|
||||
raise NotEnoughStockException
|
||||
return res
|
Loading…
Reference in New Issue
Block a user