[Plugin] Schedule: Mostly final backend implemented. Tested.

This commit is contained in:
Ferdinand Thiessen 2020-11-18 02:47:40 +01:00
parent 6612a84cd3
commit 4a4930d683
3 changed files with 88 additions and 206 deletions

View File

@ -15,6 +15,8 @@ from flaschengeist.controller import userController
from . import event_controller, permissions from . import event_controller, permissions
from . import models from . import models
from ... import logger
from ...utils.HTTP import no_content
schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule") 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") raise BadRequest("Invalid date given")
def _add_event_slot(event, data): def _add_job(event, data):
end = None end = None
try: try:
start = from_iso_format(data["start"]) start = from_iso_format(data["start"])
@ -221,11 +223,11 @@ def _add_event_slot(event, data):
raise BadRequest("Missing POST parameter") raise BadRequest("Missing POST parameter")
if "end" in data: if "end" in data:
end = from_iso_format(data["end"]) end = from_iso_format(data["end"])
event_slot = event_controller.add_event_slot(event, start, end)
if "jobs" in data: if "required_services" not in data:
for job_data in data["jobs"]: raise BadRequest
job_type = event_controller.get_job_type(job_data["type"]) job_type = event_controller.get_job_type(data["type"])
event_controller.add_job_slot(event_slot, job_type, job_data["required_jobs"]) event_controller.add_job(event, job_type, data["required_services"], start, end, comment=data.get("comment"))
@schedule_bp.route("/events", methods=["POST"]) @schedule_bp.route("/events", methods=["POST"])
@ -235,7 +237,7 @@ def create_event(current_session):
Route: ``/schedule/events`` | Method: ``POST`` 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: Args:
current_session: Session sent with Authorization Header current_session: Session sent with Authorization Header
@ -254,9 +256,9 @@ def create_event(current_session):
event = event_controller.create_event( event = event_controller.create_event(
start=start, event_type=event_type, description=data["description"] if "description" in data else None start=start, event_type=event_type, description=data["description"] if "description" in data else None
) )
if "slots" in data: if "jobs" in data:
for slot in data["slots"]: for job in data["jobs"]:
_add_event_slot(event, slot) _add_job(event, job)
return jsonify(event) return jsonify(event)
@ -307,14 +309,14 @@ def delete_event(event_id, current_session):
return "", NO_CONTENT return "", NO_CONTENT
@schedule_bp.route("/events/<int:event_id>/slots", methods=["POST"]) @schedule_bp.route("/events/<int:event_id>/jobs", methods=["POST"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
def add_event_slot(event_id, current_session): def add_job(event_id, current_session):
"""Add an new EventSlot to an Event """Add an new Job to an Event / EventSlot
Route: ``/schedule/events/<event_id>/slots`` | Method: ``POST`` Route: ``/schedule/events/<event_id>/jobs`` | Method: ``POST``
POST-data: See TS interface for EventSlot POST-data: See Job
Args: Args:
event_id: Identifier of the event 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 JSON encoded Event object or HTTP-error
""" """
event = event_controller.get_event(event_id) event = event_controller.get_event(event_id)
data = request.get_json() _add_job(event, request.get_json())
if not data:
raise BadRequest("Missing POST parameters")
_add_event_slot(event, data)
return jsonify(event) return jsonify(event)
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["PUT"]) @schedule_bp.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["DELETE"])
@login_required(permission=permissions.EDIT)
def update_event_slot(event_id, slot_id, current_session):
"""Update an EventSlot
Route: ``/schedule/events/<event_id>/slots/<slot_id>`` | 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/<int:event_id>/slots/<int:slot_id>", methods=["DELETE"])
@login_required(permission=permissions.EDIT)
def delete_event_slot(event_id, slot_id, current_session):
"""Delete an EventSlot
Route: ``/schedule/events/<event_id>/slots/<slot_id>`` | 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/<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) @login_required(permission=permissions.DELETE)
def delete_job_slot(event_id, slot_id, job_type, current_session): def delete_job(event_id, job_id, current_session):
"""Delete a JobSlot """Delete a Job
Route: ``/schedule/events/<event_id>/slots/<slot_id>/jobs/<job_type>`` | Method: ``DELETE`` Route: ``/events/<event_id>/jobs/<job_id>`` | Method: ``DELETE``
Args: Args:
event_id: Identifier of the event event_id: Identifier of the event
slot_id: Identifier of the EventSlot job_id: Identifier of the Job
job_type: Identifier of the JobSlot
current_session: Session sent with Authorization Header current_session: Session sent with Authorization Header
Returns: 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(job_id, event_id)
job_slot = event_controller.get_job_slot(slot_id, job_type) event_controller.delete_job(job_slot)
event_controller.delete_job_slot(job_slot) return no_content()
return jsonify(event)
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>/jobs/<int:job_type>", methods=["PUT"]) @schedule_bp.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"])
@login_required() @login_required()
def update_job_slot(event_id, slot_id, job_type, current_session: Session): def update_job(event_id, job_id, current_session: Session):
"""Edit JobSlot or add user to the Slot """Edit Job or assign user to the Job
Route: ``/schedule/events/<event_id>/slots/<slot_id>/jobs/<job_type>`` | Method: ``PUT`` Route: ``/events/<event_id>/jobs/<job_id>`` | 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: Args:
event_id: Identifier of the event event_id: Identifier of the event
slot_id: Identifier of the slot job_id: Identifier of the Job
job_type: Identifier of the JobSlot
current_session: Session sent with Authorization Header current_session: Session sent with Authorization Header
Returns: Returns:
JSON encoded Event object or HTTP-error JSON encoded Event object or HTTP-error
""" """
event = event_controller.get_event(event_id) job = event_controller.get_job(job_id, event_id)
slot = event_controller.get_job_slot(slot_id, job_type)
data = request.get_json() data = request.get_json()
if not data: 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) user != current_session._user and not current_session._user.has_permission(permissions.ASSIGN_OTHER)
): ):
raise Forbidden raise Forbidden
event_controller.assign_job(slot, user, value) event_controller.assign_to_job(job, user, value)
except (KeyError, ValueError): except (KeyError, ValueError):
raise BadRequest raise BadRequest
if "required_jobs" in data: if "required_services" in data:
slot.required_jobs = data["required_jobs"] job.required_services = data["required_services"]
if "type" in data: 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() event_controller.update()
return jsonify(event) return jsonify(job.event_)
# TODO: JobTransfer # TODO: JobTransfer

View File

@ -3,7 +3,7 @@ from sqlalchemy.exc import IntegrityError
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.database import db 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(): def update():
@ -55,6 +55,7 @@ def get_job_types():
def get_job_type(type_id): def get_job_type(type_id):
job_type = JobType.query.get(type_id) job_type = JobType.query.get(type_id)
print(job_type)
if not job_type: if not job_type:
raise NotFound raise NotFound
return job_type return job_type
@ -119,10 +120,10 @@ def delete_event(event_id):
db.session.commit() db.session.commit()
def create_event(event_type, start, slots=[], description=None): def create_event(event_type, start, jobs=[], description=None):
try: try:
logger.debug(event_type) 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.add(event)
db.session.commit() db.session.commit()
return event return event
@ -131,59 +132,38 @@ def create_event(event_type, start, slots=[], description=None):
raise BadRequest raise BadRequest
def get_event_slot(slot_id): def get_job(job_slot_id, event_id):
slot = EventSlot.query.get(slot_id) js = Job.query.filter(Job.id == job_slot_id).filter(Job._event_id == event_id).one_or_none()
if slot is None: if js is None:
raise NotFound raise NotFound
return slot return js
def add_event_slot(event, start, end=None): def add_job(event, job_type, required_services, start, end=None, comment=None):
event_slot = EventSlot(start=start, end=end, event_=event) job = Job(required_services=required_services, type=job_type, start=start, end=end, comment=comment)
if start < event.start: event.jobs.append(job)
raise BadRequest("Start before event start") update()
db.session.add(event_slot) return job
db.session.commit()
return event_slot
def remove_event_slot(event, slot_id): def update():
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)
try: try:
db.session.add(job_slot)
db.session.commit() db.session.commit()
except IntegrityError: 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): def delete_job(job: Job):
db.session.delete(job_slot) db.session.delete(job)
db.session.commit() db.session.commit()
def assign_job(job_slot, user, value): def assign_to_job(job: Job, user, value):
job = Job.query.get((job_slot.id_, user._id)) service = Service.query.get((job.id, user._id))
if job: if service:
job.value = value service.value = value
else: else:
job = Job(user_=user, value=value, slot_=job_slot) service = Service(user_=user, value=value, job_=job)
db.session.add(job) db.session.add(service)
db.session.commit() db.session.commit()

View File

@ -13,13 +13,13 @@ from flaschengeist.database import db
class EventType(db.Model, ModelSerializeMixin): class EventType(db.Model, ModelSerializeMixin):
__tablename__ = "event_type" __tablename__ = "schedule_event_type"
id_: int = 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) name: str = db.Column(db.String(30), nullable=False, unique=True)
class JobType(db.Model, ModelSerializeMixin): class JobType(db.Model, ModelSerializeMixin):
__tablename__ = "job_type" __tablename__ = "schedule_job_type"
id: int = 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) 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): class Service(db.Model, ModelSerializeMixin):
__tablename__ = "job" __tablename__ = "schedule_service"
userid: str = "" userid: str = ""
value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False) 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_id = db.Column("user_id", db.Integer, db.ForeignKey("user.id"), nullable=False, primary_key=True)
user_: User = db.relationship("User") user_: User = db.relationship("User")
slot_ = db.relationship("JobSlot") job_ = db.relationship("Job")
@property @property
def userid(self): def userid(self):
return self.user_.userid return self.user_.userid
class JobSlot(db.Model, ModelSerializeMixin): class Job(db.Model, ModelSerializeMixin):
__tablename__ = "job_slot" __tablename__ = "schedule_job"
_type_id = db.Column("type_id", db.Integer, db.ForeignKey("job_type.id"), nullable=False) _type_id = db.Column("type_id", db.Integer, db.ForeignKey("schedule_job_type.id"), nullable=False)
_event_slot_id = db.Column("event_slot_id", db.Integer, db.ForeignKey("event_slot.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") type: JobType = db.relationship("JobType")
users: [Job] = db.relationship("Job", back_populates="slot_") services: [Service] = db.relationship("Service", back_populates="job_")
required_jobs: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False)) 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): class Event(db.Model, ModelSerializeMixin):
"""Model for an Event""" """Model for an Event"""
__tablename__ = "event" __tablename__ = "schedule_event"
_type_id = db.Column("type_id", db.Integer, db.ForeignKey("event_type.id", ondelete="CASCADE"), nullable=False) _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) id: int = db.Column(db.Integer, primary_key=True)
start: datetime = db.Column(UtcDateTime, nullable=False) 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") type: EventType = db.relationship("EventType")
slots: [EventSlot] = db.relationship( jobs: [Job] = db.relationship(
"EventSlot", back_populates="event_", cascade="all,delete,delete-orphan", order_by="EventSlot.start" "Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]"
) )