[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 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/<int:event_id>/slots", methods=["POST"])
@schedule_bp.route("/events/<int:event_id>/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/<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:
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/<int:event_id>/slots/<int:slot_id>", methods=["PUT"])
@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"])
@schedule_bp.route("/events/<int:event_id>/jobs/<int:job_id>", 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/<event_id>/slots/<slot_id>/jobs/<job_type>`` | Method: ``DELETE``
Route: ``/events/<event_id>/jobs/<job_id>`` | 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/<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()
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/<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:
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

View File

@ -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()

View File

@ -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]"
)