Implement data model

This commit is contained in:
Gabriel Augendre 2022-04-24 16:21:39 +02:00
parent 1288a30308
commit eead96359b
13 changed files with 353 additions and 1 deletions

54
poetry.lock generated
View file

@ -333,6 +333,18 @@ python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pillow"
version = "9.1.0"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
[[package]]
name = "platformdirs"
version = "2.5.2"
@ -630,7 +642,7 @@ brotli = ["brotli"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "e141bb1b1bfaaea951e57b266d4cc4619e598f853d27f40964405303000e7ea8"
content-hash = "86cec3b5cbae86ec8a2e77f3e77139e12c48bcc8aa79918daed41f56f48597f4"
[metadata.files]
asgiref = [
@ -932,6 +944,46 @@ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pillow = [
{file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"},
{file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"},
{file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"},
{file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"},
{file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"},
{file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"},
{file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"},
{file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"},
{file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"},
{file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"},
{file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"},
{file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"},
{file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"},
{file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"},
{file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"},
{file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"},
{file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"},
{file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"},
{file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},

View file

@ -16,6 +16,7 @@ requests = "^2.27.1"
django-extensions = "^3.1.5"
bpython = "^0.22.1"
gunicorn = "^20.1.0"
Pillow = "^9.1.0"
[tool.poetry.dev-dependencies]
pre-commit = "^2.7"

View file

@ -81,6 +81,7 @@ INSTALLED_APPS = [
"anymail",
"django_cleanup.apps.CleanupConfig",
"common",
"purchase",
]
MIDDLEWARE = [

0
src/purchase/__init__.py Normal file
View file

42
src/purchase/admin.py Normal file
View file

@ -0,0 +1,42 @@
from django.contrib import admin
from django.contrib.admin import register
from purchase.models import Basket, BasketItem, PaymentMethod, Product
@register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ["name", "display_order", "unit_price"]
search_fields = ["name"]
def unit_price(self, instance: Product):
return instance.unit_price_cents / 100
@register(PaymentMethod)
class PaymentMethodAdmin(admin.ModelAdmin):
list_display = ["name"]
search_fields = ["name"]
class BasketItemInline(admin.TabularInline):
model = BasketItem
fields = ["product", "quantity", "price"]
extra = 0
readonly_fields = ["price"]
def price(self, instance) -> float:
return instance.price / 100
@register(Basket)
class BasketAdmin(admin.ModelAdmin):
list_display = ["id", "status", "payment_method", "created_at", "price"]
fields = ["created_at", "status", "payment_method"]
list_filter = ["status", "payment_method"]
date_hierarchy = "created_at"
readonly_fields = ["created_at"]
inlines = [BasketItemInline]
def price(self, instance) -> float:
return instance.price / 100

6
src/purchase/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class PurchaseConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "purchase"

View file

@ -0,0 +1,131 @@
# Generated by Django 4.0.4 on 2022-04-24 14:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Basket",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"status",
models.CharField(
choices=[("DRAFT", "Draft"), ("COMPLETE", "Complete")],
default="DRAFT",
max_length=20,
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="PaymentMethod",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=50, unique=True)),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="Product",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=250, unique=True)),
("image", models.ImageField(null=True, upload_to="")),
("unit_price_cents", models.PositiveIntegerField()),
("display_order", models.PositiveIntegerField()),
],
options={
"ordering": ["display_order", "name"],
},
),
migrations.CreateModel(
name="BasketItem",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("quantity", models.PositiveIntegerField()),
(
"basket",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="items",
to="purchase.basket",
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="basket_items",
to="purchase.product",
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="basket",
name="payment_method",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="baskets",
to="purchase.paymentmethod",
),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-04-24 14:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("purchase", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="product",
name="image",
field=models.ImageField(blank=True, null=True, upload_to=""),
),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 4.0.4 on 2022-04-24 14:17
from django.db import migrations, models
import purchase.models
class Migration(migrations.Migration):
dependencies = [
("purchase", "0002_alter_product_image"),
]
operations = [
migrations.AlterField(
model_name="product",
name="display_order",
field=models.PositiveIntegerField(
default=purchase.models.default_product_display_order
),
),
]

View file

73
src/purchase/models.py Normal file
View file

@ -0,0 +1,73 @@
from django.db import models
class Model(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class PaymentMethod(Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
def default_product_display_order():
return Product.objects.last().display_order + 1
class Product(Model):
name = models.CharField(max_length=250, unique=True)
image = models.ImageField(null=True, blank=True)
unit_price_cents = models.PositiveIntegerField()
display_order = models.PositiveIntegerField(default=default_product_display_order)
class Meta:
ordering = ["display_order", "name"]
def __str__(self):
return self.name
class Basket(Model):
STATUS_DRAFT = "DRAFT"
STATUS_COMPLETE = "COMPLETE"
_STATUS_CHOICES = [
(STATUS_DRAFT, "Draft"),
(STATUS_COMPLETE, "Complete"),
]
payment_method = models.ForeignKey(
to=PaymentMethod,
on_delete=models.PROTECT,
related_name="baskets",
null=True,
blank=True,
)
status = models.CharField(
max_length=20, choices=_STATUS_CHOICES, default=STATUS_DRAFT
)
def __str__(self):
return f"Panier #{self.id}"
@property
def price(self) -> int:
return sum(item.price for item in self.items.all())
class BasketItem(Model):
product = models.ForeignKey(
to=Product, on_delete=models.PROTECT, related_name="basket_items"
)
basket = models.ForeignKey(
to=Basket, on_delete=models.CASCADE, related_name="items"
)
quantity = models.PositiveIntegerField()
@property
def price(self) -> int:
return self.product.unit_price_cents * self.quantity

3
src/purchase/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
src/purchase/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.