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 %} +