Skip to content

Commit

Permalink
Add custom label for project crew members (resolves #1665) (#1668)
Browse files Browse the repository at this point in the history
  • Loading branch information
jace authored Mar 14, 2023
1 parent 227f194 commit bc30310
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 26 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ install-npm-ci:
npm clean-install

install-python-pip:
pip install --upgrade pip setuptools
pip install --upgrade pip

install-python-dev: install-python-pip deps-editable
pip install -r requirements/dev.txt
pip install --use-pep517 -r requirements/dev.txt

install-python-test: install-python-pip deps-editable
pip install -r requirements/test.txt
pip install --use-pep517 -r requirements/test.txt

install-python: install-python-pip deps-editable
pip install -r requirements/base.txt
pip install --use-pep517 -r requirements/base.txt

install-dev: deps-editable install-python-dev install-npm assets

Expand Down
2 changes: 1 addition & 1 deletion devserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
background_rq = None
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
# Only start RQ worker within the reloader environment
background_rq = BackgroundWorker(rq.get_worker().work)
background_rq = BackgroundWorker(rq.get_worker().work, mock_transports=True)
background_rq.start()

run_simple(
Expand Down
18 changes: 10 additions & 8 deletions funnel/devtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,24 @@ def mock_email(
from_email: Optional[Any] = None,
headers: Optional[dict] = None,
) -> str:
calls.email.append(
CapturedEmail(
subject,
[str(each) for each in to],
content,
str(from_email) if from_email else None,
)
capture = CapturedEmail(
subject,
[str(each) for each in to],
content,
str(from_email) if from_email else None,
)
calls.email.append(capture)
main_app.logger.info(capture)
return token_urlsafe()

def mock_sms(
phone: Any,
message: transports.sms.SmsTemplate,
callback: bool = True,
) -> str:
calls.sms.append(CapturedSms(str(phone), str(message), message.vars()))
capture = CapturedSms(str(phone), str(message), message.vars())
calls.sms.append(capture)
main_app.logger.info(capture)
return token_urlsafe()

# Patch email
Expand Down
8 changes: 7 additions & 1 deletion funnel/forms/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from coaster.utils import getbool

from ..models import OrganizationMembership, ProjectCrewMembership
from .helpers import nullable_strip_filters

__all__ = [
'OrganizationMembershipForm',
Expand Down Expand Up @@ -65,12 +66,17 @@ class ProjectCrewMembershipForm(forms.Form):
"Can check-in a participant using their badge at a physical event"
),
)
label = forms.StringField(
__("Role"),
description=__("Optional – Name this person’s role"),
filters=nullable_strip_filters,
)

def validate(self, *args, **kwargs):
"""Validate form."""
is_valid = super().validate(*args, **kwargs)
if not any([self.is_editor.data, self.is_promoter.data, self.is_usher.data]):
self.is_usher.errors.append("Please select one or more roles")
self.is_usher.errors.append(_("Select one or more roles"))
is_valid = False
return is_valid

Expand Down
2 changes: 1 addition & 1 deletion funnel/forms/proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class ProposalMemberForm(forms.Form):
description=__(
"Optional – A specific role in this submission (like Author or Editor)"
),
filters=[forms.filters.strip()],
filters=nullable_strip_filters,
)
is_uncredited = forms.BooleanField(__("Hide collaborator on submission"))

Expand Down
19 changes: 17 additions & 2 deletions funnel/models/project_membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from werkzeug.utils import cached_property

from coaster.sqlalchemy import DynamicAssociationProxy, with_roles
from coaster.sqlalchemy import DynamicAssociationProxy, immutable, with_roles

from . import Mapped, db, declared_attr, sa
from .helpers import reopen
Expand Down Expand Up @@ -48,7 +48,7 @@ class ProjectCrewMembership(
__null_granted_by__ = True

#: List of is_role columns in this model
__data_columns__ = ('is_editor', 'is_promoter', 'is_usher')
__data_columns__ = ('is_editor', 'is_promoter', 'is_usher', 'label')

__roles__ = {
'all': {
Expand All @@ -59,6 +59,7 @@ class ProjectCrewMembership(
'is_editor',
'is_promoter',
'is_usher',
'label',
}
},
'project_crew': {
Expand All @@ -81,6 +82,7 @@ class ProjectCrewMembership(
'is_editor',
'is_promoter',
'is_usher',
'label',
'user',
'project',
},
Expand All @@ -91,6 +93,7 @@ class ProjectCrewMembership(
'is_editor',
'is_promoter',
'is_usher',
'label',
'user',
},
'related': {
Expand All @@ -100,6 +103,7 @@ class ProjectCrewMembership(
'is_editor',
'is_promoter',
'is_usher',
'label',
},
}

Expand Down Expand Up @@ -134,6 +138,17 @@ class ProjectCrewMembership(
#: the ability to scan badges at the door
is_usher: Mapped[bool] = sa.Column(sa.Boolean, nullable=False, default=False)

#: Optional label, indicating the member's role in the project
label = immutable(
sa.Column(
sa.Unicode,
sa.CheckConstraint(
"label <> ''", name='project_crew_membership_label_check'
),
nullable=True,
)
)

@declared_attr.directive
@classmethod
def __table_args__(cls) -> tuple: # type: ignore[override]
Expand Down
2 changes: 1 addition & 1 deletion funnel/templates/js/membership.js.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
<div class="user__box mui--pull-left">
<useravatar :user='member.user' :addprofilelink=true></useravatar>
<div class="user__box__header">
<h3 class="mui--text-body2 user__box__fullname" data-cy="member">{{ member.user.fullname }}</h3>
<h3 class="mui--text-body2 user__box__fullname" data-cy="member">{{ member.user.fullname }} <span v-if="member.label" class="badge">{{ member.label }}</span></h3>
<h3 v-if="member.user.username" class="mui--text-caption user__box__userid"><span>@{{ member.user.username }}</span></h3>
</div>
</div>
Expand Down
18 changes: 10 additions & 8 deletions funnel/views/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ def edit(self) -> ReturnView:
is_editor=form.is_editor.data,
is_promoter=form.is_promoter.data,
is_usher=form.is_usher.data,
label=form.label.data,
)
except MembershipRevokedError:
return {
Expand All @@ -498,15 +499,16 @@ def edit(self) -> ReturnView:
),
'form_nonce': form.form_nonce.data,
}, 422
db.session.commit()
signals.project_role_change.send(
self.obj.project, actor=current_auth.user, user=self.obj.user
)
dispatch_notification(
ProjectCrewMembershipNotification(
document=self.obj.project, fragment=new_membership
if new_membership != previous_membership:
db.session.commit()
signals.project_role_change.send(
self.obj.project, actor=current_auth.user, user=self.obj.user
)
dispatch_notification(
ProjectCrewMembershipNotification(
document=self.obj.project, fragment=new_membership
)
)
)
return {
'status': 'ok',
'message': _("The member’s roles have been updated"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Add label to project crew membership.
Revision ID: d0a6fab28b7f
Revises: 7aa9eb80aab4
Create Date: 2023-03-15 01:37:55.947574
"""

from typing import Optional, Tuple, Union

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision: str = 'd0a6fab28b7f'
down_revision: str = '7aa9eb80aab4'
branch_labels: Optional[Union[str, Tuple[str, ...]]] = None
depends_on: Optional[Union[str, Tuple[str, ...]]] = None


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


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


def upgrade_() -> None:
"""Upgrade database bind ''."""
with op.batch_alter_table('project_crew_membership', schema=None) as batch_op:
batch_op.add_column(
sa.Column(
'label',
sa.Unicode(),
sa.CheckConstraint(
"label <> ''", name='project_crew_membership_label_check'
),
nullable=True,
)
)


def downgrade_() -> None:
"""Downgrade database bind ''."""
with op.batch_alter_table('project_crew_membership', schema=None) as batch_op:
batch_op.drop_column('label')


def upgrade_geoname() -> None:
"""Upgrade database bind 'geoname'."""


def downgrade_geoname() -> None:
"""Downgrade database bind 'geoname'."""

0 comments on commit bc30310

Please sign in to comment.