Skip to content

Commit

Permalink
Replace allow_rsvp with rsvp_state
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Nov 20, 2023
1 parent f3e10d6 commit 14ac06a
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 21 deletions.
51 changes: 40 additions & 11 deletions funnel/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
UuidMixin,
backref,
db,
hybrid_property,
relationship,
sa,
types,
Expand Down Expand Up @@ -66,6 +67,12 @@ class CFP_STATE(LabeledEnum): # noqa: N801
ANY = {NONE, PUBLIC, CLOSED}


class RSVP_STATE(LabeledEnum): # noqa: N801
NONE = (1, __("Not accepting registrations"))
ALL = (2, __("Anyone can register"))
MEMBERS = (3, __("Only members can register"))


# --- Models ------------------------------------------------------------------


Expand Down Expand Up @@ -160,6 +167,19 @@ class Project(UuidMixin, BaseScopedNameMixin, Model):
StateManager('_cfp_state', CFP_STATE, doc="CfP state"), call={'all'}
)

#: State of RSVPs
rsvp_state: Mapped[int] = with_roles(
sa.orm.mapped_column(
sa.SmallInteger,
StateManager.check_constraint('rsvp_state', RSVP_STATE),
default=RSVP_STATE.NONE,
nullable=False,
),
read={'all'},
write={'editor', 'promoter'},
datasets={'primary', 'without_parent', 'related'},
)

#: Audit timestamp to detect re-publishing to re-surface a project
first_published_at: Mapped[datetime | None] = sa.orm.mapped_column(
sa.TIMESTAMP(timezone=True), nullable=True
Expand Down Expand Up @@ -204,11 +224,6 @@ class Project(UuidMixin, BaseScopedNameMixin, Model):
sa.LargeBinary, nullable=True, deferred=True
)

allow_rsvp: Mapped[bool] = with_roles(
sa.orm.mapped_column(sa.Boolean, default=True, nullable=False),
read={'all'},
datasets={'primary', 'without_parent', 'related'},
)
buy_tickets_url: Mapped[furl | None] = with_roles(
sa.orm.mapped_column(UrlType, nullable=True),
read={'all'},
Expand Down Expand Up @@ -628,10 +643,11 @@ def datelocation(self) -> str:
> 30 Dec 2018–02 Jan 2019, Bangalore
"""
# FIXME: Replace strftime with Babel formatting
daterange = ''
if self.start_at is not None and self.end_at is not None:
schedule_start_at_date = self.start_at_localized.date()
schedule_end_at_date = self.end_at_localized.date()
start_at = self.start_at_localized
end_at = self.end_at_localized
if start_at is not None and end_at is not None:
schedule_start_at_date = start_at.date()
schedule_end_at_date = end_at.date()
daterange_format = '{start_date}–{end_date} {year}'
if schedule_start_at_date == schedule_end_at_date:
# if both dates are same, in case of single day project
Expand All @@ -646,11 +662,18 @@ def datelocation(self) -> str:
elif schedule_start_at_date.month == schedule_end_at_date.month:
# If multi-day event in same month
strf_date = '%d'
else:
raise ValueError(
"This should not happen: unknown date range"
f" {schedule_start_at_date}{schedule_end_at_date}"
)
daterange = daterange_format.format(
start_date=schedule_start_at_date.strftime(strf_date),
end_date=schedule_end_at_date.strftime('%d %b'),
year=schedule_end_at_date.year,
)
else:
daterange = ''
return ', '.join([_f for _f in [daterange, self.location] if _f])

# TODO: Removing Delete feature till we figure out siteadmin feature
Expand All @@ -662,7 +685,7 @@ def datelocation(self) -> str:
# pass

@sa.orm.validates('name', 'account')
def _validate_and_create_redirect(self, key, value):
def _validate_and_create_redirect(self, key: str, value: str | None) -> str:
# TODO: When labels, venues and other resources are relocated from project to
# account, this validator can no longer watch for `account` change. We'll need a
# more elaborate transfer mechanism that remaps resources to equivalent ones in
Expand Down Expand Up @@ -710,7 +733,13 @@ def end_at_localized(self):
"""Return localized end_at timestamp."""
return localize_timezone(self.end_at, tz=self.timezone) if self.end_at else None

def update_schedule_timestamps(self):
@with_roles(read={'all'}, datasets={'primary', 'without_parent', 'related'})
@hybrid_property
def allow_rsvp(self) -> bool:
"""RSVP state as a boolean value (allowed for all or not)."""
return self.rsvp_state == RSVP_STATE.ALL

def update_schedule_timestamps(self) -> None:
"""Update cached timestamps from sessions."""
self.start_at = self.schedule_start_at
self.end_at = self.schedule_end_at
Expand Down
27 changes: 17 additions & 10 deletions funnel/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from types import SimpleNamespace

from flask import Response, abort, current_app, flash, render_template, request
from flask_babel import format_number
from flask_babel import LazyString, format_number
from markupsafe import Markup

from baseframe import _, __, forms
Expand Down Expand Up @@ -65,14 +65,19 @@
class CountWords:
"""Labels for a count of registrations."""

unregistered: str
registered: str
not_following: str
following: str
unregistered: str | LazyString
registered: str | LazyString
not_following: str | LazyString
following: str | LazyString


registration_count_messages = [
CountWords(__("Be the first to register!"), '', __("Be the first follower!"), ''),
CountWords(
__("Be the first to register!"),
'',
__("Be the first follower!"),
'',
),
CountWords(
__("One registration so far"),
__("You have registered"),
Expand Down Expand Up @@ -142,7 +147,9 @@ class CountWords:
)


def get_registration_text(count: int, registered=False, follow_mode=False) -> str:
def get_registration_text(
count: int, registered=False, follow_mode=False
) -> str | LazyString:
if count < len(registration_count_messages):
if registered and not follow_mode:
return registration_count_messages[count].registered
Expand All @@ -162,7 +169,7 @@ def get_registration_text(count: int, registered=False, follow_mode=False) -> st

@Project.features('rsvp')
def feature_project_rsvp(obj: Project) -> bool:
return (
return bool(
obj.state.PUBLISHED
and obj.allow_rsvp is True
and (obj.start_at is None or not obj.state.PAST)
Expand Down Expand Up @@ -214,7 +221,7 @@ def feature_project_deregister(obj: Project) -> bool:

@Project.features('schedule_no_sessions')
def feature_project_has_no_sessions(obj: Project) -> bool:
return obj.state.PUBLISHED and not obj.start_at
return bool(obj.state.PUBLISHED and not obj.start_at)


@Project.features('comment_new')
Expand All @@ -233,7 +240,7 @@ def project_follow_mode(obj: Project) -> bool:


@Project.views('registration_text')
def project_registration_text(obj: Project) -> str:
def project_registration_text(obj: Project) -> str | LazyString:
return get_registration_text(
count=obj.rsvp_count_going,
registered=obj.features.rsvp_registered(),
Expand Down
76 changes: 76 additions & 0 deletions migrations/versions/f0ed25eed4bc_replace_rsvp_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Replace RSVP flag.
Revision ID: f0ed25eed4bc
Revises: 37b764bbddd8
Create Date: 2023-11-20 17:23:54.141389
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = 'f0ed25eed4bc'
down_revision: str = '37b764bbddd8'
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None


project = sa.table(
'project',
sa.column('id', sa.Integer()),
sa.column('rsvp_state', sa.SmallInteger()),
sa.column('allow_rsvp', sa.Boolean()),
)


class RsvpState:
NONE = 1
ALL = 2


def upgrade(engine_name: str = '') -> None:
"""Upgrade all databases."""
# Do not modify. Edit `upgrade_` instead
globals().get(f'upgrade_{engine_name}', lambda: None)()


def downgrade(engine_name: str = '') -> None:
"""Downgrade all databases."""
# Do not modify. Edit `downgrade_` instead
globals().get(f'downgrade_{engine_name}', lambda: None)()


def upgrade_() -> None:
"""Upgrade default database."""
op.add_column('project', sa.Column('rsvp_state', sa.SmallInteger(), nullable=True))
op.execute(
project.update().values(
rsvp_state=sa.case(
(project.c.allow_rsvp.is_(False), RsvpState.NONE),
(project.c.allow_rsvp.is_(True), RsvpState.ALL),
else_=RsvpState.NONE,
)
)
)
op.alter_column('project', 'rsvp_state', nullable=False)
op.drop_column('project', 'allow_rsvp')


def downgrade_() -> None:
"""Downgrade default database."""
op.add_column(
'project',
sa.Column('allow_rsvp', sa.BOOLEAN(), nullable=True),
)
op.execute(
project.update().values(
rsvp_state=sa.case(
(project.c.rsvp_state.is_(RsvpState.NONE), False),
(project.c.rsvp_state.is_(RsvpState.ALL), True),
else_=False,
)
)
)
op.alter_column('project', 'allow_rsvp', nullable=False)
op.drop_column('project', 'rsvp_state')

0 comments on commit 14ac06a

Please sign in to comment.