From f5c0dcd5eae446bea5d0d9757881f63a0acc0144 Mon Sep 17 00:00:00 2001 From: Mark Murnane Date: Thu, 9 May 2024 20:00:51 -0400 Subject: [PATCH 1/4] Starting on hotel_lottery --- docker-compose.yml | 2 ++ test-defaults.ini | 2 +- uber/config.py | 2 ++ uber/configspec.ini | 23 ++++++++++++ uber/errors.py | 3 ++ uber/forms/__init__.py | 2 +- uber/forms/group.py | 8 +++-- uber/forms/widgets.py | 19 ++++++++++ uber/site_sections/hotel_lottery.py | 19 ++++++++++ uber/templates/hotel_lottery/index.html | 47 +++++++++++++++++++++++++ 10 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 uber/site_sections/hotel_lottery.py create mode 100644 uber/templates/hotel_lottery/index.html diff --git a/docker-compose.yml b/docker-compose.yml index de2296737..b6919a0a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ x-uber: &uber - uber_hostname=localhost - uber_plugins=["magprime"] - uber_dev_box=True + - UBER_CONFIG_FILES=uber.ini + - LOG_CONFIG=true volumes: - $PWD:/app/ - $PWD/../magprime:/app/plugins/magprime diff --git a/test-defaults.ini b/test-defaults.ini index 6b4dead8b..0ed50520a 100644 --- a/test-defaults.ini +++ b/test-defaults.ini @@ -1,7 +1,7 @@ path = "/rams" hostname = "localhost" -url_root = "https://localhost" +url_root = "http://localhost" consent_form_url = "http://magfest.org/parentalconsentform" diff --git a/uber/config.py b/uber/config.py index af085be63..c5540fc7f 100644 --- a/uber/config.py +++ b/uber/config.py @@ -1554,6 +1554,8 @@ def _unrepr(d): c.SAME_NUMBER_REPEATED = r'^(\d)\1+$' +c.HOTEL_LOTTERY = _config.get('hotel_lottery', {}) + # Allows 0-9, a-z, A-Z, and a handful of punctuation characters c.VALID_BADGE_PRINTED_CHARS = r'[a-zA-Z0-9!"#$%&\'()*+,\-\./:;<=>?@\[\\\]^_`\{|\}~ "]' c.EVENT_QR_ID = c.EVENT_QR_ID or c.EVENT_NAME_AND_YEAR.replace(' ', '_').lower() diff --git a/uber/configspec.ini b/uber/configspec.ini index 911841850..1a30ba357 100644 --- a/uber/configspec.ini +++ b/uber/configspec.ini @@ -1152,6 +1152,29 @@ room_deadline = string(default='') # Date after which staffers cannot drop their own shifts drop_shifts_deadline = string(default='') +# Hotel lottery dates +# The first day you can request to check in +hotel_lottery_checkin_start = string(default='%(epoch)s') +# The last day you can request to check in +hotel_lottery_checkin_end = string(default='%(eschaton)s') +# The first day you can request to check out +hotel_lottery_checkout_start = string(default='%(epoch)s') +# The last day you can request to check out +hotel_lottery_checkout_end = string(default='%(eschaton)s') + +# Open and close dates of the lottery form +hotel_lottery_form_open = string(default="") +hotel_lottery_form_close = string(default="") + +[hotel_lottery] +[[__many__]] +name = string(default="Sample Hotel") +description = string(default="A very exemplar hotel you can't stay at") +[[[__many__]]] +name = string(default="Normal Room") +description = string(default="One or more beds, two or more walls.") +price = integer(default=1000) +capacity = integer(default=4) [badge_type_prices] # Add badge types here to make them attendee-facing badges. They will be displayed diff --git a/uber/errors.py b/uber/errors.py index fe8b3a127..dbb4658a8 100644 --- a/uber/errors.py +++ b/uber/errors.py @@ -1,4 +1,5 @@ from urllib.parse import quote +from uber.config import c import cherrypy @@ -49,6 +50,8 @@ def __init__(self, page, *args, **kwargs): query += '{sep}original_location={loc}'.format( sep=qs_char, loc=self.quote(original_location)) + if c.URL_ROOT.startswith("https"): + cherrypy.request.base = cherrypy.request.base.replace("http://", "https://") cherrypy.HTTPRedirect.__init__(self, query) def quote(self, s): diff --git a/uber/forms/__init__.py b/uber/forms/__init__.py index 0323c0c6d..d10fe8592 100644 --- a/uber/forms/__init__.py +++ b/uber/forms/__init__.py @@ -8,7 +8,7 @@ from wtforms.validators import ValidationError from pockets.autolog import log from uber.config import c -from uber.forms.widgets import CountrySelect, IntSelect, MultiCheckbox, NumberInputGroup, SwitchInput +from uber.forms.widgets import CountrySelect, IntSelect, MultiCheckbox, NumberInputGroup, SwitchInput, Ranking from uber.model_checks import invalid_zip_code diff --git a/uber/forms/group.py b/uber/forms/group.py index 94a924040..cb68d21c0 100644 --- a/uber/forms/group.py +++ b/uber/forms/group.py @@ -5,12 +5,16 @@ from wtforms.validators import ValidationError from uber.config import c -from uber.forms import AddressForm, CustomValidation, MultiCheckbox, MagForm, IntSelect, NumberInputGroup +from uber.forms import AddressForm, CustomValidation, MultiCheckbox, MagForm, IntSelect, NumberInputGroup, Ranking from uber.forms.attendee import valid_cellphone from uber.custom_tags import format_currency, pluralize from uber.model_checks import invalid_phone_number -__all__ = ['GroupInfo', 'ContactInfo', 'TableInfo', 'AdminGroupInfo', 'AdminTableInfo'] +__all__ = ['HotelLotteryApplication', 'GroupInfo', 'ContactInfo', 'TableInfo', 'AdminGroupInfo', 'AdminTableInfo'] + + +class HotelLotteryApplication(MagForm): + ranked_hotels = Ranking(c.HOTEL_LOTTERY.keys()) class GroupInfo(MagForm): diff --git a/uber/forms/widgets.py b/uber/forms/widgets.py index 899d093fa..fb1da036e 100644 --- a/uber/forms/widgets.py +++ b/uber/forms/widgets.py @@ -102,3 +102,22 @@ def render_option(cls, value, label, selected, **kwargs): return Markup( "".format(html_params(**options), escape(label)) ) + +class Ranking(): + def __init__(self, choices=None, **kwargs): + self.choices = choices + + def __call__(self, field, choices=None, **kwargs): + choices = choices or self.choices or [('', "ERROR: No choices provided")] + selected_choices = field.data + html = ['
'] + for choice in choices if not choice in selected_choices: + html.append(f'
{choice}
') + html.append('
') + + html.append('
') + for choice in selected_choices: + html.append(f'
{choice}
') + html.append('
') + + return Markup(''.join(html)) \ No newline at end of file diff --git a/uber/site_sections/hotel_lottery.py b/uber/site_sections/hotel_lottery.py new file mode 100644 index 000000000..8d93c7682 --- /dev/null +++ b/uber/site_sections/hotel_lottery.py @@ -0,0 +1,19 @@ +from datetime import datetime + +from uber.config import c +from uber.decorators import ajax, all_renderable +from uber.errors import HTTPRedirect +from uber.models import Attendee, Room, RoomAssignment, Shift + + +@all_renderable() +class Root: + def index(self, session): + print(c.HOTEL_LOTTERY, flush=True) + return { + "checkin_start": c.HOTEL_LOTTERY_CHECKIN_START, + "checkin_end": c.HOTEL_LOTTERY_CHECKIN_END, + "checkout_start": c.HOTEL_LOTTERY_CHECKOUT_START, + "checkout_end": c.HOTEL_LOTTERY_CHECKOUT_END, + "hotels": c.HOTEL_LOTTERY + } diff --git a/uber/templates/hotel_lottery/index.html b/uber/templates/hotel_lottery/index.html new file mode 100644 index 000000000..e0d1d651f --- /dev/null +++ b/uber/templates/hotel_lottery/index.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} +{% block backlink %} +{% if c.ATTENDEE_ACCOUNTS_ENABLED or c.HAS_REGISTRATION_ACCESS or c.HAS_GROUP_ADMIN_ACCESS %} +
+ {% if c.HAS_REGISTRATION_ACCESS and attendee and not attendee.is_new %} + Admin Form + {% elif c.HAS_GROUP_ADMIN_ACCESS and group and not group.is_new %} + Admin Form + {% elif c.HAS_ART_SHOW_ADMIN_ACCESS and app and not app.is_new %} + Admin Form + {% elif c.CURRENT_ADMIN %} + Admin Area + {% endif %} + {% if c.ATTENDEE_ACCOUNTS_ENABLED and logged_in_account and not account and c.PAGE_PATH != '/preregistration/homepage' %} + Homepage + {% endif %} + {% if c.ATTENDEE_ACCOUNTS_ENABLED and logged_in_account %} + Logout + {% endif %} +
+ +{% endif %} +{% endblock %} + +{% block masthead %} +
{{ c.ORGANIZATION_NAME }}
+{% if homepage_account %} +
{% include "preregistration/update_account.html" %}
+{% elif logged_in_account %} +
You are currently logged in as {{ logged_in_account.email }}.
+{% endif %} +{% endblock %} + +{% block footer %} + +{% endblock %} \ No newline at end of file From cdd432481fdc60f5b2acb33669320c9013bbe522 Mon Sep 17 00:00:00 2001 From: Mark Murnane Date: Thu, 9 May 2024 21:06:04 -0400 Subject: [PATCH 2/4] Adding sample config --- uber.ini | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 uber.ini diff --git a/uber.ini b/uber.ini new file mode 100644 index 000000000..a5b3890dc --- /dev/null +++ b/uber.ini @@ -0,0 +1,19 @@ +[hotel_lottery] +[[gaylord]] +name = "Gaylord National Harbor" +description = "A huge hotel" +[[[Double Queen]]] +name = "Double Queen" +description = "Two beds in a box" +price = 10000 +capacity = 4 +[[[King]]] +name = "King" +description = "One bed in a box" +price = 10000 +capacity = 4 +[[[Suite]]] +name = "Suite" +description = "Baller over here" +price = 20000 +capacity = 8 \ No newline at end of file From c4a14493089d97fb98592890e31d8003de3c04d6 Mon Sep 17 00:00:00 2001 From: Mark Murnane Date: Thu, 9 May 2024 23:26:39 -0400 Subject: [PATCH 3/4] MVP of ranking widget --- docker-compose.yml | 4 +- uber.ini | 59 ++++++++++++- uber/config.py | 4 + uber/decorators.py | 1 + uber/forms/__init__.py | 2 + uber/forms/attendee.py | 9 +- uber/forms/widgets.py | 63 +++++++++++--- uber/models/__init__.py | 1 + uber/models/hotel.py | 4 + uber/site_sections/hotel_lottery.py | 44 +++++++++- uber/templates/forms/macros.html | 6 ++ uber/templates/hotel_lottery/form.html | 71 +++++++++++++++ uber/templates/hotel_lottery/index.html | 110 +++++++++++++++--------- 13 files changed, 313 insertions(+), 65 deletions(-) create mode 100644 uber/templates/hotel_lottery/form.html diff --git a/docker-compose.yml b/docker-compose.yml index b6919a0a6..02442ef8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,8 @@ x-uber: &uber - UBER_CONFIG_FILES=uber.ini - LOG_CONFIG=true volumes: - - $PWD:/app/ - - $PWD/../magprime:/app/plugins/magprime + - .:/app/ + - ./../magprime:/app/plugins/magprime services: diff --git a/uber.ini b/uber.ini index a5b3890dc..7c7f47e9a 100644 --- a/uber.ini +++ b/uber.ini @@ -1,7 +1,64 @@ [hotel_lottery] [[gaylord]] name = "Gaylord National Harbor" -description = "A huge hotel" +description = "The drive can't be that bad, can it?" +[[[Double Queen]]] +name = "Double Queen" +description = "Two beds in a box" +price = 10000 +capacity = 4 +[[[King]]] +name = "King" +description = "One bed in a box" +price = 10000 +capacity = 4 +[[[Suite]]] +name = "Suite" +description = "Baller over here" +price = 20000 +capacity = 8 + +[[roof]] +name = "Rooftop Room" +description = "Camping out on the roof of the Donald E. Stephens Convention Center" +[[[Double Queen]]] +name = "Double Queen" +description = "Two beds in a box" +price = 10000 +capacity = 4 +[[[King]]] +name = "King" +description = "One bed in a box" +price = 10000 +capacity = 4 +[[[Suite]]] +name = "Suite" +description = "Baller over here" +price = 20000 +capacity = 8 + +[[cardboard]] +name = "Cardboard Box" +description = "Literally a big box. Do you fits?" +[[[Double Queen]]] +name = "Double Queen" +description = "Two beds in a box" +price = 10000 +capacity = 4 +[[[King]]] +name = "King" +description = "One bed in a box" +price = 10000 +capacity = 4 +[[[Suite]]] +name = "Suite" +description = "Baller over here" +price = 20000 +capacity = 8 + +[[mark_center]] +name = "Hilton Mark Center" +description = "The tall one" [[[Double Queen]]] name = "Double Queen" description = "Two beds in a box" diff --git a/uber/config.py b/uber/config.py index c5540fc7f..d3b8ab478 100644 --- a/uber/config.py +++ b/uber/config.py @@ -1555,6 +1555,10 @@ def _unrepr(d): c.SAME_NUMBER_REPEATED = r'^(\d)\1+$' c.HOTEL_LOTTERY = _config.get('hotel_lottery', {}) +c.HOTEL_LOTTERY_HOTEL_OPTS = [] +for name, item in c.HOTEL_LOTTERY.items(): + if isinstance(item, dict): + c.HOTEL_LOTTERY_HOTEL_OPTS.append((name, item)) # Allows 0-9, a-z, A-Z, and a handful of punctuation characters c.VALID_BADGE_PRINTED_CHARS = r'[a-zA-Z0-9!"#$%&\'()*+,\-\./:;<=>?@\[\\\]^_`\{|\}~ "]' diff --git a/uber/decorators.py b/uber/decorators.py index 9a14cb422..cc82af23e 100644 --- a/uber/decorators.py +++ b/uber/decorators.py @@ -255,6 +255,7 @@ def returns_json(*args, **kwargs): assert cherrypy.request.method == 'POST', 'POST required, got {}'.format(cherrypy.request.method) check_csrf(kwargs.pop('csrf_token', None)) except Exception: + traceback.print_exc() message = "There was an issue submitting the form. Please refresh and try again." return json.dumps({'success': False, 'message': message, 'error': message}, cls=serializer).encode('utf-8') return json.dumps(func(*args, **kwargs), cls=serializer).encode('utf-8') diff --git a/uber/forms/__init__.py b/uber/forms/__init__.py index d10fe8592..c3c96cf13 100644 --- a/uber/forms/__init__.py +++ b/uber/forms/__init__.py @@ -274,6 +274,8 @@ def get_field_type(self, field): return 'customselect' elif isinstance(widget, wtforms_widgets.HiddenInput): return 'hidden' + elif isinstance(widget, Ranking): + return 'ranking' else: return 'text' diff --git a/uber/forms/attendee.py b/uber/forms/attendee.py index a77a14acd..05d524623 100644 --- a/uber/forms/attendee.py +++ b/uber/forms/attendee.py @@ -9,7 +9,7 @@ from uber.config import c from uber.forms import (AddressForm, MultiCheckbox, MagForm, SelectAvailableField, SwitchInput, NumberInputGroup, - HiddenBoolField, HiddenIntField, CustomValidation) + HiddenBoolField, HiddenIntField, CustomValidation, Ranking) from uber.custom_tags import popup_link from uber.badge_funcs import get_real_badge_type from uber.models import Attendee, Session, PromoCodeGroup @@ -18,7 +18,8 @@ __all__ = ['AdminBadgeExtras', 'AdminBadgeFlags', 'AdminConsents', 'AdminStaffingInfo', 'BadgeExtras', - 'BadgeFlags', 'BadgeAdminNotes', 'PersonalInfo', 'PreregOtherInfo', 'OtherInfo', 'StaffingInfo', 'Consents'] + 'BadgeFlags', 'BadgeAdminNotes', 'PersonalInfo', 'PreregOtherInfo', 'OtherInfo', 'StaffingInfo', + 'LotteryApplication', 'Consents'] # TODO: turn this into a proper validation class @@ -28,6 +29,10 @@ def valid_cellphone(form, field): 'include a country code (e.g. +44) for international numbers.') +class LotteryApplication(MagForm): + hotel_preference = StringField('Hotel Preference', widget=Ranking(c.HOTEL_LOTTERY_HOTEL_OPTS, id="hotel_preference"), + validators=[validators.Regexp(r"^roof.*$", message="You didn't select the roof as your most preferred hotel. What, are you too good for the great outdoors?")]) + class PersonalInfo(AddressForm, MagForm): field_validation, new_or_changed_validation = CustomValidation(), CustomValidation() diff --git a/uber/forms/widgets.py b/uber/forms/widgets.py index fb1da036e..63104b128 100644 --- a/uber/forms/widgets.py +++ b/uber/forms/widgets.py @@ -104,20 +104,57 @@ def render_option(cls, value, label, selected, **kwargs): ) class Ranking(): - def __init__(self, choices=None, **kwargs): + def __init__(self, choices=None, id=None, **kwargs): self.choices = choices + self.id = id - def __call__(self, field, choices=None, **kwargs): - choices = choices or self.choices or [('', "ERROR: No choices provided")] - selected_choices = field.data - html = ['
'] - for choice in choices if not choice in selected_choices: - html.append(f'
{choice}
') - html.append('
') + def __call__(self, field, choices=None, id=None, **kwargs): + choices = choices or self.choices or [('', {"name": "Error", "description": "No choices are configured"})] + id = id or self.id or "ranking" + selected_choices = field.data.split(",") + + deselected_html = [] + selected_html = [] + choice_dict = {key: val for key, val in choices} + for choice_id in selected_choices: + try: + choice_item = choice_dict[choice_id] + el = f""" +
  • +
    + {choice_item["name"]} +
    +
    + {choice_item["description"]} +
    +
  • """ + selected_html.append(el) + except KeyError: + continue + for choice_id, choice_item in choices: + if not choice_id in selected_choices: + el = f""" +
  • +
    + {choice_item["name"]} +
    +
    + {choice_item["description"]} +
    +
  • """ + deselected_html.append(el) + + html = [ + '
    ', + '
    ', + 'Available', + f'
      ', + *deselected_html, + '
    ', + 'Selected', + f'
      ', + *selected_html, + f'
    ' + ] - html.append('
    ') - for choice in selected_choices: - html.append(f'
    {choice}
    ') - html.append('
    ') - return Markup(''.join(html)) \ No newline at end of file diff --git a/uber/models/__init__.py b/uber/models/__init__.py index b3b1482d6..554e13f9b 100644 --- a/uber/models/__init__.py +++ b/uber/models/__init__.py @@ -588,6 +588,7 @@ def minutestr(dt): from uber.models.email import Email # noqa: E402 from uber.models.group import Group # noqa: E402 from uber.models.guests import GuestGroup # noqa: E402 +from uber.models.hotel import LotteryApplication from uber.models.mits import MITSApplicant, MITSTeam # noqa: E402 from uber.models.mivs import IndieJudge, IndieGame, IndieStudio # noqa: E402 from uber.models.panels import PanelApplication, PanelApplicant # noqa: E402 diff --git a/uber/models/hotel.py b/uber/models/hotel.py index a96ea0ce2..a376db847 100644 --- a/uber/models/hotel.py +++ b/uber/models/hotel.py @@ -105,3 +105,7 @@ def check_out_date(self): class RoomAssignment(MagModel): room_id = Column(UUID, ForeignKey('room.id')) attendee_id = Column(UUID, ForeignKey('attendee.id')) + + +class LotteryApplication(MagModel): + hotel_preference = Column(MultiChoice(c.HOTEL_LOTTERY_HOTEL_OPTS)) \ No newline at end of file diff --git a/uber/site_sections/hotel_lottery.py b/uber/site_sections/hotel_lottery.py index 8d93c7682..ef288f037 100644 --- a/uber/site_sections/hotel_lottery.py +++ b/uber/site_sections/hotel_lottery.py @@ -3,17 +3,53 @@ from uber.config import c from uber.decorators import ajax, all_renderable from uber.errors import HTTPRedirect -from uber.models import Attendee, Room, RoomAssignment, Shift +from uber.models import Attendee, Room, RoomAssignment, Shift, LotteryApplication +from uber.forms import load_forms +from uber.utils import validate_model @all_renderable() class Root: - def index(self, session): - print(c.HOTEL_LOTTERY, flush=True) + def index(self, session, **params): + lottery_application = LotteryApplication() + params['id'] = 'None' + forms = load_forms(params, lottery_application, ['LotteryApplication']) return { "checkin_start": c.HOTEL_LOTTERY_CHECKIN_START, "checkin_end": c.HOTEL_LOTTERY_CHECKIN_END, "checkout_start": c.HOTEL_LOTTERY_CHECKOUT_START, "checkout_end": c.HOTEL_LOTTERY_CHECKOUT_END, - "hotels": c.HOTEL_LOTTERY + "hotels": c.HOTEL_LOTTERY, + "forms": forms } + + @ajax + def validate_hotel_lottery(self, session, form_list=[], **params): + if params.get('id') in [None, '', 'None']: + application = LotteryApplication() + else: + application = LotteryApplication.get(id=params.get('id')) + + if not form_list: + form_list = ["LotteryApplication"] + elif isinstance(form_list, str): + form_list = [form_list] + forms = load_forms(params, application, form_list, get_optional=False) + + all_errors = validate_model(forms, application, LotteryApplication(**application.to_dict())) + if all_errors: + return {"error": all_errors} + + return {"success": True} + + def form(self, session, message="", **params): + application = LotteryApplication() + forms_list = ["LotteryApplication"] + forms = load_forms(params, application, forms_list) + for form in forms.values(): + form.populate_obj(application) + return { + 'forms': forms, + 'message': message, + 'application': application + } \ No newline at end of file diff --git a/uber/templates/forms/macros.html b/uber/templates/forms/macros.html index 949204455..e5fd3d9b6 100644 --- a/uber/templates/forms/macros.html +++ b/uber/templates/forms/macros.html @@ -104,6 +104,12 @@ {{ form_label(field, label_text=label_text, required=label_required) }} {{ form_input_extras(field, help_text, admin_text, extra_field) }} +{% elif type == 'ranking' %} +
    + {{ form_label(field, label_text=label_text, required=label_required) }} + {{ field(**custom_kwargs) }} + {{ form_input_extras(field, help_text, admin_text, extra_field) }} +
    {% else %}
    {{ field(class="form-control", **custom_kwargs) }} diff --git a/uber/templates/hotel_lottery/form.html b/uber/templates/hotel_lottery/form.html new file mode 100644 index 000000000..f0856e774 --- /dev/null +++ b/uber/templates/hotel_lottery/form.html @@ -0,0 +1,71 @@ +{% extends "./preregistration/preregbase.html" %} +{% set title_text = "Hotel Lottery Application" %} +{% import 'macros.html' as macros with context %} +{% import 'forms/macros.html' as form_macros with context %} +{% set lottery_application = lottery_application or forms['lottery_application'] %} + +{% block content %} + + + + +

    Saved Hotel Preference!

    +
    +
    + {{ form_macros.form_validation('hotel-lottery-form', 'validate_hotel_lottery') }} +
    + {{ form_macros.form_input(lottery_application.hotel_preference) }} + {{ csrf_token() }} + +
    +
    +
    + + +{% endblock %} diff --git a/uber/templates/hotel_lottery/index.html b/uber/templates/hotel_lottery/index.html index e0d1d651f..ce2b1f8f2 100644 --- a/uber/templates/hotel_lottery/index.html +++ b/uber/templates/hotel_lottery/index.html @@ -1,47 +1,71 @@ -{% extends "base.html" %} -{% block backlink %} -{% if c.ATTENDEE_ACCOUNTS_ENABLED or c.HAS_REGISTRATION_ACCESS or c.HAS_GROUP_ADMIN_ACCESS %} -
    - {% if c.HAS_REGISTRATION_ACCESS and attendee and not attendee.is_new %} - Admin Form - {% elif c.HAS_GROUP_ADMIN_ACCESS and group and not group.is_new %} - Admin Form - {% elif c.HAS_ART_SHOW_ADMIN_ACCESS and app and not app.is_new %} - Admin Form - {% elif c.CURRENT_ADMIN %} - Admin Area - {% endif %} - {% if c.ATTENDEE_ACCOUNTS_ENABLED and logged_in_account and not account and c.PAGE_PATH != '/preregistration/homepage' %} - Homepage - {% endif %} - {% if c.ATTENDEE_ACCOUNTS_ENABLED and logged_in_account %} - Logout - {% endif %} +{% extends "./preregistration/preregbase.html" %} +{% set title_text = "Hotel Lottery Application" %} +{% import 'macros.html' as macros with context %} +{% import 'forms/macros.html' as form_macros with context %} +{% set lottery_application = lottery_application or forms['lottery_application'] %} + +{% block content %} + + + + + +
    +
    + {{ form_macros.form_validation('hotel-lottery-form', 'validate_hotel_lottery') }} +
    + {{ form_macros.form_input(lottery_application.hotel_preference) }} + {{ csrf_token() }} + +
    +
    + -{% endif %} -{% endblock %} + Sortable.create(deselected_hotel_preference, { + group: 'hotel_preference', + animation: 100 + }); -{% block masthead %} -
    {{ c.ORGANIZATION_NAME }}
    -{% if homepage_account %} -
    {% include "preregistration/update_account.html" %}
    -{% elif logged_in_account %} -
    You are currently logged in as {{ logged_in_account.email }}.
    -{% endif %} + Sortable.create(selected_hotel_preference, { + group: 'hotel_preference', + animation: 100, + onChange: function(evt) { + el = document.getElementById("selected_hotel_preference"); + let selected = []; + for (let i=0; i {% endblock %} - -{% block footer %} -
    -
    -
    -{% endblock %} \ No newline at end of file From 2321ba13db1529cf192f22e6aa175efa273e537a Mon Sep 17 00:00:00 2001 From: Mark Murnane Date: Sun, 11 Aug 2024 15:37:42 -0400 Subject: [PATCH 4/4] Adding lottery questions --- uber.ini | 104 +++++++++--------------- uber/config.py | 10 ++- uber/configspec.ini | 14 ++++ uber/forms/attendee.py | 20 ++++- uber/models/hotel.py | 5 +- uber/templates/hotel_lottery/index.html | 3 + 6 files changed, 85 insertions(+), 71 deletions(-) diff --git a/uber.ini b/uber.ini index 7c7f47e9a..817baee2b 100644 --- a/uber.ini +++ b/uber.ini @@ -1,76 +1,52 @@ [hotel_lottery] -[[gaylord]] +[[hotels]] +[[[gaylord]]] name = "Gaylord National Harbor" description = "The drive can't be that bad, can it?" -[[[Double Queen]]] -name = "Double Queen" -description = "Two beds in a box" -price = 10000 -capacity = 4 -[[[King]]] -name = "King" -description = "One bed in a box" -price = 10000 -capacity = 4 -[[[Suite]]] -name = "Suite" -description = "Baller over here" -price = 20000 -capacity = 8 -[[roof]] +[[[roof]]] name = "Rooftop Room" description = "Camping out on the roof of the Donald E. Stephens Convention Center" -[[[Double Queen]]] -name = "Double Queen" -description = "Two beds in a box" -price = 10000 -capacity = 4 -[[[King]]] -name = "King" -description = "One bed in a box" -price = 10000 -capacity = 4 -[[[Suite]]] -name = "Suite" -description = "Baller over here" -price = 20000 -capacity = 8 -[[cardboard]] +[[[cardboard]]] name = "Cardboard Box" description = "Literally a big box. Do you fits?" -[[[Double Queen]]] -name = "Double Queen" -description = "Two beds in a box" -price = 10000 -capacity = 4 -[[[King]]] -name = "King" -description = "One bed in a box" -price = 10000 -capacity = 4 -[[[Suite]]] -name = "Suite" -description = "Baller over here" -price = 20000 -capacity = 8 -[[mark_center]] +[[[mark_center]]] name = "Hilton Mark Center" description = "The tall one" -[[[Double Queen]]] -name = "Double Queen" -description = "Two beds in a box" -price = 10000 -capacity = 4 -[[[King]]] -name = "King" -description = "One bed in a box" -price = 10000 -capacity = 4 -[[[Suite]]] -name = "Suite" -description = "Baller over here" -price = 20000 -capacity = 8 \ No newline at end of file + +[[room_types]] +[[[king]]] +name = "King Room" +description = "One really big bed" + +[[[double]]] +name = "Double Room" +description = "Two beds" + +[[suite_room_types]] +[[[super]]] +name = "Super Suite" +description = "This is the one everyone wants" + +[[[meh]]] +name = "Meh Suite" +description = "I guess" + +[[[overpriced]]] +name = "Overpriced Suite" +description = "This one is just crazy expensive. Otherwise a normal room." + +[[hotel_priorities]] +[[[hotel]]] +name = "Hotel" +description = "Which hotel matters to me." + +[[[dates]]] +name = "Dates" +description = "Check-In and Check-Out dates matter to me." + +[[[room]]] +name = "Room Type" +description = "The type of room I get matters." \ No newline at end of file diff --git a/uber/config.py b/uber/config.py index d3b8ab478..efe1cf6fd 100644 --- a/uber/config.py +++ b/uber/config.py @@ -1555,10 +1555,12 @@ def _unrepr(d): c.SAME_NUMBER_REPEATED = r'^(\d)\1+$' c.HOTEL_LOTTERY = _config.get('hotel_lottery', {}) -c.HOTEL_LOTTERY_HOTEL_OPTS = [] -for name, item in c.HOTEL_LOTTERY.items(): - if isinstance(item, dict): - c.HOTEL_LOTTERY_HOTEL_OPTS.append((name, item)) +for key in ["hotels", "room_types", "suite_room_types", "hotel_priorities"]: + opts = [] + for name, item in c.HOTEL_LOTTERY.get(key, {}).items(): + if isinstance(item, dict): + opts.append((name, item)) + setattr(c, f"HOTEL_LOTTERY_{key.upper()}_OPTS", opts) # Allows 0-9, a-z, A-Z, and a handful of punctuation characters c.VALID_BADGE_PRINTED_CHARS = r'[a-zA-Z0-9!"#$%&\'()*+,\-\./:;<=>?@\[\\\]^_`\{|\}~ "]' diff --git a/uber/configspec.ini b/uber/configspec.ini index 1a30ba357..16aa3cd13 100644 --- a/uber/configspec.ini +++ b/uber/configspec.ini @@ -1481,6 +1481,20 @@ full = string(default="All Info") [[food_restriction]] vegan = string(default="Vegan") +[[hotel_room_type]] +room_double = string(default="Double Room") +room_king = string(default="King Room") + +[[suite_room_type]] +suite_big = string(default="Big Suite") +suite_medium = string(default="Medium Suite") +suite_tiny = string(default="Tiny Suite") + +[[hotel_priorities]] +hotel_room_type = string(default="Hotel Room Type") +hotel_selection = string(default="Hotel Selection") +hotel_dates = string(default="Check-In and Check-Out Dates") + [[sandwich]] [[dealer_status]] diff --git a/uber/forms/attendee.py b/uber/forms/attendee.py index 05d524623..72d6f6f2b 100644 --- a/uber/forms/attendee.py +++ b/uber/forms/attendee.py @@ -30,8 +30,24 @@ def valid_cellphone(form, field): class LotteryApplication(MagForm): - hotel_preference = StringField('Hotel Preference', widget=Ranking(c.HOTEL_LOTTERY_HOTEL_OPTS, id="hotel_preference"), - validators=[validators.Regexp(r"^roof.*$", message="You didn't select the roof as your most preferred hotel. What, are you too good for the great outdoors?")]) + wants_room = BooleanField('I would like to enter the hotel room lottery.', default=False) + earliest_room_checkin_date = DateField('Earliest acceptable Check-In Date', validators=[validators.DataRequired("Please enter your earliest check-in date.")]) + latest_room_checkin_date = DateField('Latest acceptable Check-In Date', validators=[validators.DataRequired("Please enter your latest check-in date.")]) + earliest_room_checkout_date = DateField('Earliest acceptable Check-Out Date', validators=[validators.DataRequired("Please enter your earliest check-out date.")]) + latest_room_checkout_date = DateField('Latest acceptable Check-Out Date', validators=[validators.DataRequired("Please enter your latest check-out date.")]) + hotel_preference = StringField('Hotel Preference', widget=Ranking(c.HOTEL_LOTTERY_HOTELS_OPTS, id="hotel_preference")) + room_type_preference = StringField('Room Type Preference', widget=Ranking(c.HOTEL_LOTTERY_ROOM_TYPES_OPTS, id="room_type_preference")) + selection_priorities = StringField('Room Priorities', widget=Ranking(c.HOTEL_LOTTERY_HOTEL_PRIORITIES_OPTS, id="selection_priorities")) + accessibility_contact = BooleanField('Please contact me in regards to an accessibility requirement.', default=False) + + wants_suite = BooleanField('I would like to enter the suite lottery.', default=False) + earliest_suite_checkin_date = DateField('Earliest acceptable Check-In Date', validators=[validators.DataRequired("Please enter your earliest check-in date.")]) + latest_suite_checkin_date = DateField('Latest acceptable Check-In Date', validators=[validators.DataRequired("Please enter your latest check-in date.")]) + earliest_suite_checkout_date = DateField('Earliest acceptable Check-Out Date', validators=[validators.DataRequired("Please enter your earliest check-out date.")]) + latest_suite_checkout_date = DateField('Latest acceptable Check-Out Date', validators=[validators.DataRequired("Please enter your latest check-out date.")]) + suite_type_preference = StringField('Hotel Preference', widget=Ranking(c.HOTEL_LOTTERY_SUITE_ROOM_TYPES_OPTS, id="suite_type_preference")) + + terms_accepted = BooleanField('I accept the terms of service of the hotel lottery.', default=False) class PersonalInfo(AddressForm, MagForm): field_validation, new_or_changed_validation = CustomValidation(), CustomValidation() diff --git a/uber/models/hotel.py b/uber/models/hotel.py index a376db847..85a11615c 100644 --- a/uber/models/hotel.py +++ b/uber/models/hotel.py @@ -108,4 +108,7 @@ class RoomAssignment(MagModel): class LotteryApplication(MagModel): - hotel_preference = Column(MultiChoice(c.HOTEL_LOTTERY_HOTEL_OPTS)) \ No newline at end of file + hotel_preference = Column(MultiChoice(c.HOTEL_LOTTERY_HOTELS_OPTS)) + room_type_preference = Column(MultiChoice(c.HOTEL_LOTTERY_ROOM_TYPES_OPTS)) + selection_priorities = Column(MultiChoice(c.HOTEL_LOTTERY_HOTEL_PRIORITIES_OPTS)) + suite_type_preference = Column(MultiChoice(c.HOTEL_LOTTERY_SUITE_ROOM_TYPES_OPTS)) \ No newline at end of file diff --git a/uber/templates/hotel_lottery/index.html b/uber/templates/hotel_lottery/index.html index ce2b1f8f2..efa6e4269 100644 --- a/uber/templates/hotel_lottery/index.html +++ b/uber/templates/hotel_lottery/index.html @@ -43,6 +43,9 @@ {{ form_macros.form_validation('hotel-lottery-form', 'validate_hotel_lottery') }}
    {{ form_macros.form_input(lottery_application.hotel_preference) }} + {{ form_macros.form_input(lottery_application.room_type_preference) }} + {{ form_macros.form_input(lottery_application.selection_priorities) }} + {{ form_macros.form_input(lottery_application.suite_type_preference) }} {{ csrf_token() }}