From 4a4930d683c4f584a772fd0fe9570685ba53710c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 18 Nov 2020 02:47:40 +0100 Subject: [PATCH] [Plugin] Schedule: Mostly final backend implemented. Tested. --- flaschengeist/plugins/schedule/__init__.py | 167 ++++-------------- .../plugins/schedule/event_controller.py | 68 +++---- flaschengeist/plugins/schedule/models.py | 59 +++---- 3 files changed, 88 insertions(+), 206 deletions(-) diff --git a/flaschengeist/plugins/schedule/__init__.py b/flaschengeist/plugins/schedule/__init__.py index f0d27df..e1b3a81 100644 --- a/flaschengeist/plugins/schedule/__init__.py +++ b/flaschengeist/plugins/schedule/__init__.py @@ -15,6 +15,8 @@ from flaschengeist.controller import userController from . import event_controller, permissions from . import models +from ... import logger +from ...utils.HTTP import no_content schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule") @@ -213,7 +215,7 @@ def get_events(current_session, year=datetime.now().year, month=datetime.now().m raise BadRequest("Invalid date given") -def _add_event_slot(event, data): +def _add_job(event, data): end = None try: start = from_iso_format(data["start"]) @@ -221,11 +223,11 @@ def _add_event_slot(event, data): raise BadRequest("Missing POST parameter") if "end" in data: end = from_iso_format(data["end"]) - event_slot = event_controller.add_event_slot(event, start, end) - if "jobs" in data: - for job_data in data["jobs"]: - job_type = event_controller.get_job_type(job_data["type"]) - event_controller.add_job_slot(event_slot, job_type, job_data["required_jobs"]) + + if "required_services" not in data: + raise BadRequest + job_type = event_controller.get_job_type(data["type"]) + event_controller.add_job(event, job_type, data["required_services"], start, end, comment=data.get("comment")) @schedule_bp.route("/events", methods=["POST"]) @@ -235,7 +237,7 @@ def create_event(current_session): Route: ``/schedule/events`` | Method: ``POST`` - POST-data: See interfaces for Event, can already contain slots + POST-data: See interfaces for Event, can already contain jobs Args: current_session: Session sent with Authorization Header @@ -254,9 +256,9 @@ def create_event(current_session): event = event_controller.create_event( start=start, event_type=event_type, description=data["description"] if "description" in data else None ) - if "slots" in data: - for slot in data["slots"]: - _add_event_slot(event, slot) + if "jobs" in data: + for job in data["jobs"]: + _add_job(event, job) return jsonify(event) @@ -307,14 +309,14 @@ def delete_event(event_id, current_session): return "", NO_CONTENT -@schedule_bp.route("/events//slots", methods=["POST"]) +@schedule_bp.route("/events//jobs", methods=["POST"]) @login_required(permission=permissions.EDIT) -def add_event_slot(event_id, current_session): - """Add an new EventSlot to an Event +def add_job(event_id, current_session): + """Add an new Job to an Event / EventSlot - Route: ``/schedule/events//slots`` | Method: ``POST`` + Route: ``/schedule/events//jobs`` | Method: ``POST`` - POST-data: See TS interface for EventSlot + POST-data: See Job Args: event_id: Identifier of the event @@ -324,139 +326,48 @@ def add_event_slot(event_id, current_session): JSON encoded Event object or HTTP-error """ event = event_controller.get_event(event_id) - data = request.get_json() - if not data: - raise BadRequest("Missing POST parameters") - - _add_event_slot(event, data) + _add_job(event, request.get_json()) return jsonify(event) -@schedule_bp.route("/events//slots/", methods=["PUT"]) -@login_required(permission=permissions.EDIT) -def update_event_slot(event_id, slot_id, current_session): - """Update an EventSlot - - Route: ``/schedule/events//slots/`` | Method: ``PUT`` - - POST-data: See TS interface for EventSlot - - Args: - event_id: Identifier of the event - slot_id: Identifier of the slot - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - event = event_controller.get_event(event_id) - slot = event_controller.get_event_slot(slot_id) - if slot not in event.slots: - raise NotFound - - data = _event_slot_from_data(request.get_json()) - slot.start = data["start"] - if "end" in data: - slot.end = data["end"] - event_controller.update() - return jsonify(event) - - -@schedule_bp.route("/events//slots/", methods=["DELETE"]) -@login_required(permission=permissions.EDIT) -def delete_event_slot(event_id, slot_id, current_session): - """Delete an EventSlot - - Route: ``/schedule/events//slots/`` | Method: ``DELETE`` - - Args: - event_id: Identifier of the event - slot_id: Identifier of the slot - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - event = event_controller.get_event(event_id) - event_controller.remove_event_slot(event, slot_id) - return jsonify(event) - - -@schedule_bp.route("/events//slots//jobs", methods=["POST"]) -@login_required(permission=permissions.EDIT) -def add_job_slot(event_id, slot_id, current_session): - """Add an new JobSlot to an Event / EventSlot - - Route: ``/schedule/events//slots//jobs`` | Method: ``POST`` - - POST-data: ``{type: string, required_jobs: number}`` - - Args: - event_id: Identifier of the event - slot_id: Identifier of the slot - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - event = event_controller.get_event(event_id) - slot = event_controller.get_event_slot(slot_id) - if slot not in event.slots: - raise NotFound - - data = request.get_json() - try: - job_type = event_controller.get_job_type(data["type"]) - required_jobs = data["required_jobs"] - except (KeyError, ValueError): - raise BadRequest("Missing POST parameters") - - event_controller.add_job_slot(slot, job_type, required_jobs) - return jsonify(event) - - -@schedule_bp.route("/events//slots//jobs/", methods=["DELETE"]) +@schedule_bp.route("/events//jobs/", methods=["DELETE"]) @login_required(permission=permissions.DELETE) -def delete_job_slot(event_id, slot_id, job_type, current_session): - """Delete a JobSlot +def delete_job(event_id, job_id, current_session): + """Delete a Job - Route: ``/schedule/events//slots//jobs/`` | Method: ``DELETE`` + Route: ``/events//jobs/`` | Method: ``DELETE`` Args: event_id: Identifier of the event - slot_id: Identifier of the EventSlot - job_type: Identifier of the JobSlot + job_id: Identifier of the Job current_session: Session sent with Authorization Header Returns: - JSON encoded Event object or HTTP-error + HTTP-no-content or HTTP error """ - event = event_controller.get_event(event_id) - job_slot = event_controller.get_job_slot(slot_id, job_type) - event_controller.delete_job_slot(job_slot) - return jsonify(event) + job_slot = event_controller.get_job(job_id, event_id) + event_controller.delete_job(job_slot) + return no_content() -@schedule_bp.route("/events//slots//jobs/", methods=["PUT"]) +@schedule_bp.route("/events//jobs/", methods=["PUT"]) @login_required() -def update_job_slot(event_id, slot_id, job_type, current_session: Session): - """Edit JobSlot or add user to the Slot +def update_job(event_id, job_id, current_session: Session): + """Edit Job or assign user to the Job - Route: ``/schedule/events//slots//jobs/`` | Method: ``PUT`` + Route: ``/events//jobs/`` | Method: ``PUT`` - POST-data: See TS interface for EventSlot or ``{user: {userid: string, value: number}}`` + POST-data: See TS interface for Job or ``{user: {userid: string, value: number}}`` Args: event_id: Identifier of the event - slot_id: Identifier of the slot - job_type: Identifier of the JobSlot + job_id: Identifier of the Job current_session: Session sent with Authorization Header Returns: JSON encoded Event object or HTTP-error """ - event = event_controller.get_event(event_id) - slot = event_controller.get_job_slot(slot_id, job_type) + job = event_controller.get_job(job_id, event_id) data = request.get_json() if not data: @@ -473,17 +384,17 @@ def update_job_slot(event_id, slot_id, job_type, current_session: Session): user != current_session._user and not current_session._user.has_permission(permissions.ASSIGN_OTHER) ): raise Forbidden - event_controller.assign_job(slot, user, value) + event_controller.assign_to_job(job, user, value) except (KeyError, ValueError): raise BadRequest - if "required_jobs" in data: - slot.required_jobs = data["required_jobs"] + if "required_services" in data: + job.required_services = data["required_services"] if "type" in data: - slot.type = event_controller.get_job_type(data["type"]) + job.type = event_controller.get_job_type(data["type"]) event_controller.update() - return jsonify(event) + return jsonify(job.event_) # TODO: JobTransfer diff --git a/flaschengeist/plugins/schedule/event_controller.py b/flaschengeist/plugins/schedule/event_controller.py index b9f0775..44cd246 100644 --- a/flaschengeist/plugins/schedule/event_controller.py +++ b/flaschengeist/plugins/schedule/event_controller.py @@ -3,7 +3,7 @@ from sqlalchemy.exc import IntegrityError from flaschengeist import logger from flaschengeist.database import db -from flaschengeist.plugins.schedule.models import EventType, Event, EventSlot, JobSlot, JobType, Job +from flaschengeist.plugins.schedule.models import EventType, Event, Job, JobType, Service def update(): @@ -55,6 +55,7 @@ def get_job_types(): def get_job_type(type_id): job_type = JobType.query.get(type_id) + print(job_type) if not job_type: raise NotFound return job_type @@ -119,10 +120,10 @@ def delete_event(event_id): db.session.commit() -def create_event(event_type, start, slots=[], description=None): +def create_event(event_type, start, jobs=[], description=None): try: logger.debug(event_type) - event = Event(start=start, description=description, type=event_type, slots=slots) + event = Event(start=start, description=description, type=event_type, jobs=jobs) db.session.add(event) db.session.commit() return event @@ -131,59 +132,38 @@ def create_event(event_type, start, slots=[], description=None): raise BadRequest -def get_event_slot(slot_id): - slot = EventSlot.query.get(slot_id) - if slot is None: +def get_job(job_slot_id, event_id): + js = Job.query.filter(Job.id == job_slot_id).filter(Job._event_id == event_id).one_or_none() + if js is None: raise NotFound - return slot + return js -def add_event_slot(event, start, end=None): - event_slot = EventSlot(start=start, end=end, event_=event) - if start < event.start: - raise BadRequest("Start before event start") - db.session.add(event_slot) - db.session.commit() - return event_slot +def add_job(event, job_type, required_services, start, end=None, comment=None): + job = Job(required_services=required_services, type=job_type, start=start, end=end, comment=comment) + event.jobs.append(job) + update() + return job -def remove_event_slot(event, slot_id): - slot = get_event_slot(slot_id) - if slot in event.slots: - event.slots.remove(slot) - else: - raise NotFound - db.session.commit() - - -def get_job_slot(event_slot_id, job_type): - jt = ( - JobSlot.query.filter(JobSlot._type_id == job_type).filter(JobSlot._event_slot_id == event_slot_id).one_or_none() - ) - if jt is None: - raise NotFound - return jt - - -def add_job_slot(event_slot, job_type, required_jobs): - job_slot = JobSlot(type=job_type, required_jobs=required_jobs, event_slot_=event_slot) +def update(): try: - db.session.add(job_slot) db.session.commit() except IntegrityError: - raise BadRequest("JobSlot with that type already exists on this EventSlot") + logger.debug("Error, looks like a Job with that type already exists on an event", exc_info=True) + raise BadRequest() -def delete_job_slot(job_slot): - db.session.delete(job_slot) +def delete_job(job: Job): + db.session.delete(job) db.session.commit() -def assign_job(job_slot, user, value): - job = Job.query.get((job_slot.id_, user._id)) - if job: - job.value = value +def assign_to_job(job: Job, user, value): + service = Service.query.get((job.id, user._id)) + if service: + service.value = value else: - job = Job(user_=user, value=value, slot_=job_slot) - db.session.add(job) + service = Service(user_=user, value=value, job_=job) + db.session.add(service) db.session.commit() diff --git a/flaschengeist/plugins/schedule/models.py b/flaschengeist/plugins/schedule/models.py index 62cbaac..e09b81c 100644 --- a/flaschengeist/plugins/schedule/models.py +++ b/flaschengeist/plugins/schedule/models.py @@ -13,13 +13,13 @@ from flaschengeist.database import db class EventType(db.Model, ModelSerializeMixin): - __tablename__ = "event_type" + __tablename__ = "schedule_event_type" id_: int = db.Column("id", db.Integer, primary_key=True) name: str = db.Column(db.String(30), nullable=False, unique=True) class JobType(db.Model, ModelSerializeMixin): - __tablename__ = "job_type" + __tablename__ = "schedule_job_type" id: int = db.Column("id", db.Integer, primary_key=True) name: str = db.Column(db.String(30), nullable=False, unique=True) @@ -29,35 +29,38 @@ class JobType(db.Model, ModelSerializeMixin): ######## -class Job(db.Model, ModelSerializeMixin): - __tablename__ = "job" +class Service(db.Model, ModelSerializeMixin): + __tablename__ = "schedule_service" userid: str = "" value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False) - _slot_id = db.Column("slot_id", db.Integer, db.ForeignKey("job_slot.id"), nullable=False, primary_key=True) + _job_id = db.Column("job_id", db.Integer, db.ForeignKey("schedule_job.id"), nullable=False, primary_key=True) _user_id = db.Column("user_id", db.Integer, db.ForeignKey("user.id"), nullable=False, primary_key=True) user_: User = db.relationship("User") - slot_ = db.relationship("JobSlot") + job_ = db.relationship("Job") @property def userid(self): return self.user_.userid -class JobSlot(db.Model, ModelSerializeMixin): - __tablename__ = "job_slot" - _type_id = db.Column("type_id", db.Integer, db.ForeignKey("job_type.id"), nullable=False) - _event_slot_id = db.Column("event_slot_id", db.Integer, db.ForeignKey("event_slot.id"), nullable=False) +class Job(db.Model, ModelSerializeMixin): + __tablename__ = "schedule_job" + _type_id = db.Column("type_id", db.Integer, db.ForeignKey("schedule_job_type.id"), nullable=False) + _event_id = db.Column("event_id", db.Integer, db.ForeignKey("schedule_event.id"), nullable=False) - id_: int = db.Column("id", db.Integer, primary_key=True) + id: int = db.Column("id", db.Integer, primary_key=True) + start: datetime = db.Column(UtcDateTime, nullable=False) + end: Optional[datetime] = db.Column(UtcDateTime) + comment: str = db.Column(db.String(256)) type: JobType = db.relationship("JobType") - users: [Job] = db.relationship("Job", back_populates="slot_") - required_jobs: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False)) + services: [Service] = db.relationship("Service", back_populates="job_") + required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False) - event_slot_ = db.relationship("EventSlot", back_populates="jobs") + event_ = db.relationship("Event", back_populates="jobs") - __table_args__ = (UniqueConstraint("type_id", "event_slot_id", name="_type_event_slot_uc"),) + __table_args__ = (UniqueConstraint("type_id", "start", name="_type_start_uc"),) ########## @@ -65,30 +68,18 @@ class JobSlot(db.Model, ModelSerializeMixin): ########## -class EventSlot(db.Model, ModelSerializeMixin): - """Model for an EventSlot""" - - __tablename__ = "event_slot" - _event_id = db.Column("event_id", db.Integer, db.ForeignKey("event.id"), nullable=False) - - id: int = db.Column(db.Integer, primary_key=True) - start: datetime = db.Column(UtcDateTime) - end: Optional[datetime] = db.Column(UtcDateTime) - jobs: [JobSlot] = db.relationship("JobSlot", back_populates="event_slot_") - - event_ = db.relationship("Event", back_populates="slots") - - class Event(db.Model, ModelSerializeMixin): """Model for an Event""" - __tablename__ = "event" - _type_id = db.Column("type_id", db.Integer, db.ForeignKey("event_type.id", ondelete="CASCADE"), nullable=False) + __tablename__ = "schedule_event" + _type_id = db.Column( + "type_id", db.Integer, db.ForeignKey("schedule_event_type.id", ondelete="CASCADE"), nullable=False + ) id: int = db.Column(db.Integer, primary_key=True) start: datetime = db.Column(UtcDateTime, nullable=False) - description: Optional[str] = db.Column(db.String(240)) + description: Optional[str] = db.Column(db.String(255)) type: EventType = db.relationship("EventType") - slots: [EventSlot] = db.relationship( - "EventSlot", back_populates="event_", cascade="all,delete,delete-orphan", order_by="EventSlot.start" + jobs: [Job] = db.relationship( + "Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]" )