diff --git a/.gitignore b/.gitignore index 4207541..516613b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ *.egg-info tmp/ +db/ diff --git a/kaba/__init__.py b/kaba/__init__.py index 194f574..a300c77 100644 --- a/kaba/__init__.py +++ b/kaba/__init__.py @@ -1,4 +1,3 @@ -import os from time import perf_counter import ujson as json @@ -6,11 +5,10 @@ import hupper import minicli from bson import ObjectId from jinja2 import Environment, PackageLoader, select_autoescape -from pymongo import MongoClient from roll import Roll, Response from roll.extensions import cors, options, traceback, simple_server -from .base import Document, Str, Float, Array, Email, Int, Reference, Datetime, Mapping +from .models import Delivery, Order, Person, Product, ProductOrder class Response(Response): @@ -45,65 +43,6 @@ env = Environment( ) -class Producer(Document): - __collection__ = "producers" - name = Str(required=True) - - @property - def products(self): - return Product.find(producer=self._id) - - -class Product(Document): - __collection__ = "products" - producer = Reference(Producer, required=True) - name = Str(required=True) - ref = Str(required=True) - description = Str() - price = Float(required=True) - - -class Person(Document): - __collection__ = "persons" - first_name = Str() - last_name = Str() - email = Email() - - -class ProductOrder(Document): - wanted = Int() - ordered = Int() - - -class PersonOrder(Document): - person = Str() - products = Mapping(str, ProductOrder) - - def get_quantity(self, product): - choice = self.products.get(product.ref) - return choice.wanted if choice else 0 - - def total(self, products): - products = {p.ref: p for p in products} - return round( - sum(p.wanted * products[ref].price for ref, p in self.products.items()), 2 - ) - - -class Order(Document): - __collection__ = "orders" - when = Datetime(required=True) - where = Str() - producer = Reference(Producer, required=True) - products = Array(Product) - orders = Mapping(str, PersonOrder) - - def product_wanted(self, product): - return round( - sum([po.products[product.ref].wanted for po in self.orders.values()]) - ) - - app = Roll() cors(app, methods="*", headers="*") options(app) @@ -121,66 +60,88 @@ async def on_startup(): @app.route("/", methods=["GET"]) async def home(request, response): - response.html("home.html", {"orders": Order.find()}) + response.html("home.html", deliveries=Delivery.all()) -@app.route("/commande/{order_id}/total", methods=["GET"]) -async def view_order(request, response, order_id): - order = Order.find_one(_id=ObjectId(order_id)) - total = round(sum(po.total(order.products) for po in order.orders.values()), 2) - response.html( - "order.html", - { - "order": order, - "producer": Producer.find_one(_id=order.producer), - "total": total, - }, - ) +@app.route("/livraison/new", methods=["GET"]) +async def new_delivery(request, response): + response.html("edit_delivery.html", delivery={}) -@app.route("/commande/{order_id}", methods=["GET"]) -async def order_form(request, response, order_id): - order = Order.find_one(_id=ObjectId(order_id)) - email = request.query.get("email") - person_order = order.orders.get(email) - response.html( - "place_order.html", - { - "order": order, - "person": email, - "person_order": person_order, - "producer": Producer.find_one(_id=order.producer), - }, - ) - - -@app.route("/commande/{order_id}", methods=["POST"]) -async def place_order(request, response, order_id): - order = Order.find_one(_id=ObjectId(order_id)) - email = request.query.get("email") - person_order = PersonOrder(person=email) +@app.route("/livraison/new", methods=["POST"]) +async def create_delivery(request, response): form = request.form - for product in order.products: + data = {} + for name, field in Delivery.__dataclass_fields__.items(): + if name in form: + data[name] = form.get(name) + delivery = Delivery(**data) + delivery.persist() + response.status = 302 + response.headers["Location"] = f"/livraison/{delivery.id}" + + +@app.route("/livraison/{id}/edit", methods=["GET"]) +async def edit_delivery(request, response, id): + delivery = Delivery.load(id) + response.html("edit_delivery.html", {"delivery": delivery}) + + +@app.route("/livraison/{id}/edit", methods=["POST"]) +async def post_delivery(request, response, id): + delivery = Delivery.load(id) + form = request.form + for name, field in Delivery.__dataclass_fields__.items(): + if name in form: + setattr(delivery, name, form.get(name)) + delivery.persist() + response.status = 302 + response.headers["Location"] = f"/livraison/{delivery.id}" + + +@app.route("/livraison/{id}", methods=["GET"]) +async def view_delivery(request, response, id): + delivery = Delivery.load(id) + total = round(sum(o.total(delivery.products) for o in delivery.orders.values()), 2) + response.html("delivery.html", {"delivery": delivery, "total": total}) + + +@app.route("/livraison/{id}/commander", methods=["GET"]) +async def order_form(request, response, id): + delivery = Delivery.load(id) + email = request.query.get("email") + order = delivery.orders.get(email) + response.html( + "place_order.html", {"delivery": delivery, "person": email, "order": order} + ) + + +@app.route("/livraison/{id}/commander", methods=["POST"]) +async def place_order(request, response, id): + delivery = Delivery.load(id) + email = request.query.get("email") + order = Order() + form = request.form + for product in delivery.products: quantity = form.int(product.ref, 0) if quantity: - person_order.products[product.ref] = ProductOrder(wanted=quantity) - if not order.orders: - order.orders = {} - order.orders[email] = person_order - order.replace_one() + order.products[product.ref] = ProductOrder(wanted=quantity) + if not delivery.orders: + delivery.orders = {} + delivery.orders[email] = order + delivery.persist() response.headers["Location"] = request.url.decode() response.status = 302 def connect(): - db = os.environ.get("KABA_DB", "mongodb://localhost/kaba") - client = MongoClient(db) - db = client.get_database() - Producer.bind(db) - Product.bind(db) - Order.bind(db) - Person.bind(db) - return client + # db = os.environ.get("KABA_DB", "mongodb://localhost/kaba") + # client = MongoClient(db) + # db = client.get_database() + # Person.bind(db) + # Delivery.bind(db) + # return client + pass @minicli.cli() @@ -194,11 +155,11 @@ def shell(): start_ipython( argv=[], user_ns={ - "Producer": Producer, "app": app, "Product": Product, "Person": Person, "Order": Order, + "Delivery": Delivery, }, ) diff --git a/kaba/base.py b/kaba/base.py index 0034d34..6ec656c 100644 --- a/kaba/base.py +++ b/kaba/base.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, date from bson import ObjectId @@ -63,6 +63,17 @@ class Datetime(Field): return datetime.fromtimestamp(value) +class Date(Field): + @staticmethod + def coerce(value): + if isinstance(value, date): + return value + if isinstance(value, datetime): + return value.date() + if isinstance(value, int): + return date.fromtimestamp(value) + + class Email(Field): @staticmethod def coerce(value): @@ -126,10 +137,13 @@ class Array(Field): class MetaDocument(type): def __new__(cls, name, bases, attrs): + fields = getattr(cls, "_fields", {}) for attr_name, attr_value in attrs.items(): if not isinstance(attr_value, Field): continue + fields[attr_name] = attr_value attr_value.name = attr_name + cls._fields = fields return super().__new__(cls, name, bases, attrs) @@ -137,9 +151,6 @@ class Document(dict, metaclass=MetaDocument): __db__ = None __collection__ = None - # def __repr__(self): - # return f"<{self.__class__.__name__} {self._id}>" - def __init__(self, data=None, **attrs): if data: for key, value in data.items(): diff --git a/kaba/templates/home.html b/kaba/templates/home.html index 67dfc06..c1910b5 100644 --- a/kaba/templates/home.html +++ b/kaba/templates/home.html @@ -1,15 +1,15 @@ {% extends "base.html" %} {% block body %} - {% for order in orders %} -

Producteur: {{ order.producer }}

-

Lieu: {{ order.where }}

-

Date: {{ order.when }}

-
- - + {% for delivery in deliveries %} +

Producteur: {{ delivery.producer }}

+

Lieu: {{ delivery.where }}

+

Date: {{ delivery.when }}

+ + +
- Résumé de la commande + Résumé de la livraison {% endfor %} {% endblock body %} diff --git a/kaba/templates/order.html b/kaba/templates/order.html deleted file mode 100644 index 3ac47d4..0000000 --- a/kaba/templates/order.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -

< Commandes

-

Producteur: {{ producer.name }}

-

Lieu: {{ order.where }}

-

Date: {{ order.when }}

- - - - - {% for email, person_order in order.orders.items() %} - - {% endfor %} - - - {% for product in order.products %} - - - - {% for email, person_order in order.orders.items() %} - {% if product.ref in person_order.products %} - - {% else %} - - {% endif %} - {% endfor %} - - - {% endfor %} -
ProduitPrix{{ email }}Total
{{ product.name }}{{ product.price }} €{{ person_order.products[product.ref].wanted }}{{ order.product_wanted(product) }}
-

Total: {{ total }} €

-{% endblock body %} diff --git a/kaba/templates/place_order.html b/kaba/templates/place_order.html index 2874721..8c4db0e 100644 --- a/kaba/templates/place_order.html +++ b/kaba/templates/place_order.html @@ -2,18 +2,18 @@ {% block body %}

< Commandes

-

Producteur: {{ producer.name }}

-

Lieu: {{ order.where }}

-

Date: {{ order.when }}

+

Producteur: {{ delivery.producer }}

+

Lieu: {{ delivery.where }}

+

Date: {{ delivery.when }}

Commande de «{{ person }}»

- {% for product in order.products %} - + {% for product in delivery.products %} + {% endfor %}
ProduitPrixQuantité
{{ product.name }}{{ product.price }} €
{{ product.name }}{{ product.price }} €
-

Total: {{ person_order.total(order.products) if person_order else 0 }} €

+

Total: {{ order.total(delivery.products) if order else 0 }} €

diff --git a/tests/conftest.py b/tests/conftest.py index bfd9dde..f91071f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,26 +3,36 @@ import os import pytest from roll.extensions import traceback -# Before loading any eurordis module. -os.environ["KABA_DB"] = "mongodb://localhost/kaba_test" - from kaba import app as kaba_app -from kaba import connect, Producer +from kaba import utils +from kaba.models import Delivery, Person def pytest_configure(config): - client = connect() - client.drop_database("test_kaba") + from kaba import models + models.Base.ROOT = "tmp/db" -def pytest_runtest_setup(item): - # assert get_db().name == "test_eurordis" - for cls in [Producer]: - collection = cls.collection - collection.drop() +# def pytest_runtest_setup(item): +# # assert get_db().name == "test_eurordis" +# for cls in [Delivery]: +# collection = cls.collection +# collection.drop() @pytest.fixture def app(): # Requested by Roll testing utilities. traceback(kaba_app) return kaba_app + + +@pytest.fixture +def delivery(): + return Delivery( + producer="Andines", when=utils.utcnow(), order_before=utils.utcnow() + ) + + +@pytest.fixture +def person(): + return Person(email="foo@bar.fr") diff --git a/tests/test_models.py b/tests/test_models.py index 81f21f9..b042354 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,23 +1,72 @@ -from kaba import Producer, Order, Product +import pytest + +from kaba import utils +from kaba.models import Delivery, Product, Person, Order, ProductOrder -def test_can_create_producer(): - producer = Producer(name="Andines") - producer.insert_one() - assert producer.name == "Andines" - retrieved = Producer.find_one(name="Andines") - assert retrieved.name == producer.name - assert retrieved._id == producer._id +def test_can_create_delivery(): + delivery = Delivery( + producer="Andines", when=utils.utcnow(), order_before=utils.utcnow() + ) + assert delivery.producer == "Andines" + assert delivery.where == "Marché de la Briche" + assert delivery.when.year == utils.utcnow().year + assert delivery.id -def test_can_create_order(): - order = Order(products=[Product(name="riz", price="2.4")]) - order.insert_one() - retrieved = Order.find_one(_id=order._id) - assert retrieved.products[0].name == "riz" +def test_wrong_datetime_raise_valueerror(): + with pytest.raises(ValueError): + Delivery(producer="Andines", order_before=utils.utcnow(), when="pouet") -def test_can_update_order_products(): - order = Order() - order.products.append(Product(name="riz", price="2.4")) +def test_can_create_product(): + product = Product(name="Lait 1.5L", ref="123", price=1.5) + assert product.ref == "123" + assert product.price == 1.5 + + +def test_can_create_delivery_with_products(): + delivery = Delivery( + producer="Andines", + when=utils.utcnow(), + order_before=utils.utcnow(), + products=[Product(name="Lait", ref="123", price=1.5)], + ) + assert len(delivery.products) == 1 + assert delivery.products[0].ref == "123" + + +def test_can_add_product_to_delivery(delivery): + assert not delivery.products + delivery.products.append(Product(name="Chocolat", ref="choco", price=10)) + assert len(delivery.products) == 1 + + +def test_can_create_person(): + person = Person(email="foo@bar.fr", first_name="Foo") + assert person.email == "foo@bar.fr" + assert person.first_name == "Foo" + + +def test_can_create_order_with_products(): + order = Order(products={"123": ProductOrder(wanted=2)}) assert len(order.products) == 1 + assert order.products["123"].wanted == 2 + + +def test_can_add_product_to_order(): + order = Order() + assert len(order.products) == 0 + order.products["123"] = ProductOrder(wanted=2) + assert order.products["123"].wanted == 2 + + +def test_can_persist_delivery(delivery): + delivery.persist() + + +def test_can_load_delivery(delivery): + delivery.producer = "Corto" + delivery.persist() + loaded = Delivery.load(delivery.id) + assert loaded.producer == "Corto"