new: first commit !minor

This commit is contained in:
cătălin 2021-01-25 02:27:02 +01:00
commit 5848134067
45 changed files with 2754 additions and 0 deletions

0
order/__init__.py Normal file
View file

8
order/admin.py Normal file
View file

@ -0,0 +1,8 @@
from django.contrib import admin
# Register your models here.
from order.models import SaleOrder, SaleOrderLine, Product
admin.site.register(SaleOrder)
admin.site.register(SaleOrderLine)
admin.site.register(Product)

5
order/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class OrderConfig(AppConfig):
name = "order"

54
order/fixtures/order.json Normal file
View file

@ -0,0 +1,54 @@
[
{
"model": "django.contrib.auth.models.User",
"pk": 100,
"fields": {
"username": "alovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "alovelace@berni.ni",
"password": "alovelace"
}
},
{
"model": "django.contrib.auth.models.User",
"pk": 200,
"fields": {
"username": "ltorvalds",
"first_name": "Linus",
"last_name": "Torvalds",
"email": "ltorvalds@berni.ni",
"password": "ltorvalds"
}
},
{
"model": "django.contrib.auth.models.User",
"pk": 300,
"fields": {
"username": "dritchie",
"first_name": "Dennis",
"last_name": "Ritchie",
"email": "dritchie@berni.ni",
"password": "dritchie"
}
},
{
"model": "django.contrib.auth.models.User",
"pk": 400,
"fields": {
"username": "kthompson",
"first_name": "Ken",
"last_name": "Thompson",
"email": "kthompson@berni.ni",
"password": "kthompson"
}
},
{
"model": "SaleOrder",
"pk": 1,
"fields": {
"name": "Ken's first sale order",
"user": 400
}
}
]

View file

@ -0,0 +1,78 @@
# Generated by Django 3.1.5 on 2021-01-19 21:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Product",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=200)),
("unit_price", models.IntegerField(default=0)),
],
),
migrations.CreateModel(
name="SaleOrder",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=200)),
(
"state",
models.CharField(
choices=[
("quoation", "Quotation"),
("sale", "Sold"),
("cancel", "Cancelled"),
],
max_length=200,
),
),
],
),
migrations.CreateModel(
name="SaleOrderLine",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=200)),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.DO_NOTHING,
to="order.product",
),
),
],
),
]

View file

@ -0,0 +1,55 @@
# Generated by Django 3.1.5 on 2021-01-24 23:04
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('order', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='saleorder',
name='state',
),
migrations.AddField(
model_name='saleorder',
name='sold_at',
field=models.DateTimeField(default=None, max_length=200, null=True),
),
migrations.AddField(
model_name='saleorder',
name='sold_to',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='saleorder',
name='total',
field=models.FloatField(default=0.0, max_length=200),
),
migrations.AddField(
model_name='saleorderline',
name='order',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='order.saleorder'),
),
migrations.AddField(
model_name='saleorderline',
name='quantity',
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name='saleorder',
name='name',
field=models.CharField(max_length=200, unique=True),
),
migrations.AlterField(
model_name='saleorderline',
name='product',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='order.product'),
),
]

View file

50
order/models.py Normal file
View file

@ -0,0 +1,50 @@
from datetime import datetime
from django.contrib.auth.models import User
from django.db import models
class BaseModel(models.Model):
class Meta:
abstract = True
class ReadonlyMeta:
readonly = []
class SaleOrder(BaseModel):
class ReadonlyMeta:
readonly = ["sold_to", "sold_at", "total"]
name = models.CharField(max_length=200, unique=True)
sold_to = models.ForeignKey(User, on_delete=models.DO_NOTHING, default=None, null=True)
total = models.FloatField(max_length=200, default=0.0)
sold_at = models.DateTimeField(max_length=200, null=True, default=None)
@property
def amount_total(self) -> float:
return sum(
map(
lambda x: x.product.unit_price * x.quantity,
self.saleorderline_set.all(),
)
)
def sell(self, user: User):
if not self.sold_at:
self.sold_at = datetime.now()
self.sold_to = user
self.total = self.amount_total
self.save()
return self
class Product(models.Model):
name = models.CharField(max_length=200)
unit_price = models.IntegerField(default=0)
class SaleOrderLine(models.Model):
name = models.CharField(max_length=200)
product = models.ForeignKey(Product, on_delete=models.DO_NOTHING, null=True)
order = models.ForeignKey(SaleOrder, on_delete=models.CASCADE, null=True)
quantity = models.IntegerField(default=1)

21
order/serializers.py Normal file
View file

@ -0,0 +1,21 @@
from rest_framework import serializers
from order.models import SaleOrder, SaleOrderLine, Product
class SaleOrderSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SaleOrder
fields = ["id", "name", "sold_to", "sold_at"]
class SaleOrderLineSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SaleOrderLine
fields = ["id", "name", "product", "order", "quantity"]
class ProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
fields = ["id", "name", "unit_price"]

107
order/tests.py Normal file
View file

@ -0,0 +1,107 @@
from django.contrib.auth.models import User
from django.test import TestCase, Client
from rest_framework import status
from rest_framework.utils import json
from order.models import SaleOrder, SaleOrderLine, Product
from order.serializers import SaleOrderSerializer
class OrderTests(TestCase):
def setUp(self) -> None:
self.product_1: Product = Product.objects.create(name="Product 1", unit_price=10.0)
self.product_2: Product = Product.objects.create(name="Product 2", unit_price=20.0)
self.product_3: Product = Product.objects.create(name="Product 3", unit_price=30.0)
self.product_4: Product = Product.objects.create(name="Product 4", unit_price=40.0)
self.order_1: SaleOrder = SaleOrder.objects.create(name="Order 1")
self.order_2: SaleOrder = SaleOrder.objects.create(name="Order 2")
self.line_1_1: SaleOrderLine = SaleOrderLine.objects.create(
name="Line 1/Order1", order=self.order_1, product=self.product_1
)
self.line_1_2: SaleOrderLine = SaleOrderLine.objects.create(
name="Line 2/Order 1", quantity=3, order=self.order_1, product=self.product_2
)
self.line_2_1: SaleOrderLine = SaleOrderLine.objects.create(
name="Line 1/Order2", order=self.order_2, product=self.product_3
)
self.line_2_2: SaleOrderLine = SaleOrderLine.objects.create(
name="Line 2/Order2", order=self.order_2, product=self.product_4
)
self.user_foo: User = User.objects.create(
username="foo"
)
self.user_foo.set_password("foo")
self.user_foo.save()
self.user_bar = User.objects.create(
username="bar", password="bar"
)
self.client = Client()
self.client.login(username="foo", password="foo")
def test_model_getters(self):
self.assertEqual(len(self.order_1.saleorderline_set.all()), 2)
self.assertEqual(self.order_1.total, 0.0)
self.assertEqual(self.order_1.amount_total, 70.0)
self.assertEqual(len(self.order_2.saleorderline_set.all()), 2)
self.assertEqual(self.order_2.total, 0.0)
self.assertEqual(self.order_2.amount_total, 70.0)
self.assertIsNotNone(self.line_1_1.product)
self.assertIsNotNone(self.line_1_2.product)
self.assertIsNotNone(self.line_2_1.product)
self.assertIsNotNone(self.line_2_2.product)
def test_model_sell(self):
self.assertIsNone(self.order_1.sold_at)
self.assertIsNone(self.order_1.sold_to)
self.order_1.sell(user=self.user_bar)
old_date = self.order_1.sold_at
old_user = self.order_1.sold_to
self.assertIsNotNone(self.order_1.sold_at)
self.assertIsNotNone(self.order_1.sold_to)
self.assertEqual(self.order_1.total, self.order_1.amount_total)
self.assertEqual(self.order_1.total, 70.0)
self.order_1.sell(user=self.user_bar)
self.assertEqual(self.order_1.sold_at, old_date)
self.assertEqual(self.order_1.sold_to, old_user)
def test_view_readonly(self):
response = self.client.patch(
path="/api/orders/1/",
data={
"sold_at": "foo"
},
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = self.client.patch(
path="/api/orders/1/",
data={
"sold_to": self.user_foo.id
},
content_type="application/json"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_view_total(self):
response = self.client.get(
path="/api/orders/1/total/"
)
self.assertEqual(json.loads(response.content.decode())['amount_total'], 70.0)
response = self.client.get(
path="/api/orders/2/total/"
)
self.assertEqual(json.loads(response.content.decode())['amount_total'], 70.0)
def test_view_sell(self):
response = self.client.get(
path="/api/orders/2/sold/"
)
self.order_2.refresh_from_db()
self.assertEqual(self.order_2.sold_to, self.user_foo)

113
order/views.py Normal file
View file

@ -0,0 +1,113 @@
from django.core.mail import EmailMessage
from django.shortcuts import render
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from bernini import settings
from order.models import SaleOrder, SaleOrderLine, Product
from order.serializers import (
SaleOrderSerializer,
SaleOrderLineSerializer,
ProductSerializer,
)
class SaleOrderViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows orders to be viewed or edited. A `SaleOrder` object will render the general sale
document that wraps n products a client can buy, using an intermediate model called `SaleOrderLine`, this means:
a `SaleOrder` object does not interact directly with a product, a `SaleOrderLine` is just a wrap of a product,
which holds its price-per-unit value, and a quantity for that product; lastly, on the other hand, a `SaleOrder`
object will hold the total information by doing a sum() operation with all the lines
"""
queryset = SaleOrder.objects.all().order_by("name")
serializer_class = SaleOrderSerializer
permission_classes = [permissions.IsAuthenticated]
def update(self, request, pk=None, *args, **kwargs):
order: SaleOrder = self.get_object()
for field in SaleOrder.ReadonlyMeta.readonly:
if request.data.get(field) != SaleOrderSerializer(order).data[field]:
return Response(
data=f"You can't manually set the field {field}",
status=status.HTTP_400_BAD_REQUEST
)
return super(SaleOrderViewSet, self).update(request, *args, **kwargs)
def partial_update(self, request, pk=None, *args, **kwargs):
order: SaleOrder = self.get_object()
for field in SaleOrder.ReadonlyMeta.readonly:
if request.data.get(field) != SaleOrderSerializer(order).data[field]:
return Response(
data=f"You can't manually set the field {field}",
status=status.HTTP_400_BAD_REQUEST
)
return super(SaleOrderViewSet, self).partial_update(request, *args, **kwargs)
@action(detail=True, name="Get the total amount")
def total(self, request, pk=None):
order: SaleOrder = self.get_object()
return Response({"amount_total": order.amount_total})
@action(detail=True)
def sold(self, request, pk=None):
"""
Try to sell a `SaleOrder` object, this is, the sale will have assigned
the user which is doing this operation, the persistent total attribute
will be set -`total`- and the `sold_at`
:param request:
:param pk:
:return: the object with -possibly- updated values
"""
return Response(
SaleOrderSerializer(
self.get_object().sell(request.user), context={"request": request}
).data
)
@action(detail=True)
def sent(self, request, pk=None):
order: SaleOrder = self.get_object()
if order.sold_at and order.sold_to.email and settings.EMAIL_HOST_USER:
mail = EmailMessage(
f'Order {order.name} from Zapatos Bernini',
render(
'order_sold.html',
{
'order': order
},
),
settings.EMAIL_HOST_USER,
[order.sold_to.email],
reply_to=[settings.EMAIL_HOST_USER]
)
mail.send()
return Response(status=status.HTTP_200_OK,
data=f"Email sent! You should receive it at {order.sold_to.email}")
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data="Something went wrong on our end")
class SaleOrderLineViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows orders to be viewed or edited.
"""
queryset = SaleOrderLine.objects.all().order_by("name")
serializer_class = SaleOrderLineSerializer
permission_classes = [permissions.IsAuthenticated]
class ProductViewSet(viewsets.ReadOnlyModelViewSet):
"""
API endpoint that allows orders to be viewed or edited
"""
queryset = Product.objects.all().order_by("name")
serializer_class = ProductSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]