Compare commits

..

2 Commits

5 changed files with 141 additions and 40 deletions

View File

@ -14,14 +14,20 @@ __version__ = pkg_resources.get_distribution("flaschengeist_events").version
class EventPlugin(Plugin): class EventPlugin(Plugin):
id = "dev.flaschengeist.events" #id = "dev.flaschengeist.events"
plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][EventPlugin.id]) #plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][EventPlugin.id])
# provided resources # provided resources
permissions = permissions.permissions #permissions = permissions.permissions
blueprint = Blueprint("events", __name__)
models = models models = models
def __init__(self, cfg): # def __init__(self, cfg):
super(EventPlugin, self).__init__(cfg) # super(EventPlugin, self).__init__(cfg)
from . import routes # from . import routes
from .event_controller import clear_services # from .event_controller import clear_services
def load(self):
from .routes import blueprint
self.blueprint = blueprint
def install(self):
self.install_permissions(permissions.permissions)

View File

@ -0,0 +1,92 @@
"""init events
Revision ID: e70508bd8cb4
Revises: 20482a003db8
Create Date: 2023-04-10 14:21:47.007251
"""
from alembic import op
import sqlalchemy as sa
import flaschengeist
# revision identifiers, used by Alembic.
revision = 'e70508bd8cb4'
down_revision = None
branch_labels = ('events',)
depends_on = "flaschengeist"
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('events_event_type',
sa.Column('id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('name', sa.String(length=30), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_events_event_type')),
sa.UniqueConstraint('name', name=op.f('uq_events_event_type_name'))
)
op.create_table('events_job_type',
sa.Column('id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('name', sa.String(length=30), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_events_job_type')),
sa.UniqueConstraint('name', name=op.f('uq_events_job_type_name'))
)
op.create_table('events_event',
sa.Column('id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('start', flaschengeist.database.types.UtcDateTime(), nullable=False),
sa.Column('end', flaschengeist.database.types.UtcDateTime(), nullable=True),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('description', sa.String(length=512), nullable=True),
sa.Column('is_template', sa.Boolean(), nullable=True),
sa.Column('type_id', flaschengeist.database.types.Serial(), nullable=False),
sa.ForeignKeyConstraint(['type_id'], ['events_event_type.id'], name=op.f('fk_events_event_type_id_events_event_type'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_events_event'))
)
op.create_table('events_job',
sa.Column('id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('start', flaschengeist.database.types.UtcDateTime(), nullable=False),
sa.Column('end', flaschengeist.database.types.UtcDateTime(), nullable=True),
sa.Column('comment', sa.String(length=256), nullable=True),
sa.Column('type_id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('locked', sa.Boolean(), nullable=False),
sa.Column('required_services', sa.Numeric(precision=4, scale=2, asdecimal=False), nullable=False),
sa.Column('event_id', flaschengeist.database.types.Serial(), nullable=False),
sa.ForeignKeyConstraint(['event_id'], ['events_event.id'], name=op.f('fk_events_job_event_id_events_event')),
sa.ForeignKeyConstraint(['type_id'], ['events_job_type.id'], name=op.f('fk_events_job_type_id_events_job_type')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_events_job')),
sa.UniqueConstraint('type_id', 'start', 'event_id', name='_type_start_uc')
)
op.create_table('events_invitation',
sa.Column('id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('time', flaschengeist.database.types.UtcDateTime(), nullable=False),
sa.Column('job_id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('invitee_id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('inviter_id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('transferee_id', flaschengeist.database.types.Serial(), nullable=True),
sa.ForeignKeyConstraint(['invitee_id'], ['user.id'], name=op.f('fk_events_invitation_invitee_id_user')),
sa.ForeignKeyConstraint(['inviter_id'], ['user.id'], name=op.f('fk_events_invitation_inviter_id_user')),
sa.ForeignKeyConstraint(['job_id'], ['events_job.id'], name=op.f('fk_events_invitation_job_id_events_job')),
sa.ForeignKeyConstraint(['transferee_id'], ['user.id'], name=op.f('fk_events_invitation_transferee_id_user')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_events_invitation'))
)
op.create_table('events_service',
sa.Column('is_backup', sa.Boolean(), nullable=True),
sa.Column('value', sa.Numeric(precision=3, scale=2, asdecimal=False), nullable=False),
sa.Column('job_id', flaschengeist.database.types.Serial(), nullable=False),
sa.Column('user_id', flaschengeist.database.types.Serial(), nullable=False),
sa.ForeignKeyConstraint(['job_id'], ['events_job.id'], name=op.f('fk_events_service_job_id_events_job')),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('fk_events_service_user_id_user')),
sa.PrimaryKeyConstraint('job_id', 'user_id', name=op.f('pk_events_service'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('events_service')
op.drop_table('events_invitation')
op.drop_table('events_job')
op.drop_table('events_event')
op.drop_table('events_job_type')
op.drop_table('events_event_type')
# ### end Alembic commands ###

View File

@ -1,7 +1,7 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10 from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
from datetime import datetime from datetime import datetime
from typing import Optional, Union from typing import Optional, Union, List
from sqlalchemy import UniqueConstraint from sqlalchemy import UniqueConstraint
@ -17,6 +17,7 @@ _table_prefix_ = "events_"
class EventType(db.Model, ModelSerializeMixin): class EventType(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = _table_prefix_ + "event_type" __tablename__ = _table_prefix_ + "event_type"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, 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)
@ -34,6 +35,7 @@ class JobType(db.Model, ModelSerializeMixin):
class Service(db.Model, ModelSerializeMixin): class Service(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = _table_prefix_ + "service" __tablename__ = _table_prefix_ + "service"
userid: str = "" userid: str = ""
is_backup: bool = db.Column(db.Boolean, default=False) is_backup: bool = db.Column(db.Boolean, default=False)
@ -57,6 +59,7 @@ class Service(db.Model, ModelSerializeMixin):
class Job(db.Model, ModelSerializeMixin): class Job(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = _table_prefix_ + "job" __tablename__ = _table_prefix_ + "job"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
@ -65,7 +68,7 @@ class Job(db.Model, ModelSerializeMixin):
comment: Optional[str] = db.Column(db.String(256)) comment: Optional[str] = db.Column(db.String(256))
type: int = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False) type: int = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False)
locked: bool = db.Column(db.Boolean(), default=False, nullable=False) locked: bool = db.Column(db.Boolean(), default=False, nullable=False)
services: list[Service] = db.relationship( services: List[Service] = db.relationship(
"Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan" "Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan"
) )
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False) required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
@ -85,6 +88,7 @@ class Job(db.Model, ModelSerializeMixin):
class Event(db.Model, ModelSerializeMixin): class Event(db.Model, ModelSerializeMixin):
"""Model for an Event""" """Model for an Event"""
__allow_unmapped__ = True
__tablename__ = _table_prefix_ + "event" __tablename__ = _table_prefix_ + "event"
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)
@ -93,7 +97,7 @@ class Event(db.Model, ModelSerializeMixin):
description: Optional[str] = db.Column(db.String(512)) description: Optional[str] = db.Column(db.String(512))
type: Union[EventType, int] = db.relationship("EventType") type: Union[EventType, int] = db.relationship("EventType")
is_template: bool = db.Column(db.Boolean, default=False) is_template: bool = db.Column(db.Boolean, default=False)
jobs: list[Job] = db.relationship( jobs: List[Job] = db.relationship(
"Job", "Job",
back_populates="event_", back_populates="event_",
cascade="all,delete,delete-orphan", cascade="all,delete,delete-orphan",
@ -109,6 +113,7 @@ class Event(db.Model, ModelSerializeMixin):
class Invitation(db.Model, ModelSerializeMixin): class Invitation(db.Model, ModelSerializeMixin):
__allow_unmapped__ = True
__tablename__ = _table_prefix_ + "invitation" __tablename__ = _table_prefix_ + "invitation"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)

View File

@ -1,5 +1,5 @@
from http.client import NO_CONTENT from http.client import NO_CONTENT
from flask import request, jsonify from flask import request, jsonify, Blueprint
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
@ -11,6 +11,8 @@ from flaschengeist.utils.HTTP import get_filter_args, no_content
from . import event_controller, permissions, EventPlugin from . import event_controller, permissions, EventPlugin
blueprint = Blueprint("events", __name__)
def dict_get(self, key, default=None, type=None): def dict_get(self, key, default=None, type=None):
"""Same as .get from MultiDict""" """Same as .get from MultiDict"""
try: try:
@ -25,14 +27,14 @@ def dict_get(self, key, default=None, type=None):
return rv return rv
@EventPlugin.blueprint.route("/events/templates", methods=["GET"]) @blueprint.route("/events/templates", methods=["GET"])
@login_required() @login_required()
def get_templates(current_session): def get_templates(current_session):
return jsonify(event_controller.get_templates()) return jsonify(event_controller.get_templates())
@EventPlugin.blueprint.route("/events/event-types", methods=["GET"]) @blueprint.route("/events/event-types", methods=["GET"])
@EventPlugin.blueprint.route("/events/event-types/<int:identifier>", methods=["GET"]) @blueprint.route("/events/event-types/<int:identifier>", methods=["GET"])
@login_required() @login_required()
def get_event_types(current_session, identifier=None): def get_event_types(current_session, identifier=None):
"""Get EventType(s) """Get EventType(s)
@ -54,7 +56,7 @@ def get_event_types(current_session, identifier=None):
return jsonify(result) return jsonify(result)
@EventPlugin.blueprint.route("/events/event-types", methods=["POST"]) @blueprint.route("/events/event-types", methods=["POST"])
@login_required(permission=permissions.EVENT_TYPE) @login_required(permission=permissions.EVENT_TYPE)
def new_event_type(current_session): def new_event_type(current_session):
"""Create a new EventType """Create a new EventType
@ -76,7 +78,7 @@ def new_event_type(current_session):
return jsonify(event_type) return jsonify(event_type)
@EventPlugin.blueprint.route("/events/event-types/<int:identifier>", methods=["PUT", "DELETE"]) @blueprint.route("/events/event-types/<int:identifier>", methods=["PUT", "DELETE"])
@login_required(permission=permissions.EVENT_TYPE) @login_required(permission=permissions.EVENT_TYPE)
def modify_event_type(identifier, current_session): def modify_event_type(identifier, current_session):
"""Rename or delete an event type """Rename or delete an event type
@ -102,7 +104,7 @@ def modify_event_type(identifier, current_session):
return "", NO_CONTENT return "", NO_CONTENT
@EventPlugin.blueprint.route("/events/job-types", methods=["GET"]) @blueprint.route("/events/job-types", methods=["GET"])
@login_required() @login_required()
def get_job_types(current_session): def get_job_types(current_session):
"""Get all JobTypes """Get all JobTypes
@ -119,7 +121,7 @@ def get_job_types(current_session):
return jsonify(types) return jsonify(types)
@EventPlugin.blueprint.route("/events/job-types", methods=["POST"]) @blueprint.route("/events/job-types", methods=["POST"])
@login_required(permission=permissions.JOB_TYPE) @login_required(permission=permissions.JOB_TYPE)
def new_job_type(current_session): def new_job_type(current_session):
"""Create a new JobType """Create a new JobType
@ -141,7 +143,7 @@ def new_job_type(current_session):
return jsonify(jt) return jsonify(jt)
@EventPlugin.blueprint.route("/events/job-types/<int:type_id>", methods=["PUT", "DELETE"]) @blueprint.route("/events/job-types/<int:type_id>", methods=["PUT", "DELETE"])
@login_required(permission=permissions.JOB_TYPE) @login_required(permission=permissions.JOB_TYPE)
def modify_job_type(type_id, current_session): def modify_job_type(type_id, current_session):
"""Rename or delete a JobType """Rename or delete a JobType
@ -167,7 +169,7 @@ def modify_job_type(type_id, current_session):
return "", NO_CONTENT return "", NO_CONTENT
@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["GET"]) @blueprint.route("/events/<int:event_id>", methods=["GET"])
@login_required() @login_required()
def get_event(event_id, current_session): def get_event(event_id, current_session):
"""Get event by id """Get event by id
@ -188,7 +190,7 @@ def get_event(event_id, current_session):
return jsonify(event) return jsonify(event)
@EventPlugin.blueprint.route("/events", methods=["GET"]) @blueprint.route("/events", methods=["GET"])
@login_required() @login_required()
def get_events(current_session): def get_events(current_session):
count, result = event_controller.get_events( count, result = event_controller.get_events(
@ -218,7 +220,7 @@ def _add_job(event, data):
) )
@EventPlugin.blueprint.route("/events", methods=["POST"]) @blueprint.route("/events", methods=["POST"])
@login_required(permission=permissions.CREATE) @login_required(permission=permissions.CREATE)
def create_event(current_session): def create_event(current_session):
"""Create an new event """Create an new event
@ -259,7 +261,7 @@ def create_event(current_session):
raise BadRequest("Invalid parameter") raise BadRequest("Invalid parameter")
@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["PUT"]) @blueprint.route("/events/<int:event_id>", methods=["PUT"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
def modify_event(event_id, current_session): def modify_event(event_id, current_session):
"""Modify an event """Modify an event
@ -289,7 +291,7 @@ def modify_event(event_id, current_session):
return jsonify(event) return jsonify(event)
@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["DELETE"]) @blueprint.route("/events/<int:event_id>", methods=["DELETE"])
@login_required(permission=permissions.DELETE) @login_required(permission=permissions.DELETE)
def delete_event(event_id, current_session): def delete_event(event_id, current_session):
"""Delete an event """Delete an event
@ -307,7 +309,7 @@ def delete_event(event_id, current_session):
return "", NO_CONTENT return "", NO_CONTENT
@EventPlugin.blueprint.route("/events/<int:event_id>/jobs", methods=["POST"]) @blueprint.route("/events/<int:event_id>/jobs", methods=["POST"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
def add_job(event_id, current_session): def add_job(event_id, current_session):
"""Add an new Job to an Event / EventSlot """Add an new Job to an Event / EventSlot
@ -328,7 +330,7 @@ def add_job(event_id, current_session):
return jsonify(event) return jsonify(event)
@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["DELETE"]) @blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["DELETE"])
@login_required(permission=permissions.DELETE) @login_required(permission=permissions.DELETE)
def delete_job(event_id, job_id, current_session): def delete_job(event_id, job_id, current_session):
"""Delete a Job """Delete a Job
@ -348,7 +350,7 @@ def delete_job(event_id, job_id, current_session):
return no_content() return no_content()
@EventPlugin.blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"]) @blueprint.route("/events/<int:event_id>/jobs/<int:job_id>", methods=["PUT"])
@login_required() @login_required()
def update_job(event_id, job_id, current_session: Session): def update_job(event_id, job_id, current_session: Session):
"""Edit Job """Edit Job
@ -390,20 +392,20 @@ def update_job(event_id, job_id, current_session: Session):
return jsonify(job) return jsonify(job)
@EventPlugin.blueprint.route("/events/jobs", methods=["GET"]) @blueprint.route("/events/jobs", methods=["GET"])
@login_required() @login_required()
def get_jobs(current_session: Session): def get_jobs(current_session: Session):
count, result = event_controller.get_jobs(current_session.user_, *get_filter_args()) count, result = event_controller.get_jobs(current_session.user_, *get_filter_args())
return jsonify({"count": count, "result": result}) return jsonify({"count": count, "result": result})
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>", methods=["GET"]) @blueprint.route("/events/jobs/<int:job_id>", methods=["GET"])
@login_required() @login_required()
def get_job(job_id, current_session: Session): def get_job(job_id, current_session: Session):
return jsonify(event_controller.get_job(job_id)) return jsonify(event_controller.get_job(job_id))
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"]) @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):
"""Assign / unassign user to the Job """Assign / unassign user to the Job
@ -439,7 +441,7 @@ def assign_job(job_id, current_session: Session):
return jsonify(job) return jsonify(job)
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/lock", methods=["POST"]) @blueprint.route("/events/jobs/<int:job_id>/lock", methods=["POST"])
@login_required(permissions.LOCK_JOBS) @login_required(permissions.LOCK_JOBS)
def lock_job(job_id, current_session: Session): def lock_job(job_id, current_session: Session):
"""Lock / unlock the Job """Lock / unlock the Job
@ -466,7 +468,7 @@ def lock_job(job_id, current_session: Session):
return no_content() return no_content()
@EventPlugin.blueprint.route("/events/invitations", methods=["POST"]) @blueprint.route("/events/invitations", methods=["POST"])
@login_required() @login_required()
def invite(current_session: Session): def invite(current_session: Session):
"""Invite an user to a job or transfer job """Invite an user to a job or transfer job
@ -504,13 +506,13 @@ def invite(current_session: Session):
raise BadRequest raise BadRequest
@EventPlugin.blueprint.route("/events/invitations", methods=["GET"]) @blueprint.route("/events/invitations", methods=["GET"])
@login_required() @login_required()
def get_invitations(current_session: Session): def get_invitations(current_session: Session):
return jsonify(event_controller.get_invitations(current_session.user_)) return jsonify(event_controller.get_invitations(current_session.user_))
@EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"]) @blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"])
@login_required() @login_required()
def get_invitation(invitation_id: int, current_session: Session): def get_invitation(invitation_id: int, current_session: Session):
inv = event_controller.get_invitation(invitation_id) inv = event_controller.get_invitation(invitation_id)
@ -519,7 +521,7 @@ def get_invitation(invitation_id: int, current_session: Session):
return jsonify(inv) return jsonify(inv)
@EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["DELETE", "PUT"]) @blueprint.route("/events/invitations/<int:invitation_id>", methods=["DELETE", "PUT"])
@login_required() @login_required()
def respond_invitation(invitation_id: int, current_session: Session): def respond_invitation(invitation_id: int, current_session: Session):
inv = event_controller.get_invitation(invitation_id) inv = event_controller.get_invitation(invitation_id)

View File

@ -28,8 +28,6 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-card>
<q-card-section>
<q-table :title="title" :rows="rows" row-key="id" :columns="columns"> <q-table :title="title" :rows="rows" row-key="id" :columns="columns">
<template #top-right> <template #top-right>
<q-input <q-input
@ -51,8 +49,6 @@
</q-td> </q-td>
</template> </template>
</q-table> </q-table>
</q-card-section>
</q-card>
</div> </div>
</template> </template>