Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shifting to tag-based functionality for registration questions & attendance/check-in #1157

Merged
merged 10 commits into from
Aug 25, 2023
81 changes: 45 additions & 36 deletions api/app/attendance/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
from app.invitedGuest.repository import InvitedGuestRepository as invited_guest_repository
from app.registrationResponse.repository import RegistrationRepository as registration_response_repository
from app.guestRegistrations.repository import GuestRegistrationRepository as guest_registration_repository
from app.registration.repository import RegistrationFormRepository as registration_form_repository
from app.events.repository import EventRepository as event_repository
from app.users.repository import UserRepository as user_repository
from app.utils.auth import auth_required
from app.utils.emailer import email_user
from app.utils.errors import ATTENDANCE_ALREADY_CONFIRMED, ATTENDANCE_NOT_FOUND, EVENT_NOT_FOUND, FORBIDDEN, USER_NOT_FOUND, INDEMNITY_NOT_FOUND, INDEMNITY_NOT_SIGNED, NOT_A_GUEST
from app.registration.models import Offer
from app.registration.models import get_registration_answer_based_headline
from app.registration.models import Offer, OfferTag
from app.registration.models import get_registration_answer_by_question_id
from app.invitedGuest.models import InvitedGuest
from app.tags.api import TagAPI

attendance_fields = {
'id': fields.Integer,
Expand All @@ -27,13 +29,17 @@
'timestamp': fields.DateTime('iso8601'),
'signed_indemnity_form': fields.Boolean,
'updated_by_user_id': fields.Integer,
'accommodation_award': fields.Boolean,
'shirt_size': fields.String,
'is_invitedguest': fields.Boolean,
'bringing_poster': fields.Boolean,
'message': fields.String,
'invitedguest_role': fields.String,
'confirmed': fields.Boolean
'confirmed': fields.Boolean,
'registration_metadata': fields.List(fields.Nested({
'name': fields.String,
'response': fields.String
})),
'offer_metadata': fields.List(fields.Nested({
'name': fields.String
}))
}

_attendee_fields = {
Expand All @@ -46,33 +52,27 @@


class AttendanceUser():
def __init__(self, user, attendance, accommodation_award, shirt_size, is_invitedguest, bringing_poster, invitedguest_role, confirmed):
def __init__(self, user, attendance, is_invitedguest, invitedguest_role, confirmed, registration_metadata, offer_metadata):
self.id = attendance.id if attendance is not None else None
self.fullname = user.full_name
self.event_id = attendance.event_id if attendance is not None else None
self.user_id = user.id
self.timestamp = attendance.timestamp if attendance is not None else None
self.signed_indemnity_form = attendance.indemnity_signed if attendance is not None else None
self.updated_by_user_id = attendance.updated_by_user_id if attendance is not None else None
self.accommodation_award = accommodation_award
self.shirt_size = shirt_size
self.is_invitedguest = is_invitedguest
self.bringing_poster = bringing_poster
self.invitedguest_role = invitedguest_role
self.confirmed = confirmed
self.registration_metadata = registration_metadata
self.offer_metadata = offer_metadata


def _get_registration_answer(user_id, event_id, headline, is_invited_guest):
def _get_registration_answer(user_id, event_id, question_id, is_invited_guest):
if is_invited_guest:
answer = guest_registration_repository.get_guest_registration_answer_by_headline(user_id, event_id, headline)
answer = guest_registration_repository.get_guest_registration_answer_by_question_id(user_id, event_id, question_id)
else:
answer = get_registration_answer_based_headline(user_id, event_id, headline)
answer = get_registration_answer_by_question_id(user_id, event_id, question_id)

if answer is None:
return None
else:
return answer.value

return answer.value if answer else "Unanswered"

class AttendanceAPI(AttendanceMixin, restful.Resource):

Expand All @@ -97,16 +97,10 @@ def get(self):

attendance = attendance_repository.get(event_id, user_id)

has_accepted_accom_award = False

offer = db.session.query(Offer).filter(
Offer.user_id == user_id).filter(Offer.event_id == event_id).first()
# TODO: Add tags from offer to attendance.

# Check if invited guest
invited_guest = db.session.query(InvitedGuest).filter(
InvitedGuest.event_id == event_id).filter(InvitedGuest.user_id == user.id).first()
if(invited_guest):
if (invited_guest):
is_invited_guest = True
invitedguest_role = invited_guest.role
confirmed = True
Expand All @@ -115,16 +109,31 @@ def get(self):
confirmed = registration.confirmed if registration is not None else True
invitedguest_role = "General Attendee"
is_invited_guest = False
# Shirt Size
shirt_answer = _get_registration_answer(user_id, event_id, "T-Shirt Size", is_invited_guest)

# Poster registration
bringing_poster = _get_registration_answer(user_id, event_id, "Will you be bringing a poster?", is_invited_guest) == 'yes'

attendance_user = AttendanceUser(user, attendance, accommodation_award=has_accepted_accom_award,
shirt_size=shirt_answer, is_invitedguest=is_invited_guest,
bringing_poster=bringing_poster, invitedguest_role=invitedguest_role,
confirmed=confirmed)

# collate all tags belonging to user into a list of (tag_id, tag_name, answer) tuples
# first, get all tags from registration questions
registration_metadata = []
questions_with_tags = registration_form_repository.get_registration_questions_with_tags(event_id)
for question in questions_with_tags:
for question_tag in question.tags:
answer = _get_registration_answer(user_id, event_id, question.id, is_invited_guest)
registration_metadata.append({"name": TagAPI._stringify_tag_name(question_tag.tag), "response": answer})
print(registration_metadata)

# second, get all tags from offers
offer_metadata = []
offer = db.session.query(Offer).filter(
danielamassiceti marked this conversation as resolved.
Show resolved Hide resolved
Offer.user_id == user_id).filter(Offer.event_id == event_id).first()
if offer:
offer_tags = db.session.query(OfferTag).filter(
danielamassiceti marked this conversation as resolved.
Show resolved Hide resolved
OfferTag.offer_id == offer.id).all()
for offer_tag in offer_tags:
offer_metadata.append({"name": TagAPI._stringify_tag_name(offer_tag.tag)})
print(offer_metadata)

attendance_user = AttendanceUser(user, attendance, is_invitedguest=is_invited_guest,
invitedguest_role=invitedguest_role,
confirmed=confirmed, registration_metadata=registration_metadata, offer_metadata=offer_metadata)
return marshal(attendance_user, attendance_fields), 200

@auth_required
Expand Down
6 changes: 3 additions & 3 deletions api/app/guestRegistrations/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ def timeseries_guest_registrations(event_id):
.order_by(cast(GuestRegistration.created_at, Date))
.all())
return timeseries

@staticmethod
def get_guest_registration_answer_by_headline(user_id, event_id, headline):
def get_guest_registration_answer_by_question_id(user_id, event_id, question_id):
answer = (
db.session.query(GuestRegistrationAnswer)
.join(GuestRegistration, GuestRegistrationAnswer.guest_registration_id == GuestRegistration.id)
.filter_by(user_id=user_id)
.join(RegistrationForm, GuestRegistration.registration_form_id == RegistrationForm.id)
.filter_by(event_id=event_id)
.join(RegistrationQuestion, GuestRegistrationAnswer.registration_question_id == RegistrationQuestion.id)
.filter_by(headline=headline)
.filter_by(id=question_id)
.first())
return answer

Expand Down
12 changes: 2 additions & 10 deletions api/app/registration/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from sqlalchemy.exc import SQLAlchemyError
from app.events.models import Event
from app.tags.models import Tag, TagType
from app.tags.api import TagAPI
from app.registration.models import Offer, OfferTag
from app.registration.mixins import OfferMixin
from app.users.models import AppUser
Expand All @@ -27,7 +28,6 @@
from app.responses.repository import ResponseRepository as response_repository
from app.registration.repository import OfferRepository as offer_repository
from app.registration.repository import RegistrationRepository as registration_repository
from app.users.repository import UserRepository as user_repository

def offer_info(offer_entity, requested_travel=None):
return {
Expand Down Expand Up @@ -97,14 +97,6 @@ def _serialize_tag(offer_tag, language='en'):
'accepted': offer_tag.accepted
}

@staticmethod
def _stringify_tag_name_description(offer_tag, language='en'):
translation = offer_tag.tag.get_translation(language)
if translation is None:
LOGGER.warn('Could not find {} translation for tag id {}'.format(language, offer_tag.tag.id))
translation = offer_tag.tag.get_translation('en')
return '{}: {}'.format(translation.name, translation.description)

@auth_required
def put(self):
# update existing offer
Expand Down Expand Up @@ -210,7 +202,7 @@ def post(self, event_id):
db.session.commit()

if grant_tags:
grant_strs = [OfferAPI._stringify_tag_name_description(offer_tag) for offer_tag in offer_entity.offer_tags]
grant_strs = [TagAPI._stringify_tag_name_description(offer_tag.tag) for offer_tag in offer_entity.offer_tags]
grants_summary = "\n\u2022 " + "\n\u2022 ".join(grant_strs)
email_template = 'offer-grants'
else:
Expand Down
37 changes: 24 additions & 13 deletions api/app/registration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,16 @@ def __init__(self, registration_form_id, name, description, order, show_for_tag_
self.show_for_tag_id = show_for_tag_id
self.show_for_invited_guest = show_for_invited_guest


def get_registration_answer_based_headline(user_id, event_id, headline):
answer = (
db.session.query(RegistrationAnswer)
.join(Registration, RegistrationAnswer.registration_id == Registration.id)
.join(Offer, Offer.id == Registration.offer_id)
.filter_by(user_id=user_id, event_id=event_id)
.join(RegistrationQuestion, RegistrationAnswer.registration_question_id == RegistrationQuestion.id)
.filter_by(headline=headline)
.first())
return answer

def get_registration_answer_by_question_id(user_id, event_id, question_id):
answer = (
db.session.query(RegistrationAnswer)
.join(Registration, RegistrationAnswer.registration_id == Registration.id)
.join(Offer, Offer.id == Registration.offer_id)
.filter_by(user_id=user_id, event_id=event_id)
.join(RegistrationQuestion, RegistrationAnswer.registration_question_id == RegistrationQuestion.id)
.filter_by(id=question_id)
.first())
return answer

class RegistrationQuestion(db.Model):

Expand All @@ -129,6 +127,8 @@ class RegistrationQuestion(db.Model):
"registration_question.id"), nullable=True)
hide_for_dependent_value = db.Column(db.String(), nullable=True)

tags = db.relationship('RegistrationQuestionTag')

def __init__(self, registration_form_id, section_id, headline, placeholder, order, type, validation_regex, validation_text=None, is_required=True, description=None, options=None):
self.registration_form_id = registration_form_id
self.section_id = section_id
Expand All @@ -142,8 +142,19 @@ def __init__(self, registration_form_id, section_id, headline, placeholder, orde
self.validation_regex = validation_regex
self.validation_text = validation_text

# Registration
class RegistrationQuestionTag(db.Model):
id = db.Column(db.Integer(), primary_key=True)
registration_question_id = db.Column(db.Integer(), db.ForeignKey('registration_question.id'), nullable=False)
tag_id = db.Column(db.Integer(), db.ForeignKey('tag.id'), nullable=False)

registration_question = db.relationship('RegistrationQuestion', foreign_keys=[registration_question_id])
tag = db.relationship('Tag', foreign_keys=[tag_id])

def __init__(self, registration_question_id, tag_id):
self.registration_question_id = registration_question_id
self.tag_id = tag_id

# Registration

class Registration(db.Model):
__tablename__ = "registration"
Expand Down
14 changes: 12 additions & 2 deletions api/app/registration/repository.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from app import db
from app.registration.models import Offer, Registration, RegistrationForm, OfferTag
from app.registration.models import Offer, Registration, RegistrationForm, RegistrationQuestion, OfferTag
from app.tags.models import Tag
from sqlalchemy import and_, func, cast, Date


Expand Down Expand Up @@ -101,7 +102,6 @@ def timeseries_registrations(event_id):
def get_form_for_event(event_id):
return (db.session.query(RegistrationForm).filter_by(event_id=event_id)).first()


class RegistrationFormRepository():
@staticmethod
def get_by_event_id(event_id):
Expand All @@ -112,5 +112,15 @@ def get_by_event_id(event_id):
@staticmethod
def get_by_id(id):
return db.session.query(RegistrationForm).get(id)

@staticmethod
def get_registration_questions_with_tags(event_id):
"""Get all questions with active tags in a registration."""
danielamassiceti marked this conversation as resolved.
Show resolved Hide resolved
return db.session.query(RegistrationQuestion).join(
RegistrationForm, RegistrationQuestion.registration_form_id == RegistrationForm.id).filter(
RegistrationForm.event_id == event_id).filter(
RegistrationQuestion.tags != None).join(
Tag, RegistrationQuestion.tags.any(Tag.active == True)
).all()


4 changes: 1 addition & 3 deletions api/app/registrationResponse/repository.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from app import db
from app.registration.models import Offer, Registration
from app.tags.models import Tag
from app.users.models import AppUser
from app.events.models import Event
from app.attendance.models import Attendance
from sqlalchemy.sql import exists
from app import LOGGER


class RegistrationRepository():

Expand Down
16 changes: 16 additions & 0 deletions api/app/tags/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ def put(self, event_id):
tag_repository.commit()

return _serialize_tag_detail(tag), 200

@staticmethod
danielamassiceti marked this conversation as resolved.
Show resolved Hide resolved
def _stringify_tag_name_description(tag, language='en'):
translation = tag.get_translation(language)
if translation is None:
LOGGER.warn('Could not find {} translation for tag id {}'.format(language, tag.id))
translation = tag.get_translation('en')
return '{}: {}'.format(translation.name, translation.description)

@staticmethod
def _stringify_tag_name(tag, language='en'):
translation = tag.get_translation(language)
if translation is None:
LOGGER.warn('Could not find {} translation for tag id {}'.format(language, tag.id))
translation = tag.get_translation('en')
return '{}'.format(translation.name)

class TagListAPI(restful.Resource):
@event_admin_required
Expand Down
1 change: 1 addition & 0 deletions api/app/tags/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class TagType(Enum):
RESPONSE = 'response'
REGISTRATION = 'registration'
GRANT = 'grant'
QUESTION = 'question'

class Tag(db.Model):
__tablename__ = 'tag'
Expand Down
41 changes: 41 additions & 0 deletions api/migrations/versions/478d1ac3d0ed_addquestiontagmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Add question tag model

Revision ID: 478d1ac3d0ed
Revises: 9143756e596d
Create Date: 2023-08-20 09:31:29.616347

"""

# revision identifiers, used by Alembic.
revision = '478d1ac3d0ed'
down_revision = '9143756e596d'

from alembic import op
import sqlalchemy as sa

def upgrade():
op.create_table('registration_question_tag',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('registration_question_id', sa.Integer(), nullable=False),
sa.Column('tag_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['registration_question_id'], ['registration_question.id'], ),
sa.ForeignKeyConstraint(['tag_id'], ['tag.id'], ),
sa.PrimaryKeyConstraint('id')
)
#op.create_foreign_key('registration_question_registration_question_tag_id_fkey', 'registration_question', 'registration_question_tag', ['id'], ['registration_question_id'])

op.execute("COMMIT")
op.execute("ALTER TYPE tag_type ADD VALUE 'QUESTION'")
# ### end Alembic commands ###


def downgrade():
op.drop_table('registration_question_tag')
#op.drop_constraint('registration_question_registration_question_tag_id_fkey', 'registration_question', type_='foreignkey')

op.execute("""DELETE FROM pg_enum
WHERE enumlabel = 'QUESTION'
AND enumtypid = (
SELECT oid FROM pg_type WHERE typname = 'tag_type'
)""")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class ResponseListComponent extends Component {
reviewerAssignError,
newReviewerEmail,
reviewerAssignSuccess,
tags,
filteredTags,
filteredResponses
} = this.state

Expand Down Expand Up @@ -337,7 +337,7 @@ class ResponseListComponent extends Component {
closeMenuOnSelect={false}
components={animatedComponents}
isMulti
options={this.getSearchTags(tags)}
options={this.getSearchTags(filteredTags)}
id="TagFilter"
placeholder="Search"
onChange={this.updateTagSearch}
Expand Down
Loading