[Plugin] schedule: Basics work (all models)
This commit is contained in:
parent
ac1189ecaa
commit
5a8a4aa23d
|
@ -5,11 +5,13 @@ Provides duty schedule / duty roster functions
|
|||
from datetime import datetime, timedelta, timezone
|
||||
from http.client import NO_CONTENT, CREATED
|
||||
from flask import Blueprint, request, jsonify
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
|
||||
|
||||
from flaschengeist.plugins import Plugin
|
||||
from flaschengeist.models.session import Session
|
||||
from flaschengeist.decorator import login_required
|
||||
from flaschengeist.utils.datetime import from_iso_format
|
||||
from flaschengeist.controller import userController
|
||||
|
||||
from . import event_controller, permissions
|
||||
from . import models
|
||||
|
@ -32,7 +34,7 @@ class SchedulePlugin(Plugin):
|
|||
def get_event_types(current_session):
|
||||
"""Get all EventTypes
|
||||
|
||||
Route: ``/event-types`` | Method: ``GET``
|
||||
Route: ``/schedule/event-types`` | Method: ``GET``
|
||||
|
||||
Args:
|
||||
current_session: Session sent with Authorization Header
|
||||
|
@ -49,7 +51,7 @@ def get_event_types(current_session):
|
|||
def new_event_type(current_session):
|
||||
"""Create a new EventType
|
||||
|
||||
Route: ``/event-types`` | Method: ``POST``
|
||||
Route: ``/schedule/event-types`` | Method: ``POST``
|
||||
|
||||
POST-data: ``{name: string}``
|
||||
|
||||
|
@ -71,7 +73,7 @@ def new_event_type(current_session):
|
|||
def modify_event_type(name, current_session):
|
||||
"""Rename or delete an event type
|
||||
|
||||
Route: ``/event-types/<name>`` | Method: ``PUT`` or ``DELETE``
|
||||
Route: ``/schedule/event-types/<name>`` | Method: ``PUT`` or ``DELETE``
|
||||
|
||||
POST-data: (if renaming) ``{name: string}``
|
||||
|
||||
|
@ -97,7 +99,7 @@ def modify_event_type(name, current_session):
|
|||
def get_job_types(current_session):
|
||||
"""Get all JobTypes
|
||||
|
||||
Route: ``/job-types`` | Method: ``GET``
|
||||
Route: ``/schedule/job-types`` | Method: ``GET``
|
||||
|
||||
Args:
|
||||
current_session: Session sent with Authorization Header
|
||||
|
@ -114,7 +116,7 @@ def get_job_types(current_session):
|
|||
def new_job_type(current_session):
|
||||
"""Create a new JobType
|
||||
|
||||
Route: ``/job-types`` | Method: ``POST``
|
||||
Route: ``/schedule/job-types`` | Method: ``POST``
|
||||
|
||||
POST-data: ``{name: string}``
|
||||
|
||||
|
@ -122,38 +124,38 @@ def new_job_type(current_session):
|
|||
current_session: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
HTTP-Created or HTTP-error
|
||||
JSON encoded JobType or HTTP-error
|
||||
"""
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest
|
||||
event_controller.create_job_type(data["name"])
|
||||
return "", CREATED
|
||||
jt = event_controller.create_job_type(data["name"])
|
||||
return jsonify(jt)
|
||||
|
||||
|
||||
@schedule_bp.route("/job-types/<name>", methods=["PUT", "DELETE"])
|
||||
@schedule_bp.route("/job-types/<int:type_id>", methods=["PUT", "DELETE"])
|
||||
@login_required(permission=permissions.JOB_TYPE)
|
||||
def modify_job_type(name, current_session):
|
||||
def modify_job_type(type_id, current_session):
|
||||
"""Rename or delete a JobType
|
||||
|
||||
Route: ``/job-types/<name>`` | Method: ``PUT`` or ``DELETE``
|
||||
Route: ``/schedule/job-types/<name>`` | Method: ``PUT`` or ``DELETE``
|
||||
|
||||
POST-data: (if renaming) ``{name: string}``
|
||||
|
||||
Args:
|
||||
name: Name identifying the JobType
|
||||
type_id: Identifier of the JobType
|
||||
current_session: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
HTTP-NoContent or HTTP-error
|
||||
"""
|
||||
if request.method == "DELETE":
|
||||
event_controller.delete_name_type(name)
|
||||
event_controller.delete_job_type(type_id)
|
||||
else:
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest("Parameter missing in data")
|
||||
event_controller.rename_name_type(name, data["name"])
|
||||
event_controller.rename_job_type(type_id, data["name"])
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
|
@ -162,7 +164,7 @@ def modify_job_type(name, current_session):
|
|||
def get_event(event_id, current_session):
|
||||
"""Get event by id
|
||||
|
||||
Route: ``/events/<event_id>`` | Method: ``GET``
|
||||
Route: ``/schedule/events/<event_id>`` | Method: ``GET``
|
||||
|
||||
Args:
|
||||
event_id: ID identifying the event
|
||||
|
@ -183,7 +185,7 @@ def get_events(current_session, year=datetime.now().year, month=datetime.now().m
|
|||
"""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``
|
||||
Route: ``/schedule/events[/<year>/<month>[/<int:day>]]`` | Method: ``GET``
|
||||
|
||||
Args:
|
||||
year (int, optional): year to query, defaults to current year
|
||||
|
@ -195,15 +197,15 @@ def get_events(current_session, year=datetime.now().year, month=datetime.now().m
|
|||
JSON encoded list containing events found or HTTP-error
|
||||
"""
|
||||
try:
|
||||
begin = datetime(year=year, month=month, day=1)
|
||||
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)
|
||||
end = datetime(year=year + 1, month=1, day=1, tzinfo=timezone.utc)
|
||||
else:
|
||||
end = datetime(year=year, month=month + 1, day=1)
|
||||
end = datetime(year=year, month=month + 1, day=1, tzinfo=timezone.utc)
|
||||
|
||||
events = event_controller.get_events(begin, end)
|
||||
return jsonify(events)
|
||||
|
@ -226,7 +228,7 @@ def _event_slot_from_data(data):
|
|||
def create_event(current_session):
|
||||
"""Create an new event
|
||||
|
||||
Route: ``/events`` | Method: ``POST``
|
||||
Route: ``/schedule/events`` | Method: ``POST``
|
||||
|
||||
POST-data: See interfaces for Event, can already contain slots
|
||||
|
||||
|
@ -258,7 +260,7 @@ def create_event(current_session):
|
|||
def modify_event(event_id, current_session):
|
||||
"""Modify an event
|
||||
|
||||
Route: ``/events/<event_id>`` | Method: ``PUT``
|
||||
Route: ``/schedule/events/<event_id>`` | Method: ``PUT``
|
||||
|
||||
POST-data: See interfaces for Event, can already contain slots
|
||||
|
||||
|
@ -287,7 +289,7 @@ def modify_event(event_id, current_session):
|
|||
def delete_event(event_id, current_session):
|
||||
"""Delete an event
|
||||
|
||||
Route: ``/events/<event_id>`` | Method: ``DELETE``
|
||||
Route: ``/schedule/events/<event_id>`` | Method: ``DELETE``
|
||||
|
||||
Args:
|
||||
event_id: Identifier of the event
|
||||
|
@ -305,7 +307,7 @@ def delete_event(event_id, current_session):
|
|||
def add_event_slot(event_id, current_session):
|
||||
"""Add an new EventSlot to an Event
|
||||
|
||||
Route: ``/events/<event_id>/slots`` | Method: ``POST``
|
||||
Route: ``/schedule/events/<event_id>/slots`` | Method: ``POST``
|
||||
|
||||
POST-data: See TS interface for EventSlot
|
||||
|
||||
|
@ -330,7 +332,7 @@ def add_event_slot(event_id, current_session):
|
|||
def update_event_slot(event_id, slot_id, current_session):
|
||||
"""Update an EventSlot
|
||||
|
||||
Route: ``/events/<event_id>/slots/<slot_id>`` | Method: ``PUT``
|
||||
Route: ``/schedule/events/<event_id>/slots/<slot_id>`` | Method: ``PUT``
|
||||
|
||||
POST-data: See TS interface for EventSlot
|
||||
|
||||
|
@ -360,7 +362,7 @@ def update_event_slot(event_id, slot_id, current_session):
|
|||
def delete_event_slot(event_id, slot_id, current_session):
|
||||
"""Delete an EventSlot
|
||||
|
||||
Route: ``/events/<event_id>/slots/<slot_id>`` | Method: ``DELETE``
|
||||
Route: ``/schedule/events/<event_id>/slots/<slot_id>`` | Method: ``DELETE``
|
||||
|
||||
Args:
|
||||
event_id: Identifier of the event
|
||||
|
@ -375,5 +377,106 @@ def delete_event_slot(event_id, slot_id, current_session):
|
|||
return jsonify(event)
|
||||
|
||||
|
||||
# TODO: JobSlot, Job!
|
||||
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>/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/<event_id>/slots/<slot_id>/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/<int:event_id>/slots/<int:slot_id>/jobs/<int:job_type>", methods=["DELETE"])
|
||||
@login_required(permission=permissions.DELETE)
|
||||
def delete_job_slot(event_id, slot_id, job_type, current_session):
|
||||
"""Delete a JobSlot
|
||||
|
||||
Route: ``/schedule/events/<event_id>/slots/<slot_id>/jobs/<job_type>`` | Method: ``DELETE``
|
||||
|
||||
Args:
|
||||
event_id: Identifier of the event
|
||||
slot_id: Identifier of the EventSlot
|
||||
job_type: Identifier of the JobSlot
|
||||
current_session: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
JSON encoded Event object 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)
|
||||
|
||||
|
||||
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>/jobs/<int:job_type>", 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
|
||||
|
||||
Route: ``/schedule/events/<event_id>/slots/<slot_id>/jobs/<job_type>`` | Method: ``PUT``
|
||||
|
||||
POST-data: See TS interface for EventSlot 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
|
||||
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)
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
raise BadRequest
|
||||
|
||||
if ("user" not in data or len(data) > 1) and not current_session._user.has_permission(permissions.EDIT):
|
||||
raise Forbidden
|
||||
|
||||
if "user" in data:
|
||||
try:
|
||||
user = userController.get_user(data["user"]["userid"])
|
||||
value = data["user"]["value"]
|
||||
if (user == current_session._user and not user.has_permission(permissions.ASSIGN)) or (user != current_session._user and not current_session._user.has_permission(permissions.ASSIGN_OTHER)):
|
||||
raise Forbidden
|
||||
event_controller.assign_job(slot, user, value)
|
||||
except (KeyError, ValueError):
|
||||
raise BadRequest
|
||||
|
||||
if "required_jobs" in data:
|
||||
slot.required_jobs = data["required_jobs"]
|
||||
if "type" in data:
|
||||
slot.type = event_controller.get_job_type(data["type"])
|
||||
event_controller.update()
|
||||
|
||||
return jsonify(event)
|
||||
|
||||
|
||||
# TODO: JobTransfer
|
||||
|
|
|
@ -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
|
||||
from flaschengeist.plugins.schedule.models import EventType, Event, EventSlot, JobSlot, JobType, Job
|
||||
|
||||
|
||||
def update():
|
||||
|
@ -53,8 +53,8 @@ def get_job_types():
|
|||
return JobType.query.all()
|
||||
|
||||
|
||||
def get_job_type(name):
|
||||
job_type = JobType.query.filter(JobType.name == name).one_or_none()
|
||||
def get_job_type(type_id):
|
||||
job_type = JobType.query.get(type_id)
|
||||
if not job_type:
|
||||
raise NotFound
|
||||
return job_type
|
||||
|
@ -153,3 +153,34 @@ def remove_event_slot(event, slot_id):
|
|||
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)
|
||||
try:
|
||||
db.session.add(job_slot)
|
||||
db.session.commit()
|
||||
except IntegrityError:
|
||||
raise BadRequest("JobSlot with that type already exists on this EventSlot")
|
||||
|
||||
|
||||
def delete_job_slot(job_slot):
|
||||
db.session.delete(job_slot)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def assign_job(job_slot, user, value):
|
||||
job = Job.query.get((job_slot.id_, user._id))
|
||||
if job:
|
||||
job.value = value
|
||||
else:
|
||||
job = Job(user_=user, value=value, slot_=job_slot)
|
||||
db.session.add(job)
|
||||
db.session.commit()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import UniqueConstraint
|
||||
|
||||
from flaschengeist.models import ModelSerializeMixin, UtcDateTime
|
||||
from flaschengeist.models.user import User
|
||||
from flaschengeist.database import db
|
||||
|
@ -18,7 +20,7 @@ class EventType(db.Model, ModelSerializeMixin):
|
|||
|
||||
class JobType(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = "job_type"
|
||||
id_ = db.Column("id", db.Integer, primary_key=True)
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
|
@ -29,33 +31,34 @@ class JobType(db.Model, ModelSerializeMixin):
|
|||
|
||||
class Job(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = "job"
|
||||
userid: str = None
|
||||
value: float = db.Column(db.Numeric(precision=3, scale=2), nullable=False)
|
||||
userid: str = ""
|
||||
value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
id_ = db.Column("id", db.Integer, primary_key=True)
|
||||
_user_id = db.Column("user_id", db.Integer, db.ForeignKey("user.id"), nullable=False)
|
||||
_slot_id = db.Column("slot_id", db.Integer, db.ForeignKey("job_slot.id"), nullable=False)
|
||||
_slot_id = db.Column("slot_id", db.Integer, db.ForeignKey("job_slot.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")
|
||||
|
||||
@property
|
||||
def userid(self):
|
||||
return self._user.userid
|
||||
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"))
|
||||
_event_slot_id = db.Column("event_slot_id", db.Integer, db.ForeignKey("event_slot.id"))
|
||||
_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)
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
id_: int = db.Column("id", db.Integer, primary_key=True)
|
||||
type: JobType = db.relationship("JobType")
|
||||
users: [Job] = db.relationship("Job", back_populates="slot_")
|
||||
required_jobs: float = db.Column(db.Numeric(precision=4, scale=2))
|
||||
required_jobs: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False))
|
||||
|
||||
event_slot_ = db.relationship("EventSlot", back_populates="jobs")
|
||||
|
||||
__table_args__ = (UniqueConstraint('type_id', 'event_slot_id', name='_type_event_slot_uc'),)
|
||||
|
||||
|
||||
##########
|
||||
# Events #
|
||||
|
|
Loading…
Reference in New Issue