Renamed schedule to events, added recurring events and invites
This commit is contained in:
parent
e31c5756a6
commit
4cd353cf4e
|
@ -17,10 +17,10 @@ from . import event_controller, permissions
|
|||
from . import models
|
||||
from ...utils.HTTP import no_content
|
||||
|
||||
schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule")
|
||||
schedule_bp = Blueprint("events", __name__, url_prefix="/schedule")
|
||||
|
||||
|
||||
class SchedulePlugin(Plugin):
|
||||
class EventPlugin(Plugin):
|
||||
models = models
|
||||
|
||||
def __init__(self, config):
|
||||
|
@ -268,12 +268,20 @@ def create_event(current_session):
|
|||
except (NotFound, ValueError):
|
||||
raise BadRequest("Invalid parameter")
|
||||
|
||||
recurrence_rule = None
|
||||
if "recurrence_rule" in data:
|
||||
recurrence_rule = event_controller.create_recurrence(data=data["recurrence_rule"])
|
||||
event = event_controller.create_event(
|
||||
start=start, end=end, event_type=event_type, description=data.get("description", None)
|
||||
start=start,
|
||||
end=end,
|
||||
event_type=event_type,
|
||||
description=data.get("description", None),
|
||||
recurrence_rule=recurrence_rule,
|
||||
)
|
||||
if "jobs" in data:
|
||||
for job in data["jobs"]:
|
||||
_add_job(event, job)
|
||||
|
||||
return jsonify(event)
|
||||
|
||||
|
||||
|
@ -297,6 +305,8 @@ def modify_event(event_id, current_session):
|
|||
data = request.get_json()
|
||||
if "start" in data:
|
||||
event.start = from_iso_format(data["start"])
|
||||
if "end" in data:
|
||||
event.end = from_iso_format(data["end"])
|
||||
if "description" in data:
|
||||
event.description = data["description"]
|
||||
if "type" in data:
|
|
@ -3,7 +3,8 @@ from sqlalchemy.exc import IntegrityError
|
|||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.database import db
|
||||
from flaschengeist.plugins.schedule.models import EventType, Event, Job, JobType, Service
|
||||
from flaschengeist.plugins.events.models import EventType, Event, Job, JobType, Service, RecurrenceRule
|
||||
from flaschengeist.utils.datetime import from_iso_format
|
||||
|
||||
|
||||
def update():
|
||||
|
@ -97,7 +98,7 @@ def delete_job_type(name):
|
|||
raise BadRequest("Type still in use")
|
||||
|
||||
|
||||
def get_event(event_id):
|
||||
def get_event(event_id) -> Event:
|
||||
event = Event.query.get(event_id)
|
||||
if event is None:
|
||||
raise NotFound
|
||||
|
@ -128,11 +129,15 @@ def delete_event(event_id):
|
|||
db.session.commit()
|
||||
|
||||
|
||||
def create_event(event_type, start, end=None, jobs=[], description=None):
|
||||
def create_event(event_type, start, end=None, jobs=[], description=None, recurrence_rule=None):
|
||||
try:
|
||||
logger.debug(event_type)
|
||||
event = Event(start=start, end=end, description=description, type=event_type, jobs=jobs)
|
||||
event = Event(
|
||||
start=start, end=end, description=description, type=event_type, jobs=jobs, recurrence_rule=recurrence_rule
|
||||
)
|
||||
db.session.add(event)
|
||||
if recurrence_rule is not None:
|
||||
event = Event(start=start, end=end, description=description, type=event_type, jobs=jobs, template_=event)
|
||||
db.session.commit()
|
||||
return event
|
||||
except IntegrityError:
|
||||
|
@ -141,7 +146,7 @@ def create_event(event_type, start, end=None, jobs=[], description=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()
|
||||
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 js
|
||||
|
@ -168,7 +173,7 @@ def delete_job(job: Job):
|
|||
|
||||
|
||||
def assign_to_job(job: Job, user, value):
|
||||
service = Service.query.get((job.id, user._id))
|
||||
service = Service.query.get((job.id, user.id_))
|
||||
if value < 0:
|
||||
if not service:
|
||||
raise BadRequest
|
||||
|
@ -180,3 +185,18 @@ def assign_to_job(job: Job, user, value):
|
|||
service = Service(user_=user, value=value, job_=job)
|
||||
db.session.add(service)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def create_recurrence(data=None, count=None, end_date=None, frequency=None, interval=None):
|
||||
if data is not None:
|
||||
if "frequency" not in data:
|
||||
raise BadRequest("Missing POST parameter")
|
||||
frequency = data["frequency"]
|
||||
if "end_date" in data:
|
||||
end_date = from_iso_format(data["end_date"])
|
||||
if "count" in data:
|
||||
count = data["count"]
|
||||
if "interval" in data:
|
||||
interval = data["interval"]
|
||||
recurrence = RecurrenceRule(frequency=frequency, end_date=end_date, count=count, interval=interval)
|
||||
db.session.add(recurrence)
|
|
@ -0,0 +1,142 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from sqlalchemy import UniqueConstraint
|
||||
|
||||
from flaschengeist.models import ModelSerializeMixin, UtcDateTime
|
||||
from flaschengeist.models.user import User
|
||||
from flaschengeist.database import db
|
||||
|
||||
#########
|
||||
# Types #
|
||||
#########
|
||||
|
||||
_table_prefix_ = "events_"
|
||||
|
||||
|
||||
class EventType(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "event_type"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
class JobType(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "job_type"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
########
|
||||
# Jobs #
|
||||
########
|
||||
|
||||
|
||||
class Service(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "service"
|
||||
userid: str = ""
|
||||
value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
_job_id = db.Column(
|
||||
"job_id", db.Integer, db.ForeignKey(f"{_table_prefix_}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")
|
||||
job_: Job = db.relationship("Job")
|
||||
|
||||
@property
|
||||
def userid(self):
|
||||
return self.user_.userid
|
||||
|
||||
|
||||
class Job(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "job"
|
||||
_type_id = db.Column("type_id", db.Integer, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False)
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
start: datetime = db.Column(UtcDateTime, nullable=False)
|
||||
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||
type: Union[JobType, int] = db.relationship("JobType")
|
||||
comment: Optional[str] = db.Column(db.String(256))
|
||||
services: list[Service] = db.relationship("Service", back_populates="job_")
|
||||
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
event_ = db.relationship("Event", back_populates="jobs")
|
||||
event_id_ = db.Column("event_id", db.Integer, db.ForeignKey(f"{_table_prefix_}event.id"), nullable=False)
|
||||
|
||||
__table_args__ = (UniqueConstraint("type_id", "start", name="_type_start_uc"),)
|
||||
|
||||
|
||||
##########
|
||||
# Events #
|
||||
##########
|
||||
|
||||
|
||||
class _Frequency(enum.Enum):
|
||||
daily = 1
|
||||
weekly = 2
|
||||
monthly = 3
|
||||
yearly = 4
|
||||
|
||||
|
||||
class RecurrenceRule(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "recurrence_rule"
|
||||
|
||||
frequency: str = db.Column(db.Enum(_Frequency))
|
||||
until: Optional[datetime] = db.Column(UtcDateTime)
|
||||
count: Optional[int] = db.Column(db.Integer)
|
||||
interval: int = db.Column(db.Integer, nullable=False, default=1)
|
||||
|
||||
id_: int = db.Column("id", db.Integer, primary_key=True)
|
||||
|
||||
|
||||
class Event(db.Model, ModelSerializeMixin):
|
||||
"""Model for an Event"""
|
||||
|
||||
__tablename__ = _table_prefix_ + "event"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
start: datetime = db.Column(UtcDateTime, nullable=False)
|
||||
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||
description: Optional[str] = db.Column(db.String(255))
|
||||
type: Union[EventType, int] = db.relationship("EventType")
|
||||
jobs: list[Job] = db.relationship(
|
||||
"Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]"
|
||||
)
|
||||
recurrence_rule: Optional[RecurrenceRule] = db.relationship("RecurrenceRule")
|
||||
template_id: Optional[int] = db.Column("template_id", db.Integer, db.ForeignKey(f"{_table_prefix_}event.id"))
|
||||
# Not exported properties for backend use
|
||||
template_ = db.relationship("Event")
|
||||
# Protected for internal use
|
||||
_recurrence_rule_id = db.Column(
|
||||
"recurrence_rule_id", db.Integer, db.ForeignKey(f"{_table_prefix_}recurrence_rule.id")
|
||||
)
|
||||
_type_id = db.Column(
|
||||
"type_id", db.Integer, db.ForeignKey(f"{_table_prefix_}event_type.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
|
||||
|
||||
class Invite(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "invite"
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
job_id: int = db.Column(db.Integer, db.ForeignKey(_table_prefix_ + "job.id"), nullable=False)
|
||||
# Dummy properties for API export
|
||||
invitee_id: str = None
|
||||
sender_id: str = None
|
||||
# Not exported properties for backend use
|
||||
invitee_: User = db.relationship("User", foreign_keys="Invite._invitee_id")
|
||||
sender_: User = db.relationship("User", foreign_keys="Invite._sender_id")
|
||||
# Protected properties needed for internal use
|
||||
_invitee_id = db.Column("invitee_id", db.Integer, db.ForeignKey("user.id"), nullable=False)
|
||||
_sender_id = db.Column("sender_id", db.Integer, db.ForeignKey("user.id"), nullable=False)
|
||||
|
||||
@property
|
||||
def invitee_id(self):
|
||||
return self.invitee_.userid
|
||||
|
||||
@property
|
||||
def sender_id(self):
|
||||
return self.sender_.userid
|
|
@ -1,88 +0,0 @@
|
|||
from __future__ import annotations # TODO: Remove if python requirement is >= 3.10
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from sqlalchemy import UniqueConstraint
|
||||
|
||||
from flaschengeist.models import ModelSerializeMixin, UtcDateTime
|
||||
from flaschengeist.models.user import User
|
||||
from flaschengeist.database import db
|
||||
|
||||
#########
|
||||
# Types #
|
||||
#########
|
||||
|
||||
|
||||
class EventType(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = "schedule_event_type"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
class JobType(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = "schedule_job_type"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
########
|
||||
# Jobs #
|
||||
########
|
||||
|
||||
|
||||
class Service(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = "schedule_service"
|
||||
userid: str = ""
|
||||
value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
_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")
|
||||
job_ = db.relationship("Job")
|
||||
|
||||
@property
|
||||
def userid(self):
|
||||
return self.user_.userid
|
||||
|
||||
|
||||
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(db.Integer, primary_key=True)
|
||||
start: datetime = db.Column(UtcDateTime, nullable=False)
|
||||
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||
type: Union[JobType, int] = db.relationship("JobType")
|
||||
comment: Optional[str] = db.Column(db.String(256))
|
||||
services: list[Service] = db.relationship("Service", back_populates="job_")
|
||||
required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
|
||||
|
||||
event_ = db.relationship("Event", back_populates="jobs")
|
||||
|
||||
__table_args__ = (UniqueConstraint("type_id", "start", name="_type_start_uc"),)
|
||||
|
||||
|
||||
##########
|
||||
# Events #
|
||||
##########
|
||||
|
||||
|
||||
class Event(db.Model, ModelSerializeMixin):
|
||||
"""Model for an Event"""
|
||||
|
||||
__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)
|
||||
end: Optional[datetime] = db.Column(UtcDateTime)
|
||||
description: Optional[str] = db.Column(db.String(255))
|
||||
type: Union[EventType, int] = db.relationship("EventType")
|
||||
jobs: list[Job] = db.relationship(
|
||||
"Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]"
|
||||
)
|
2
setup.py
2
setup.py
|
@ -34,7 +34,7 @@ setup(
|
|||
"users = flaschengeist.plugins.users:UsersPlugin",
|
||||
"roles = flaschengeist.plugins.roles:RolesPlugin",
|
||||
"balance = flaschengeist.plugins.balance:BalancePlugin",
|
||||
"schedule = flaschengeist.plugins.schedule:SchedulePlugin",
|
||||
"events = flaschengeist.plugins.events:EventPlugin",
|
||||
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
|
||||
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
|
||||
],
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import pytest
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
import flaschengeist.plugins.events.event_controller as event_controller
|
||||
from flaschengeist.plugins.events.models import EventType
|
||||
|
||||
VALID_TOKEN = "f4ecbe14be3527ca998143a49200e294"
|
||||
EVENT_TYPE_NAME = "Test Type"
|
||||
|
||||
|
||||
def test_create_event_type(app):
|
||||
with app.app_context():
|
||||
type = event_controller.create_event_type(EVENT_TYPE_NAME)
|
||||
assert isinstance(type, EventType)
|
||||
|
||||
with pytest.raises(BadRequest):
|
||||
event_controller.create_event_type(EVENT_TYPE_NAME)
|
Loading…
Reference in New Issue