From 1609d8ae29b71e6e05c7f02a7e8d394c63787e05 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 25 Nov 2021 15:40:15 +0100 Subject: [PATCH 1/4] [utils] Add util to get pagination filter args from request --- flaschengeist/utils/HTTP.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/flaschengeist/utils/HTTP.py b/flaschengeist/utils/HTTP.py index 8fe57a9..e81316b 100644 --- a/flaschengeist/utils/HTTP.py +++ b/flaschengeist/utils/HTTP.py @@ -2,6 +2,24 @@ from http.client import NO_CONTENT, CREATED from flask import make_response, jsonify +from flaschengeist.utils.datetime import from_iso_format + + +def get_filter_args(): + """ + Get filter parameter from request + returns: FROM, TO, LIMIT, OFFSET, DESCENDING + """ + from flask import request + + return ( + request.args.get("from", type=from_iso_format), + request.args.get("to", type=from_iso_format), + request.args.get("limit", type=int), + request.args.get("offset", type=int), + "descending" in request.args, + ) + def no_content(): return make_response(jsonify(""), NO_CONTENT) From 1c091311de7d3250b596d8b2ed9e6053c53d07a0 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 25 Nov 2021 15:43:02 +0100 Subject: [PATCH 2/4] [events] Use new pagination responses, drop unused api endpoint --- .../plugins/events/event_controller.py | 8 ++- flaschengeist/plugins/events/routes.py | 63 ++----------------- 2 files changed, 11 insertions(+), 60 deletions(-) diff --git a/flaschengeist/plugins/events/event_controller.py b/flaschengeist/plugins/events/event_controller.py index 4cd2d03..8de2ed7 100644 --- a/flaschengeist/plugins/events/event_controller.py +++ b/flaschengeist/plugins/events/event_controller.py @@ -147,7 +147,7 @@ def get_events( offset: Optional[int] = None, descending: Optional[bool] = False, with_backup=False, -): +) -> Tuple[int, list[Event]]: """Query events which start from begin until end Args: start (datetime): Earliest start @@ -161,10 +161,14 @@ def get_events( query = query.filter(start <= Event.start) if end is not None: query = query.filter(Event.start < end) + elif start is None: + # Neither start nor end was given + query = query.filter(datetime.now() <= Event.start) if descending: query = query.order_by(Event.start.desc()) else: query = query.order_by(Event.start) + count = query.count() if limit is not None: query = query.limit(limit) if offset is not None and offset > 0: @@ -173,7 +177,7 @@ def get_events( if not with_backup: for event in events: clear_backup(event) - return events + return count, events def delete_event(event_id): diff --git a/flaschengeist/plugins/events/routes.py b/flaschengeist/plugins/events/routes.py index 2fb9b60..71c18d9 100644 --- a/flaschengeist/plugins/events/routes.py +++ b/flaschengeist/plugins/events/routes.py @@ -1,8 +1,6 @@ from datetime import datetime, timedelta, timezone from http.client import NO_CONTENT -from re import template from flask import request, jsonify -from sqlalchemy import exc from werkzeug.exceptions import BadRequest, NotFound, Forbidden from flaschengeist.models.session import Session @@ -12,7 +10,7 @@ from flaschengeist.utils.datetime import from_iso_format from flaschengeist.controller import userController from . import event_controller, permissions, EventPlugin -from ...utils.HTTP import no_content +from ...utils.HTTP import get_filter_args, no_content def dict_get(self, key, default=None, type=None): @@ -194,63 +192,12 @@ def get_event(event_id, current_session): @EventPlugin.blueprint.route("/events", methods=["GET"]) @login_required() -def get_filtered_events(current_session): - begin = request.args.get("from", type=from_iso_format) - end = request.args.get("to", type=from_iso_format) - limit = request.args.get("limit", type=int) - offset = request.args.get("offset", type=int) - descending = "descending" in request.args - if begin is None and end is None: - begin = datetime.now() - return jsonify( - event_controller.get_events( - start=begin, - end=end, - limit=limit, - offset=offset, - descending=descending, +def get_events(current_session): + count, result = event_controller.get_events( + *get_filter_args(), with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), - ) ) - - -@EventPlugin.blueprint.route("/events//", methods=["GET"]) -@EventPlugin.blueprint.route("/events///", methods=["GET"]) -@login_required() -def get_events(current_session, year=datetime.now().year, month=datetime.now().month, day=None): - """Get Event objects for specified date (or month or year), - if nothing set then events for current month are returned - - Route: ``/events[//[/]]`` | Method: ``GET`` - - Args: - year (int, optional): year to query, defaults to current year - month (int, optional): month to query (if set), defaults to current month - day (int, optional): day to query events for (if set) - current_session: Session sent with Authorization Header - - Returns: - JSON encoded list containing events found or HTTP-error - """ - try: - begin = datetime(year=year, month=month, day=1, tzinfo=timezone.utc) - if day: - begin += timedelta(days=day - 1) - end = begin + timedelta(days=1) - else: - if month == 12: - end = datetime(year=year + 1, month=1, day=1, tzinfo=timezone.utc) - else: - end = datetime(year=year, month=month + 1, day=1, tzinfo=timezone.utc) - - events = event_controller.get_events( - begin, - end, - with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), - ) - return jsonify(events) - except ValueError: - raise BadRequest("Invalid date given") + return jsonify({"count": count, "result": result}) def _add_job(event, data): From aa64c769ef2bdfd10b5fadb829edd4aac617835d Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 25 Nov 2021 15:44:10 +0100 Subject: [PATCH 3/4] [events] Implemented API endpoint for jobs of the current user --- .../plugins/events/event_controller.py | 20 ++++++++++++++++++- flaschengeist/plugins/events/models.py | 3 ++- flaschengeist/plugins/events/routes.py | 11 +++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/flaschengeist/plugins/events/event_controller.py b/flaschengeist/plugins/events/event_controller.py index 8de2ed7..46dcb33 100644 --- a/flaschengeist/plugins/events/event_controller.py +++ b/flaschengeist/plugins/events/event_controller.py @@ -1,6 +1,6 @@ from datetime import datetime, timedelta, timezone from enum import IntEnum -from typing import Optional +from typing import Optional, Tuple from werkzeug.exceptions import BadRequest, Conflict, NotFound from sqlalchemy.exc import IntegrityError @@ -225,6 +225,24 @@ def get_job(job_id, event_id=None) -> Job: return job +def get_jobs(user, start=None, end=None, limit=None, offset=None, descending=None) -> Tuple[int, list[Job]]: + query = Job.query.join(Service).filter(Service.user_ == user) + if start is not None: + query = query.filter(start <= Job.end) + if end is not None: + query = query.filter(end >= Job.start) + if descending is not None: + query = query.order_by(Job.start.desc(), Job.type_id_) + else: + query = query.order_by(Job.start, Job.type_id_) + count = query.count() + if limit is not None: + query = query.limit(limit) + if offset is not None: + query = query.offset(offset) + return count, query.all() + + def add_job(event, job_type, required_services, start, end=None, comment=None): job = Job( required_services=required_services, diff --git a/flaschengeist/plugins/events/models.py b/flaschengeist/plugins/events/models.py index 36fd3f9..97c06ca 100644 --- a/flaschengeist/plugins/events/models.py +++ b/flaschengeist/plugins/events/models.py @@ -58,7 +58,6 @@ class Service(db.Model, ModelSerializeMixin): class Job(db.Model, ModelSerializeMixin): __tablename__ = _table_prefix_ + "job" - _type_id = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False) id: int = db.Column(Serial, primary_key=True) start: datetime = db.Column(UtcDateTime, nullable=False) @@ -73,6 +72,8 @@ class Job(db.Model, ModelSerializeMixin): event_ = db.relationship("Event", back_populates="jobs") event_id_ = db.Column("event_id", Serial, db.ForeignKey(f"{_table_prefix_}event.id"), nullable=False) + type_id_ = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False) + invitations_ = db.relationship("Invitation", cascade="all,delete,delete-orphan", back_populates="job_") __table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),) diff --git a/flaschengeist/plugins/events/routes.py b/flaschengeist/plugins/events/routes.py index 71c18d9..fe7b2f9 100644 --- a/flaschengeist/plugins/events/routes.py +++ b/flaschengeist/plugins/events/routes.py @@ -1,10 +1,8 @@ -from datetime import datetime, timedelta, timezone from http.client import NO_CONTENT from flask import request, jsonify from werkzeug.exceptions import BadRequest, NotFound, Forbidden from flaschengeist.models.session import Session -from flaschengeist.plugins.events.models import Job from flaschengeist.utils.decorators import login_required from flaschengeist.utils.datetime import from_iso_format from flaschengeist.controller import userController @@ -195,7 +193,7 @@ def get_event(event_id, current_session): def get_events(current_session): count, result = event_controller.get_events( *get_filter_args(), - with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), + with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), ) return jsonify({"count": count, "result": result}) @@ -396,6 +394,13 @@ def update_job(event_id, job_id, current_session: Session): return jsonify(job) +@EventPlugin.blueprint.route("/events/jobs", methods=["GET"]) +@login_required() +def get_jobs(current_session: Session): + count, result = event_controller.get_jobs(current_session.user_, *get_filter_args()) + return jsonify({"count": count, "result": result}) + + @EventPlugin.blueprint.route("/events/jobs//assign", methods=["POST"]) @login_required() def assign_job(job_id, current_session: Session): From e626239d8484eecd9d2b9a76199ca800e357f26e Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 25 Nov 2021 15:44:37 +0100 Subject: [PATCH 4/4] [cleanup] Minor pep8 cleanup --- flaschengeist/plugins/auth_ldap/__init__.py | 2 +- .../plugins/events/event_controller.py | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/flaschengeist/plugins/auth_ldap/__init__.py b/flaschengeist/plugins/auth_ldap/__init__.py index ebe84c3..29659bf 100644 --- a/flaschengeist/plugins/auth_ldap/__init__.py +++ b/flaschengeist/plugins/auth_ldap/__init__.py @@ -114,7 +114,7 @@ class AuthLDAP(AuthPlugin): } ) if user.display_name: - attributes.update( {"displayName": user.display_name}) + attributes.update({"displayName": user.display_name}) ldap_conn.add(dn, self.object_classes, attributes) self._set_roles(user) self.update_user(user) diff --git a/flaschengeist/plugins/events/event_controller.py b/flaschengeist/plugins/events/event_controller.py index 46dcb33..432a5a8 100644 --- a/flaschengeist/plugins/events/event_controller.py +++ b/flaschengeist/plugins/events/event_controller.py @@ -336,7 +336,16 @@ def respond_invitation(invite: Invitation, accepted=True): raise Conflict if not accepted: - EventPlugin.plugin.notify(inviter, _("Invitation rejected"), {"type": NotifyType.INVITATION_REJECTED, "event": job.event_id_, "job": invite.job_id, "invitee": invite.invitee_id}) + EventPlugin.plugin.notify( + inviter, + _("Invitation rejected"), + { + "type": NotifyType.INVITATION_REJECTED, + "event": job.event_id_, + "job": invite.job_id, + "invitee": invite.invitee_id, + }, + ) else: if invite.transferee_id is None: assign_job(job, invite.invitee_, 1) @@ -346,7 +355,16 @@ def respond_invitation(invite: Invitation, accepted=True): raise Conflict unassign_job(job, invite.transferee_, service[0], True) assign_job(job, invite.invitee_, service[0].value) - EventPlugin.plugin.notify(inviter, _("Invitation accepted"), {"type": NotifyType.INVITATION_ACCEPTED, "event": job.event_id_, "job": invite.job_id, "invitee": invite.invitee_id}) + EventPlugin.plugin.notify( + inviter, + _("Invitation accepted"), + { + "type": NotifyType.INVITATION_ACCEPTED, + "event": job.event_id_, + "job": invite.job_id, + "invitee": invite.invitee_id, + }, + ) @scheduled