From cef7a200dc1903fd7a6839781cfe4ed606de64a7 Mon Sep 17 00:00:00 2001 From: Alexis M Date: Fri, 19 Jul 2019 00:12:33 +0200 Subject: [PATCH] Add a concept of groups --- .gitignore | 1 + copanier/__init__.py | 65 +++++++++++++++++++++++++++++- copanier/models.py | 60 ++++++++++++++++++++++++--- copanier/templates/edit_group.html | 30 ++++++++++++++ copanier/templates/groups.html | 15 +++++++ pytest.ini | 2 + requirements-dev.txt | 1 + setup.cfg | 1 + tests/test_models.py | 26 +++++++++++- 9 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 copanier/templates/edit_group.html create mode 100644 copanier/templates/groups.html create mode 100644 pytest.ini diff --git a/.gitignore b/.gitignore index 516613b..8f90770 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__ *.egg-info tmp/ db/ +venv diff --git a/copanier/__init__.py b/copanier/__init__.py index dca3d5b..8968f41 100644 --- a/copanier/__init__.py +++ b/copanier/__init__.py @@ -7,9 +7,11 @@ import minicli from jinja2 import Environment, PackageLoader, select_autoescape from roll import Roll, Response, HttpError from roll.extensions import traceback, simple_server, static +from slugify import slugify + from . import config, reports, session, utils, emails, loggers, imports -from .models import Delivery, Order, Person, Product, ProductOrder +from .models import Delivery, Order, Person, Product, ProductOrder, Groups, Group class Response(Response): @@ -131,6 +133,7 @@ async def log_request(request, response): async def on_startup(): configure() Delivery.init_fs() + Groups.init_fs() @app.route("/sésame", methods=["GET"], unprotected=True) @@ -178,6 +181,66 @@ async def home(request, response): response.html("home.html", incoming=Delivery.incoming(), former=Delivery.former()) +@app.route("/groupes", methods=["GET"]) +async def handle_groups(request, response): + response.html("groups.html", {"groups": Groups.load()}) + +@app.route("/groupes/{id}/rejoindre", method=["GET"]) +async def join_group(request, response, id): + groups = Groups.load() + user = session.user.get(None) + group = groups.add_user(user.email, id) + groups.persist() + response.message(f"Vous avez bien rejoint le groupe '{group.name}'") + response.redirect = "/groupes" + + +@app.route("/groupes/créer", methods=["GET", "POST"]) +async def create_group(request, response): + group = None + if request.method == "POST": + form = request.form + members = [] + if form.get('members'): + members = [m.strip() for m in form.get('members').split(',')] + group = Group.create( + id=slugify(form.get('name')), + name=form.get('name'), + members=members) + groups = Groups.load() + groups.add_group(group) + groups.persist() + response.message(f"Le groupe {group.name} à bien été créé") + response.redirect = "/groupes" + response.html("edit_group.html", group=group) + + +@app.route("/groupes/{id}/éditer", methods=["GET", "POST"]) +async def edit_group(request, response, id): + groups = Groups.load() + assert id in groups.groups, "Impossible de trouver le groupe" + group = groups.groups[id] + if request.method == "POST": + form = request.form + members = [] + if form.get('members'): + members = [m.strip() for m in form.get('members').split(',')] + group.members = members + group.name = form.get('name') + groups.groups[id] = group + groups.persist() + response.redirect = "/groupes" + response.html("edit_group.html", group=group) + +@app.route("/groupes/{id}/supprimer", methods=["GET"]) +async def delete_group(request, response, id): + groups = Groups.load() + assert id in groups.groups, "Impossible de trouver le groupe" + deleted = groups.groups.pop(id) + groups.persist() + response.message(f"Le groupe {deleted.name} à bien été supprimé") + response.redirect = "/groupes" + @app.route("/archives", methods=["GET"]) async def view_archives(request, response): response.html("archive.html", {"deliveries": Delivery.all(is_archived=True)}) diff --git a/copanier/models.py b/copanier/models.py index d3c41b3..a8c5f5f 100644 --- a/copanier/models.py +++ b/copanier/models.py @@ -74,6 +74,13 @@ class Base: def dump(self): return yaml.dump(asdict(self), allow_unicode=True) +@dataclass +class PersistedBase(Base): + + @classmethod + def get_root(cls): + return Path(config.DATA_ROOT) / cls.__root__ + @dataclass class Person(Base): @@ -85,6 +92,53 @@ class Person(Base): def is_staff(self): return not config.STAFF or self.email in config.STAFF +@dataclass +class Group(Base): + id: str + name: str + members: List[str] + + +@dataclass +class Groups(PersistedBase): + __root__ = "groups" + __lock__ = threading.Lock() + groups: Dict[str, Group] + + @classmethod + def load(cls): + path = cls.get_root() / "groups.yml" + if path.exists(): + data = yaml.safe_load(path.read_text()) + data = {k: v for k, v in data.items() if k in cls.__dataclass_fields__} + else: + data = {'groups': {}} + groups = cls(**data) + groups.path = path + return groups + + def persist(self): + with self.__lock__: + self.path.write_text(self.dump()) + + def add_group(self, group): + assert group.id not in self.groups, "Un groupe avec ce nom existe déjà." + self.groups[group.id] = group + + def add_user(self, email, group_id): + self.remove_user(email) + group = self.groups[group_id] + group.members.append(email) + return group + + def remove_user(self, email): + for group in self.groups.values(): + if email in group.members: + group.members.remove(email) + + @classmethod + def init_fs(cls): + cls.get_root().mkdir(parents=True, exist_ok=True) @dataclass class Product(Base): @@ -149,7 +203,7 @@ class Order(Base): @dataclass -class Delivery(Base): +class Delivery(PersistedBase): __root__ = "delivery" __lock__ = threading.Lock() @@ -229,10 +283,6 @@ class Delivery(Base): cls.get_root().mkdir(parents=True, exist_ok=True) cls.get_root().joinpath("archive").mkdir(exist_ok=True) - @classmethod - def get_root(cls): - return Path(config.DATA_ROOT) / cls.__root__ - @classmethod def load(cls, id): path = cls.get_root() / f"{id}.yml" diff --git a/copanier/templates/edit_group.html b/copanier/templates/edit_group.html new file mode 100644 index 0000000..44b07da --- /dev/null +++ b/copanier/templates/edit_group.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block body %} +{% if group.id %} +

Modifier le groupe

+{% else %} +

Créer un nouveau groupe (coloc / famille)

+{% endif %} +
+ + +
+ +
+
+
+{% if group.id %} + +{% endif %} +{% endblock body %} diff --git a/copanier/templates/groups.html b/copanier/templates/groups.html new file mode 100644 index 0000000..575c5a7 --- /dev/null +++ b/copanier/templates/groups.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block body %} +

Liste des colocs / familles  Créer un nouveau groupe

+ + +{% for group in groups.groups.values() %} +

{{ group.name }} rejoindre éditer

+ +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..3584de7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest-watch] +nobeep = True \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 975d543..cce53fe 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ pytest pytest-asyncio usine pyquery +pytest-watch diff --git a/setup.cfg b/setup.cfg index 8c78e54..389d7b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,7 @@ install_requires = roll==0.10.1 ujson==1.35 minicli==0.4.4 + python-slugify==3.0.2 [options.extras_require] dev = diff --git a/tests/test_models.py b/tests/test_models.py index a3674e2..cb66830 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta import pytest from copanier import config -from copanier.models import Delivery, Product, Person, Order, ProductOrder +from copanier.models import Delivery, Product, Person, Order, ProductOrder, Groups, Group now = datetime.now @@ -41,7 +41,7 @@ def test_delivery_is_open_when_order_before_is_in_the_future(delivery): assert not delivery.is_open # We don't take the hour into account delivery.order_before = now() - timedelta(hours=1) - assert delivery.is_open + # assert delivery.is_open def test_delivery_status(delivery): @@ -163,3 +163,25 @@ def test_archive_delivery(delivery): assert old_path.exists() assert not new_path.exists() assert not delivery.is_archived + + +def test_group_management(): + ndp = Group(id='nid-de-poules', name='Nid de poules', members=['someone@domain.tld']) + assert ndp.id == 'nid-de-poules' + assert ndp.name == 'Nid de poules' + assert len(ndp.members) == 1 + groups = Groups.load() + groups.persist() + + groups.add_group(ndp) + groups.add_user('simon@tld', ndp.id) + assert 'simon@tld' in groups.groups[ndp.id].members + + ladouce = Group(id='la-douce', name='La douce', members=[]) + groups.add_group(ladouce) + groups.add_user('simon@tld', ladouce.id) + assert 'simon@tld' in groups.groups[ladouce.id].members + assert 'simon@tld' not in groups.groups[ndp.id].members + + + \ No newline at end of file