539fa2dfdd
Add shirt types and sizes. Improve messaging about discounts. Restyle ticket wizard and product category screens. Enable page titles and messages. Update dashboard to hide raffle. Enable inventory population for dev container.
559 lines
17 KiB
Python
559 lines
17 KiB
Python
from decimal import Decimal
|
|
import os
|
|
import sys
|
|
|
|
import django
|
|
import dj_database_url
|
|
import saml2
|
|
import saml2.saml
|
|
|
|
from datetime import date, datetime, timedelta
|
|
|
|
from dataclasses import dataclass
|
|
|
|
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
DJANGO_ROOT = os.path.abspath(os.path.dirname(django.__file__))
|
|
BASE_DIR = PACKAGE_ROOT
|
|
sys.path.append(os.path.join(PROJECT_ROOT, 'vendor'))
|
|
|
|
|
|
### USER SETTINGS
|
|
DEV_MODE = os.environ.get("SYMPOSION_DEV_MODE", None)
|
|
DEBUG = os.environ.get('SYMPOSION_APP_DEBUG', '0')
|
|
if isinstance(DEBUG, str):
|
|
try:
|
|
i = int(DEBUG)
|
|
if not i in [0, 1]:
|
|
raise ValueError("not 0 or 1")
|
|
DEBUG = bool(i)
|
|
except ValueError:
|
|
sys.exit('DEBUG env var must be set to string value of a 0 or 1')
|
|
else:
|
|
sys.exit('DEBUG env var is in unexpected format. Should be a string'
|
|
'containing either a 0 or a 1 - Got type %s' % type(DEBUG))
|
|
|
|
DATABASES = {}
|
|
DATABASES['default'] = dj_database_url.config(conn_max_age=600)
|
|
if DATABASES['default']['ENGINE'] == 'django.db.backends.mysql':
|
|
DATABASES['default']['OPTIONS'] = {'charset': 'utf8mb4'}
|
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = os.environ.get('EMAIL_HOST', None)
|
|
EMAIL_PORT = os.environ.get('EMAIL_PORT', 25)
|
|
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', None)
|
|
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', None)
|
|
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
|
|
EMAIL_USE_SSL = False
|
|
EMAIL_USE_TLS = False
|
|
_EMAIL_SSL_FLAVOR=os.environ.get('EMAIL_SSL_FLAVOR', None)
|
|
if _EMAIL_SSL_FLAVOR == "TLS":
|
|
EMAIL_USE_TLS = True
|
|
elif _EMAIL_SSL_FLAVOR == "SSL":
|
|
EMAIL_USE_SSL = True
|
|
|
|
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', None)
|
|
|
|
PINAX_STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY', None)
|
|
PINAX_STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY', None)
|
|
PINAX_STRIPE_SEND_EMAIL_RECEIPTS = False
|
|
|
|
ANALYTICS_KEY = os.environ.get('ANALYTICS_KEY', None)
|
|
|
|
saml2_entityid = os.environ.get('SAML2_ENTITYID', None)
|
|
saml2_sp_name = os.environ.get('SAML2_SP_NAME', None)
|
|
saml2_sp_assertion_service = os.environ.get('SAML2_SP_ASSERTION_SERVICE', None)
|
|
saml2_sp_slo_rdir = os.environ.get('SAML2_SP_SLO_RDIR', None)
|
|
saml2_sp_slo_post = os.environ.get('SAML2_SP_SLO_POST', None)
|
|
|
|
saml2_idp_metadata = {
|
|
'local': [os.environ.get('SAML2_IDP_METADATA_FILE', None)],
|
|
}
|
|
saml2_signing_key = os.environ.get('SAML2_SIGNING_KEY', None)
|
|
saml2_signing_crt = os.environ.get('SAML2_SIGNING_CRT', None)
|
|
saml2_encr_key = os.environ.get('SAML2_ENCRYPTION_KEY', None)
|
|
saml2_encr_crt = os.environ.get('SAML2_ENCRYPTION_CRT', None)
|
|
saml2_contact = {
|
|
'given_name': os.environ.get("META_GIVEN_NAME", 'Bastard'),
|
|
'sur_name': os.environ.get('META_FAM_NAME', 'Operator'),
|
|
'company': os.environ.get('META_COMPANY', 'Corp1'),
|
|
'email_address': os.environ.get('META_EMAIL', 'op@example.com'),
|
|
'contact_type': 'technical'},
|
|
|
|
fail = False
|
|
|
|
BADGER_DEFAULT_SVG = 'registrasion/badge.svg'
|
|
BADGER_DEFAULT_FORM = "registrasion/badge_form.html"
|
|
|
|
if SECRET_KEY is None:
|
|
print("FAILURE: You need to supply a DJANGO_SECRET_KEY "
|
|
"environment variable")
|
|
fail = True
|
|
|
|
if PINAX_STRIPE_PUBLIC_KEY is None:
|
|
print("FAILURE: You need to supply a STRIPE_PUBLIC_KEY "
|
|
"environment variable")
|
|
fail = True
|
|
|
|
if PINAX_STRIPE_SECRET_KEY is None:
|
|
print("FAILURE: You need to supply a STRIPE_SECRET_KEY "
|
|
"environment variable")
|
|
fail = True
|
|
|
|
if fail:
|
|
sys.exit('FAILURE: Missing environment variables.')
|
|
|
|
### Standard settings
|
|
|
|
ADMIN_USERNAMES = []
|
|
|
|
CACHES = {
|
|
'default': {
|
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
|
'LOCATION': 'unique-snowflake',
|
|
}
|
|
}
|
|
|
|
|
|
ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '*']
|
|
|
|
TIME_ZONE = "Pacific/Auckland"
|
|
DATE_FORMAT = "j F Y"
|
|
LANGUAGE_CODE = "en-au"
|
|
|
|
SITE_ID = int(os.environ.get("SITE_ID", 1))
|
|
USE_I18N = True
|
|
USE_L10N = True
|
|
USE_TZ = True
|
|
|
|
MEDIA_ROOT = os.path.join(PACKAGE_ROOT, "site_media", "media")
|
|
MEDIA_URL = "/site_media/media/"
|
|
|
|
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static/build')
|
|
STATIC_URL = '/static/build/'
|
|
|
|
STATICFILES_DIRS = [
|
|
os.path.join(PROJECT_ROOT, 'static/src'),
|
|
]
|
|
|
|
STATICFILES_FINDERS = [
|
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
|
"sass_processor.finders.CssFinder",
|
|
]
|
|
|
|
TEMPLATES = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [
|
|
os.path.join(PACKAGE_ROOT, "templates"),
|
|
os.path.join(DJANGO_ROOT, 'forms/templates')
|
|
],
|
|
"APP_DIRS": True,
|
|
"OPTIONS": {
|
|
"debug": DEBUG,
|
|
"context_processors": [
|
|
"django.contrib.auth.context_processors.auth",
|
|
"django.template.context_processors.debug",
|
|
"django.template.context_processors.i18n",
|
|
"django.template.context_processors.media",
|
|
"django.template.context_processors.static",
|
|
"django.template.context_processors.tz",
|
|
"django.template.context_processors.request",
|
|
"django.contrib.messages.context_processors.messages",
|
|
"pinax_theme_bootstrap.context_processors.theme",
|
|
"symposion.reviews.context_processors.reviews",
|
|
"django_settings_export.settings_export",
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
MIDDLEWARE_CLASSES = [
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"django.middleware.common.CommonMiddleware",
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.auth.middleware.SessionAuthenticationMiddleware",
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
|
"reversion.middleware.RevisionMiddleware",
|
|
"waffle.middleware.WaffleMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
|
'pinaxcon.monkey_patch.MonkeyPatchMiddleware',
|
|
]
|
|
|
|
if DEV_MODE and DEV_MODE == "LAPTOP":
|
|
ROOT_URLCONF = "pinaxcon.devmode_urls"
|
|
else:
|
|
ROOT_URLCONF = "pinaxcon.urls"
|
|
|
|
# Python dotted path to the WSGI application used by Django's runserver.
|
|
WSGI_APPLICATION = "pinaxcon.wsgi.application"
|
|
|
|
INSTALLED_APPS = [
|
|
"django.contrib.admin",
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.flatpages",
|
|
"django.contrib.messages",
|
|
"django.contrib.sessions",
|
|
"django.contrib.sites",
|
|
"django.contrib.staticfiles",
|
|
"django.contrib.humanize",
|
|
"debug_toolbar",
|
|
|
|
'djangosaml2',
|
|
|
|
# theme
|
|
"bootstrapform",
|
|
"pinax_theme_bootstrap",
|
|
"sass_processor",
|
|
|
|
# external
|
|
"easy_thumbnails",
|
|
"taggit",
|
|
"reversion",
|
|
"sitetree",
|
|
"pinax.eventlog",
|
|
|
|
# symposion
|
|
"symposion",
|
|
"symposion.conference",
|
|
"symposion.proposals",
|
|
"symposion.reviews",
|
|
"symposion.schedule",
|
|
"symposion.speakers",
|
|
"symposion.teams",
|
|
|
|
# Registrasion
|
|
"registrasion",
|
|
|
|
# Registrasion-stripe
|
|
"pinax.stripe",
|
|
"django_countries",
|
|
"registripe",
|
|
|
|
#registrasion-desk
|
|
"regidesk",
|
|
|
|
# admin - required by registrasion ??
|
|
"nested_admin",
|
|
|
|
# project
|
|
"pinaxcon",
|
|
"pinaxcon.proposals",
|
|
"pinaxcon.registrasion",
|
|
"pinaxcon.raffle",
|
|
"jquery",
|
|
"djangoformsetjs",
|
|
|
|
# testing and rollout
|
|
"django_nose",
|
|
"waffle",
|
|
|
|
"crispy_forms",
|
|
]
|
|
|
|
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
|
|
|
DEBUG_TOOLBAR_PANELS = [
|
|
'debug_toolbar.panels.versions.VersionsPanel',
|
|
'debug_toolbar.panels.timer.TimerPanel',
|
|
'debug_toolbar.panels.settings.SettingsPanel',
|
|
'debug_toolbar.panels.headers.HeadersPanel',
|
|
'debug_toolbar.panels.request.RequestPanel',
|
|
'debug_toolbar.panels.sql.SQLPanel',
|
|
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
|
|
'debug_toolbar.panels.cache.CachePanel',
|
|
'debug_toolbar.panels.signals.SignalsPanel',
|
|
'debug_toolbar.panels.logging.LoggingPanel',
|
|
'debug_toolbar.panels.templates.TemplatesPanel',
|
|
'debug_toolbar.panels.redirects.RedirectsPanel',
|
|
]
|
|
|
|
DEBUG_TOOLBAR_CONFIG = {
|
|
'INTERCEPT_REDIRECTS': False,
|
|
'SHOW_TOOLBAR_CALLBACK': lambda x: DEBUG,
|
|
}
|
|
|
|
LOGGING = {
|
|
'version': 1,
|
|
'disable_existing_loggers': False,
|
|
'formatters': {
|
|
'verbose': {
|
|
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
|
|
},
|
|
'simple': {
|
|
'format': '%(asctime)s %(levelname)s $(module)s %(message)s'
|
|
},
|
|
},
|
|
'filters': {
|
|
'require_debug_false': {
|
|
'()': 'django.utils.log.RequireDebugFalse'
|
|
}
|
|
},
|
|
'handlers': {
|
|
'console': {
|
|
'level': 'DEBUG',
|
|
'class': 'logging.StreamHandler',
|
|
'formatter': 'simple'
|
|
},
|
|
'mail_admins': {
|
|
'level': 'ERROR',
|
|
'filters': ['require_debug_false'],
|
|
'class': 'django.utils.log.AdminEmailHandler',
|
|
'include_html': True,
|
|
}
|
|
},
|
|
'loggers': {
|
|
'django.request': {
|
|
'handlers': ['mail_admins'],
|
|
'level': 'DEBUG',
|
|
'propagate': True,
|
|
},
|
|
'symposion.request': {
|
|
'handlers': ['mail_admins'],
|
|
'level': 'DEBUG',
|
|
'propagate': True,
|
|
},
|
|
},
|
|
'root': {
|
|
'handlers': ['console'],
|
|
'level': 'DEBUG'
|
|
},
|
|
}
|
|
FIXTURE_DIRS = [
|
|
os.path.join(PROJECT_ROOT, "fixtures"),
|
|
]
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
'symposion.teams.backends.TeamPermissionsBackend',
|
|
'django.contrib.auth.backends.ModelBackend',
|
|
'djangosaml2.backends.Saml2Backend',
|
|
]
|
|
|
|
LOGIN_URL = '/saml2/login/'
|
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
|
|
|
CONFERENCE_ID = 1
|
|
PROPOSAL_FORMS = {
|
|
"talk": "pinaxcon.proposals.forms.TalkProposalForm",
|
|
"tutorial": "pinaxcon.proposals.forms.TutorialProposalForm",
|
|
"miniconf": "pinaxcon.proposals.forms.MiniconfProposalForm",
|
|
### LCA2020 Miniconfs
|
|
"containers-miniconf": "pinaxcon.proposals.forms.ContainersProposalForm",
|
|
"creative-arts-miniconf": "pinaxcon.proposals.forms.CreativeArtsProposalForm",
|
|
"docs-miniconf": "pinaxcon.proposals.forms.DocsProposalForm",
|
|
"freebsd-miniconf": "pinaxcon.proposals.forms.FreeBsdProposalForm",
|
|
"games-miniconf": "pinaxcon.proposals.forms.GamesProposalForm",
|
|
"glam-miniconf": "pinaxcon.proposals.forms.GlamProposalForm",
|
|
"kernel-miniconf": "pinaxcon.proposals.forms.KernelProposalForm",
|
|
"open-education-miniconf": "pinaxcon.proposals.forms.OpenEducationProposalForm",
|
|
"open-hardware-miniconf": "pinaxcon.proposals.forms.OpenHardwareProposalForm",
|
|
"open-isa-miniconf": "pinaxcon.proposals.forms.OpenIsaProposalForm",
|
|
"security-miniconf": "pinaxcon.proposals.forms.SecurityProposalForm",
|
|
"sysadmin-miniconf": "pinaxcon.proposals.forms.SysAdminProposalForm",
|
|
}
|
|
|
|
# Registrasion bits:
|
|
ATTENDEE_PROFILE_MODEL = "pinaxcon.registrasion.models.AttendeeProfile"
|
|
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
|
|
INVOICE_CURRENCY = "AUD"
|
|
TICKET_PRODUCT_CATEGORY = 1
|
|
TERMS_PRODUCT_CATEGORY = 2
|
|
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
|
|
|
|
#REGIDESK
|
|
REGIDESK_BOARDING_GROUP = "Ready For Boarding"
|
|
|
|
# CSRF custom error screen
|
|
CSRF_FAILURE_VIEW = "pinaxcon.csrf_view.csrf_failure"
|
|
|
|
# Use nose to run all tests
|
|
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
|
|
|
# Tell nose to measure coverage on the 'foo' and 'bar' apps
|
|
NOSE_ARGS = [
|
|
'--with-coverage',
|
|
'--cover-package=registrasion.controllers,registrasion.models',
|
|
]
|
|
|
|
SASS_PROCESSOR_INCLUDE_DIRS = [
|
|
os.path.join(PROJECT_ROOT, 'static/src/bootstrap/scss'),
|
|
os.path.join(PROJECT_ROOT, 'static/src/scss'),
|
|
]
|
|
|
|
xmlsec_binary = '/usr/bin/xmlsec1'
|
|
if not os.path.isfile(xmlsec_binary):
|
|
sys.exit('ERROR: xmlsec1 binary missing, EXITING')
|
|
|
|
SAML_ATTRIBUTE_MAPPING = {
|
|
'uid': ('username', ),
|
|
'mail': ('email', ),
|
|
'givenName': ('first_name', ),
|
|
'sn': ('last_name', ),
|
|
}
|
|
SAML_CONFIG = {
|
|
'xmlsec_binary': xmlsec_binary,
|
|
'entityid': saml2_entityid,
|
|
'attribute_map_dir': os.path.join(PACKAGE_ROOT, 'saml2/attribute-maps'),
|
|
'service': {
|
|
'sp': {
|
|
'name': saml2_sp_name,
|
|
'endpoints': {
|
|
'assertion_consumer_service': [
|
|
saml2_sp_assertion_service,
|
|
],
|
|
'single_logout_service': [
|
|
(saml2_sp_slo_rdir, saml2.BINDING_HTTP_REDIRECT),
|
|
(saml2_sp_slo_post, saml2.BINDING_HTTP_POST),
|
|
],
|
|
},
|
|
'logout_requests_signed': True,
|
|
'required_attributes': ['uid', 'mail', 'givenName', 'sn'],
|
|
},
|
|
},
|
|
'metadata': saml2_idp_metadata,
|
|
'debug': 0,
|
|
'key_file': saml2_signing_key,
|
|
'cert_file': saml2_signing_crt,
|
|
'encryption_keypairs': [{
|
|
'key_file': saml2_encr_key,
|
|
'cert_file': saml2_encr_crt,
|
|
}],
|
|
'contact_person': saml2_contact,
|
|
'valid_for': 10,
|
|
}
|
|
|
|
if 'SAML_CONFIG_LOADER' in os.environ:
|
|
SAML_CONFIG_LOADER = os.environ.get('SAML_CONFIG_LOADER')
|
|
|
|
DEFAULT_FILE_STORAGE = 'gapc_storage.storage.GoogleCloudStorage'
|
|
GAPC_STORAGE = {
|
|
'num_retries': 2,
|
|
}
|
|
|
|
SETTINGS_EXPORT = [
|
|
'DEBUG',
|
|
'ANALYTICS_KEY',
|
|
]
|
|
|
|
if DEV_MODE and DEV_MODE == "LAPTOP":
|
|
print("ENABLING LAPTOP MODE")
|
|
from .devmode_settings import *
|
|
|
|
|
|
class Category(object):
|
|
tickets = []
|
|
|
|
@classmethod
|
|
def order(cls, ticket) -> int:
|
|
return (cls.tickets.index(ticket) + 1) * 10
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Ticket:
|
|
name: str
|
|
regular_price: Decimal
|
|
earlybird_price: Decimal
|
|
|
|
def earlybird_discount(self):
|
|
return self.regular_price - self.earlybird_price
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DinnerTicket:
|
|
name: str
|
|
price: Decimal
|
|
description: str
|
|
reservation: timedelta
|
|
cat: Category
|
|
|
|
def order(self):
|
|
return self.cat.order(self)
|
|
|
|
|
|
class PenguinDinnerTicket(DinnerTicket):
|
|
pass
|
|
|
|
|
|
class SpeakersDinnerTicket(DinnerTicket):
|
|
pass
|
|
|
|
|
|
class SpeakersDinnerCat(Category):
|
|
@classmethod
|
|
def create(cls, name: str, price: Decimal, description: str, reservation: timedelta) -> SpeakersDinnerTicket:
|
|
t = SpeakersDinnerTicket(name, price, description, reservation, cls)
|
|
cls.tickets.append(t)
|
|
return t
|
|
|
|
|
|
class PenguinDinnerCat(Category):
|
|
@classmethod
|
|
def create(cls, name: str, price: Decimal, description: str, reservation: timedelta) -> PenguinDinnerTicket:
|
|
t = PenguinDinnerTicket(name, price, description, reservation, cls)
|
|
cls.tickets.append(t)
|
|
return t
|
|
|
|
|
|
LCA_START = datetime(2020, 1, 13)
|
|
LCA_END = datetime(2020, 1, 17)
|
|
EARLY_BIRD_DEADLINE = datetime(2019, 11, 1)
|
|
PENGUIN_DINNER_TICKET_DATE = date(2020, 1, 15)
|
|
SPEAKER_DINNER_TICKET_DATE = date(2020, 1, 14)
|
|
PDNS_TICKET_DATE = date(2020, 1, 16)
|
|
|
|
TSHIRT_PRICE = Decimal("25.00")
|
|
|
|
CONTRIBUTOR = Ticket("Contributor", Decimal("1999.00"), Decimal("1849.00"))
|
|
PROFESSIONAL = Ticket("Professional", Decimal("1099.00"), Decimal("949.00"))
|
|
HOBBYIST = Ticket("Hobbyist", Decimal("549.00"), Decimal("399.00"))
|
|
STUDENT = Ticket("Student", Decimal("199.00"), None)
|
|
|
|
MINICONF_MT = Ticket("Monday and Tuesday Only", Decimal("198.00"), None)
|
|
MINICONF_M = Ticket("Monday Only", Decimal("99.00"), None)
|
|
MINICONF_T = Ticket("Tuesday Only", Decimal("99.00"), None)
|
|
|
|
MEDIA = Ticket("Media", Decimal("0.0"), None)
|
|
SPEAKER = Ticket("Speaker", Decimal("0.0"), None)
|
|
SPONSOR = Ticket("Sponsor", Decimal("0.0"), None)
|
|
|
|
CONFERENCE_ORG = Ticket("Conference Organiser", Decimal("0.0"), None)
|
|
CONFERENCE_VOL = Ticket("Conference Volunteer", Decimal("0.0"), None)
|
|
|
|
PENGUIN_DINNER = PenguinDinnerCat
|
|
PENGUIN_DINNER_ADULT = PenguinDinnerCat.create(
|
|
"Adult", Decimal("95.00"),
|
|
"Includes an adult's meal and full beverage service.",
|
|
timedelta(hours=1))
|
|
PENGUIN_DINNER_CHILD = PenguinDinnerCat.create(
|
|
"Child", Decimal("50.00"),
|
|
"Children 14 and under. "
|
|
"Includes a child's meal and soft drink service.",
|
|
timedelta(hours=1))
|
|
PENGUIN_DINNER_INFANT = PenguinDinnerCat.create(
|
|
"Infant", Decimal("0.0"),
|
|
"Includes no food or beverage service.",
|
|
timedelta(hours=1))
|
|
|
|
SPEAKERS_DINNER = SpeakersDinnerCat
|
|
|
|
SPEAKERS_DINNER_ADULT = SpeakersDinnerCat.create(
|
|
"Adult", Decimal("100.00"),
|
|
"Includes an adult's meal and full beverage service.",
|
|
timedelta(hours=1))
|
|
|
|
# SPEAKERS_DINNER_CHILD = SpeakersDinnerCat.create(
|
|
# "Child", Decimal("60.00"),
|
|
# "Children 14 and under. "
|
|
# "Includes a child's meal and soft drink service.",
|
|
# timedelta(hours=1))
|
|
|
|
# SPEAKERS_DINNER_INFANT = SpeakersDinnerCat.create(
|
|
# "Infant", Decimal("00.00"),
|
|
# "Infant must be seated in an adult's lap. "
|
|
# "No food or beverage service.",
|
|
# timedelta(hours=1))
|