feature/events #18

Merged
crimsen merged 4 commits from feature/events into develop 2021-11-25 17:00:41 +00:00
5 changed files with 79 additions and 68 deletions

View File

@ -1,6 +1,6 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from enum import IntEnum from enum import IntEnum
from typing import Optional from typing import Optional, Tuple
from werkzeug.exceptions import BadRequest, Conflict, NotFound from werkzeug.exceptions import BadRequest, Conflict, NotFound
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@ -147,7 +147,7 @@ def get_events(
offset: Optional[int] = None, offset: Optional[int] = None,
descending: Optional[bool] = False, descending: Optional[bool] = False,
with_backup=False, with_backup=False,
): ) -> Tuple[int, list[Event]]:
"""Query events which start from begin until end """Query events which start from begin until end
Args: Args:
start (datetime): Earliest start start (datetime): Earliest start
@ -161,10 +161,14 @@ def get_events(
query = query.filter(start <= Event.start) query = query.filter(start <= Event.start)
if end is not None: if end is not None:
query = query.filter(Event.start < end) 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: if descending:
query = query.order_by(Event.start.desc()) query = query.order_by(Event.start.desc())
else: else:
query = query.order_by(Event.start) query = query.order_by(Event.start)
count = query.count()
if limit is not None: if limit is not None:
query = query.limit(limit) query = query.limit(limit)
if offset is not None and offset > 0: if offset is not None and offset > 0:
@ -173,7 +177,7 @@ def get_events(
if not with_backup: if not with_backup:
for event in events: for event in events:
clear_backup(event) clear_backup(event)
return events return count, events
def delete_event(event_id): def delete_event(event_id):
@ -221,6 +225,24 @@ def get_job(job_id, event_id=None) -> Job:
return 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): def add_job(event, job_type, required_services, start, end=None, comment=None):
job = Job( job = Job(
required_services=required_services, required_services=required_services,
@ -314,7 +336,16 @@ def respond_invitation(invite: Invitation, accepted=True):
raise Conflict raise Conflict
if not accepted: 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: else:
if invite.transferee_id is None: if invite.transferee_id is None:
assign_job(job, invite.invitee_, 1) assign_job(job, invite.invitee_, 1)
@ -324,7 +355,16 @@ def respond_invitation(invite: Invitation, accepted=True):
raise Conflict raise Conflict
unassign_job(job, invite.transferee_, service[0], True) unassign_job(job, invite.transferee_, service[0], True)
assign_job(job, invite.invitee_, service[0].value) 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 @scheduled

View File

@ -58,7 +58,6 @@ class Service(db.Model, ModelSerializeMixin):
class Job(db.Model, ModelSerializeMixin): class Job(db.Model, ModelSerializeMixin):
__tablename__ = _table_prefix_ + "job" __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) id: int = db.Column(Serial, primary_key=True)
start: datetime = db.Column(UtcDateTime, nullable=False) start: datetime = db.Column(UtcDateTime, nullable=False)
@ -73,6 +72,8 @@ class Job(db.Model, ModelSerializeMixin):
event_ = db.relationship("Event", back_populates="jobs") event_ = db.relationship("Event", back_populates="jobs")
event_id_ = db.Column("event_id", Serial, db.ForeignKey(f"{_table_prefix_}event.id"), nullable=False) 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_") invitations_ = db.relationship("Invitation", cascade="all,delete,delete-orphan", back_populates="job_")
__table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),) __table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),)

View File

@ -1,18 +1,14 @@
from datetime import datetime, timedelta, timezone
from http.client import NO_CONTENT from http.client import NO_CONTENT
from re import template
from flask import request, jsonify from flask import request, jsonify
from sqlalchemy import exc
from werkzeug.exceptions import BadRequest, NotFound, Forbidden from werkzeug.exceptions import BadRequest, NotFound, Forbidden
from flaschengeist.models.session import Session from flaschengeist.models.session import Session
from flaschengeist.plugins.events.models import Job
from flaschengeist.utils.decorators import login_required from flaschengeist.utils.decorators import login_required
from flaschengeist.utils.datetime import from_iso_format from flaschengeist.utils.datetime import from_iso_format
from flaschengeist.controller import userController from flaschengeist.controller import userController
from . import event_controller, permissions, EventPlugin 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): def dict_get(self, key, default=None, type=None):
@ -194,63 +190,12 @@ def get_event(event_id, current_session):
@EventPlugin.blueprint.route("/events", methods=["GET"]) @EventPlugin.blueprint.route("/events", methods=["GET"])
@login_required() @login_required()
def get_filtered_events(current_session): def get_events(current_session):
begin = request.args.get("from", type=from_iso_format) count, result = event_controller.get_events(
end = request.args.get("to", type=from_iso_format) *get_filter_args(),
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,
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})
@EventPlugin.blueprint.route("/events/<int:year>/<int:month>", methods=["GET"])
@EventPlugin.blueprint.route("/events/<int:year>/<int:month>/<int:day>", 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[/<year>/<month>[/<int:day>]]`` | 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")
def _add_job(event, data): def _add_job(event, data):
@ -449,6 +394,13 @@ def update_job(event_id, job_id, current_session: Session):
return jsonify(job) 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/<int:job_id>/assign", methods=["POST"]) @EventPlugin.blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"])
@login_required() @login_required()
def assign_job(job_id, current_session: Session): def assign_job(job_id, current_session: Session):

View File

@ -2,6 +2,24 @@ from http.client import NO_CONTENT, CREATED
from flask import make_response, jsonify 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(): def no_content():
return make_response(jsonify(""), NO_CONTENT) return make_response(jsonify(""), NO_CONTENT)