Changeset - 252697b842c0
[Not reviewed]
! ! !
Joel Addison - 4 years ago 2020-11-22 13:58:14
joel@addison.net.au
Update to Django 2.2

Upgrade site and modules to Django 2.2. Remove and replace obsolete
functionality with current equivalents. Update requirements to latest
versions where possible. Remove unused dependencies.
47 files changed:
Changeset was too big and was cut off... Show full diff anyway
0 comments (0 inline, 0 general)
constraints.txt
Show inline comments
 
django<1.12,>=1.11
 
pysaml2==4.8.0
 
django<3.0,>=2.2
 
pysaml2>=5.3.0
docker/Dockerfile
Show inline comments
 
FROM python:3.6-stretch as symposion_base
 
FROM python:3.8-buster as symposion_base
 

	
 
RUN set -ex \
 
    && apt-get update
 
RUN set -ex \
 
    && buildDeps=' \
 
        libffi-dev \
 
        libfreetype6-dev \
 
        libjpeg-dev \
 
        libwebp-dev \
 
        libpng-dev \
 
        liblcms2-dev \
 
        zlib1g-dev \
 
        libmemcached-dev \
 
        libsasl2-dev \
 
        inkscape \
 
    ' \
 
    && apt-get install -y git xmlsec1 \
 
    && apt-get install -y $buildDeps --no-install-recommends \
 
    && rm -rf /var/lib/apt/lists/*
 

	
 
RUN set -ex \
 
    && pip install uwsgi
 

	
 
COPY constraints.txt requirements.txt /reqs/
 

	
 
RUN set -ex \
 
    && pip install -U pip \
 
    && pip install --no-cache-dir -r /reqs/requirements.txt -c /reqs/constraints.txt \
 
    && apt-get purge -y --auto-remove $buildDeps \
 
    && rm -rf /usr/src/python ~/.cache
 

	
 
COPY . /app/symposion_app
 

	
 
WORKDIR /app/symposion_app
 
RUN set -x \
 
    && pip install -r vendored_requirements.txt -c /reqs/constraints.txt
 
RUN set -x \
 
    && DJANGO_SECRET_KEY=1234 STRIPE_PUBLIC_KEY=1234 STRIPE_SECRET_KEY=1234 \
 
       DATABASE_URL="sqlite:////dev/null" python manage.py compilescss
 
RUN set -x \
 
    && DJANGO_SECRET_KEY=1234 STRIPE_PUBLIC_KEY=1234 STRIPE_SECRET_KEY=1234 \
 
       DATABASE_URL="sqlite:////dev/null" \
 
       python manage.py collectstatic --noinput -l -v 0
 
RUN set -ex \
 
    && cp static/build/fonts/*.ttf /usr/local/share/fonts/ \
 
    && fc-cache \
 
    && fc-list
 

	
 
FROM symposion_base as symposion_dev
 
VOLUME /app/symposion_app
 
CMD ["./manage.py", "runserver", "-v3", "0.0.0.0:8000"]
 

	
 
FROM symposion_base as symposion_prod
 
CMD ["/usr/local/bin/uwsgi", "--http-socket", "0.0.0.0:8000", "-b", "8192", "--wsgi-file", "pinaxcon/wsgi.py"]
docker/laptop-mode-env
Show inline comments
 
DJANGO_SECRET_KEY=5CEA51A5-A613-4AEF-A9FB-D0A57D77C13B
 
STRIPE_PUBLIC_KEY=5CEA51A5-A613-4AEF-A9FB-D0A57D77C13B
 
STRIPE_SECRET_KEY=5CEA51A5-A613-4AEF-A9FB-D0A57D77C13B
 
GCS_BUCKET=5CEA51A5-A613-4AEF-A9FB-D0A57D77C13B
 
GOOGLE_APPLICATION_CREDENTIALS=/dev/null
 
DATABASE_URL=sqlite:////tmp/symposion.sqlite
 
SYMPOSION_DEV_MODE=LAPTOP
 
SYMPOSION_APP_DEBUG=1
...
 
\ No newline at end of file
 
SYMPOSION_APP_DEBUG=1
pinaxcon/devmode_settings.py
Show inline comments
 
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 
AUTHENTICATION_BACKENDS = [
 
    'symposion.teams.backends.TeamPermissionsBackend',
 
    'django.contrib.auth.backends.ModelBackend',
 
]
 
LOGIN_URL='/accounts/login'
 

	
 
ROOT_URLCONF = "pinaxcon.devmode_urls"
 

	
 
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
 

	
 
INTERNAL_IPS = ['*']
pinaxcon/devmode_urls.py
Show inline comments
 
from django.conf.urls import include, url
 
from django.contrib.auth.views import login, logout
 
    
 
from django.contrib.auth.views import LoginView, LogoutView
 
from django.urls import include, path
 

	
 
from pinaxcon import urls
 

	
 
urlpatterns = [
 
    url(r'^accounts/logout', logout, {'template_name': 'admin/logout.html'}),
 
    url(r'^accounts/login', login, {'template_name': 'admin/login.html'}),
 
    path('accounts/logout', LogoutView.as_view(template_name='admin/logout.html')),
 
    path('accounts/login', LoginView.as_view(template_name='admin/login.html')),
 
]
 

	
 
urlpatterns += urls.urlpatterns
pinaxcon/monkey_patch.py
Show inline comments
 
from functools import wraps
 

	
 

	
 
class MonkeyPatchMiddleware(object):
 
    ''' Ensures that our monkey patching only gets called after it is safe to do so.'''
 

	
 
    def process_request(self, request):
 
    def __init__(self, get_response):
 
        self.get_response = get_response
 

	
 
    def __call__(self, request):
 
        do_monkey_patch()
 
        response = self.get_response(request)
 
        return response
 

	
 

	
 
def do_monkey_patch():
 
    patch_stripe_payment_form()
 

	
 
    # Remove this function from existence
 
    global do_monkey_patch
 
    do_monkey_patch = lambda: None  # noqa: E731
 

	
 

	
 
def patch_stripe_payment_form():  # noqa: C901
 

	
 
    import inspect  # Oh no.
 
    from django.http.request import HttpRequest
 
    from registripe.forms import CreditCardForm
 
    from pinaxcon.registrasion import models
 

	
 
    old_init = CreditCardForm.__init__
 

	
 
    @wraps(old_init)
 
    def new_init(self, *a, **k):
 

	
 
        # Map the names from our attendee profile model
 
        # To the values expected in the Stripe card model
 
        mappings = (
 
            ("address_line_1", "address_line1"),
 
            ("address_line_2", "address_line2"),
 
            ("address_suburb", "address_city"),
 
            ("address_postcode", "address_zip"),
 
            ("state", "address_state"),
 
            ("country", "address_country"),
 
        )
 

	
 
        initial = "initial"
 
        if initial not in k:
 
            k[initial] = {}
 
        initial = k[initial]
 

	
 
        # Find request context maybe?
 
        frame = inspect.currentframe()
 
        attendee_profile = None
 
        if frame:
 
            context = frame.f_back.f_locals
 
            for name, value in (context.items() or {}):
 
                if not isinstance(value, HttpRequest):
 
                    continue
 
                user = value.user
 
                if not user.is_authenticated():
 
                if not user.is_authenticated:
 
                    break
 
                try:
 
                    attendee_profile = models.AttendeeProfile.objects.get(
 
                        attendee__user=user
 
                    )
 
                except models.AttendeeProfile.DoesNotExist:
 
                    # Profile is still none.
 
                    pass
 
                break
 

	
 
        if attendee_profile:
 
            for us, stripe in mappings:
 
                i = getattr(attendee_profile, us, None)
 
                if i:
 
                    initial[stripe] = i
 

	
 
        old_init(self, *a, **k)
 

	
 
    CreditCardForm.__init__ = new_init
pinaxcon/raffle/models.py
Show inline comments
 
from django.conf import settings
 
from django.db import models
 

	
 
from pinaxcon.raffle.mixins import PrizeMixin, RaffleMixin
 

	
 

	
 
class Raffle(RaffleMixin, models.Model):
 
    """
 
    Stores a single Raffle object, related to one or many
 
    :model:`pinaxcon_registrasion.Product`, which  is usually a raffle ticket,
 
    but can be set to tickets or other products for door prizes.
 
    """
 
    description = models.CharField(max_length=255)
 
    products = models.ManyToManyField('registrasion.Product')
 
    hidden = models.BooleanField(default=True)
 

	
 
    def __str__(self):
 
        return self.description
 

	
 

	
 
class Prize(PrizeMixin, models.Model):
 
    """
 
    Stores a Prize for a given :model:`pinaxcon_raffle.Raffle`.
 

	
 
    Once `winning_ticket` has been set to a :model:`pinaxcon_raffle.DrawnTicket`
 
    object, no further changes are permitted unless the object is explicitely
 
    unlocked.
 
    """
 
    description = models.CharField(max_length=255)
 
    raffle = models.ForeignKey('pinaxcon_raffle.Raffle', related_name='prizes')
 
    raffle = models.ForeignKey(
 
        'pinaxcon_raffle.Raffle',
 
        related_name='prizes',
 
        on_delete=models.CASCADE,
 
    )
 
    order = models.PositiveIntegerField()
 
    winning_ticket = models.OneToOneField(
 
        'pinaxcon_raffle.DrawnTicket', null=True,
 
        blank=True, related_name='+', on_delete=models.PROTECT
 
        'pinaxcon_raffle.DrawnTicket',
 
        null=True,
 
        blank=True,
 
        related_name='+',
 
        on_delete=models.PROTECT,
 
    )
 

	
 
    class Meta:
 
        unique_together = ('raffle', 'order')
 

	
 
    def __str__(self):
 
        return f"{self.order}. Prize: {self.description}"
 

	
 

	
 
class PrizeAudit(models.Model):
 
    """
 
    Stores an audit event for changes to a particular :model:`pinaxcon_raffle.Prize`.
 
    """
 
    reason = models.CharField(max_length=255)
 
    prize = models.ForeignKey('pinaxcon_raffle.Prize', related_name='audit_events')
 
    prize = models.ForeignKey(
 
        'pinaxcon_raffle.Prize',
 
        related_name='audit_events',
 
        on_delete=models.CASCADE,
 
    )
 

	
 
    user = models.ForeignKey('auth.User')
 
    user = models.ForeignKey(
 
        settings.AUTH_USER_MODEL,
 
        on_delete=models.CASCADE,
 
    )
 
    timestamp = models.DateTimeField(auto_now_add=True)
 

	
 
    class Meta:
 
        ordering = ('-timestamp',)
 

	
 
    def __str__(self):
 
        return self.reason
 

	
 

	
 
class Draw(models.Model):
 
    """
 
    Stores a draw for a given :model:`pinaxcon_raffle.Raffle`, along with audit fields
 
    for the creating :model:`auth.User` and the creation timestamp.
 
    """
 
    raffle = models.ForeignKey('pinaxcon_raffle.Raffle', related_name='draws')
 
    drawn_by = models.ForeignKey('auth.User')
 
    raffle = models.ForeignKey(
 
        'pinaxcon_raffle.Raffle',
 
        related_name='draws',
 
        on_delete=models.CASCADE,
 
    )
 
    drawn_by = models.ForeignKey(
 
        settings.AUTH_USER_MODEL,
 
        on_delete=models.CASCADE,
 
    )
 
    drawn_time = models.DateTimeField(auto_now_add=True)
 

	
 
    def __str__(self):
 
        return f"{self.raffle}: {self.drawn_time}"
 

	
 

	
 
class DrawnTicket(models.Model):
 
    """
 
    Stores the result of a ticket draw, along with the corresponding
 
    :model:`pinaxcon_raffle.Draw`, :model:`pinaxcon_raffle.Prize` and the
 
    :model:`registrasion.commerce.LineItem` from which it was generated.
 
    """
 
    ticket = models.CharField(max_length=255)
 

	
 
    draw = models.ForeignKey('pinaxcon_raffle.Draw')
 
    prize = models.ForeignKey('pinaxcon_raffle.Prize')
 
    lineitem = models.ForeignKey('registrasion.LineItem')
 
    draw = models.ForeignKey(
 
        'pinaxcon_raffle.Draw',
 
        on_delete=models.CASCADE,
 
    )
 
    prize = models.ForeignKey(
 
        'pinaxcon_raffle.Prize',
 
        on_delete=models.CASCADE,
 
    )
 
    lineitem = models.ForeignKey(
 
        'registrasion.LineItem',
 
        on_delete=models.CASCADE,
 
    )
 

	
 
    def __str__(self):
 
        return f"{self.ticket}: {self.draw.raffle}"
...
 
\ No newline at end of file
 
        return f"{self.ticket}: {self.draw.raffle}"
pinaxcon/registrasion/management/commands/dummy_presentations.py
Show inline comments
 
from django.contrib.auth.models import User
 
from django.contrib.auth import get_user_model
 
from django.core.management.base import BaseCommand
 

	
 
from symposion.conference.models import Section, current_conference
 

	
 
from symposion.speakers.models import Speaker
 
from symposion.schedule.models import Presentation
 
from symposion.proposals.models import ProposalKind
 
from pinaxcon.proposals.models import TalkProposal
 

	
 
User = get_user_model()
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    help = "Creates a bunch of dummy presentations to play around with."
 

	
 
    def handle(self, *args, **options):
 
        conf = current_conference()
 
        section = Section.objects.filter(conference=conf, slug="main").all().first()
 

	
 
        user = User.objects.first()
 
        speaker = Speaker.objects.first()
 
        if not speaker:
 
            speaker, _ = Speaker.objects.get_or_create(name="Dummy Speaker",
 
                                                       defaults={"user": user})
 
        talk_kind = ProposalKind.objects.first()
 
        target_audience = TalkProposal.TARGET_USER
 

	
 
        for i in range(1000, 1020):
 
            prop, _created = TalkProposal.objects.get_or_create(
 
                pk=i, kind=talk_kind, speaker=speaker, target_audience=target_audience,
 
                title=f"dummy title {i}", abstract=f"dummy abstract {i}")
 

	
 
            pres, _created = Presentation.objects.get_or_create(
 
                proposal_base=prop, section=section, speaker=speaker,
 
                title=f"dummy title {i}", abstract=f"dummy abstract {i}")
pinaxcon/settings.py
Show inline comments
 
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
 
import pytz
 

	
 
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 = []
 

	
 
if DEV_MODE and DEV_MODE == "LAPTOP":
 
    CACHES = {
 
        'default': {
 
            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
 
        }
 
    }
 
else:
 
    CACHES = {
 
        'default': {
 
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
 
            'LOCATION': 'unique-snowflake',
 
        }
 
    }
 

	
 

	
 
ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '*']
 

	
 
TIME_ZONE = "Australia/Brisbane"
 
TIME_ZONE = "Australia/Melbourne"
 
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.environ.get("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 = [
 
MIDDLEWARE = [
 
    "whitenoise.middleware.WhiteNoiseMiddleware",
 
    "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 = [
 
    "whitenoise.runserver_nostatic",
 
    "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",
 
    "capture_tag",
 

	
 
    # external
 
    "easy_thumbnails",
 
    "taggit",
 
    "reversion",
 
    "sitetree",
 
    "django_jsonfield_backport",
 
    "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,
 
}
 

	
 
INTERNAL_IPS = [
 
    '127.0.0.1',
 
]
 

	
 
from debug_toolbar.panels.logging import collector
 
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,
 
        },
 
        'djdt_log': {
 
            'level': 'DEBUG',
 
            'class': 'debug_toolbar.panels.logging.ThreadTrackingHandler',
 
            'collector': collector,
 
        },
 
    },
 
    'loggers': {
 
        'django.request': {
 
            'handlers': ['mail_admins'],
 
            'level': 'DEBUG',
 
            'propagate': True,
 
        },
 
        'symposion.request': {
 
            'handlers': ['mail_admins'],
 
            'level': 'DEBUG',
 
            'propagate': True,
 
        },
 
    },
 
    'root': {
 
        'handlers': ['console', 'djdt_log'],
 
        '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",
 
}
 
MAIN_CONFERENCE_PROPOSAL_KINDS = ("Talk", "Miniconf")
 

	
 
# Registrasion bits:
 
ATTENDEE_PROFILE_MODEL = "pinaxcon.registrasion.models.AttendeeProfile"
 
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
 
INVOICE_CURRENCY = "AUD"
 
GST_RATE =  Decimal('0.1')
 
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 = os.environ.get('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(2021, 1, 23)
 
LCA_END = datetime(2021, 1, 25)
 
EARLY_BIRD_DEADLINE = datetime(2020, 12, 1)
 
_TZINFO = pytz.timezone(TIME_ZONE)
 
LCA_START = datetime(2021, 1, 23, tzinfo=_TZINFO)
 
LCA_END = datetime(2021, 1, 25, tzinfo=_TZINFO)
 
EARLY_BIRD_DEADLINE = datetime(2020, 12, 1, tzinfo=_TZINFO)
 
PENGUIN_DINNER_TICKET_DATE = date(2021, 1, 23)
 
SPEAKER_DINNER_TICKET_DATE = date(2021, 1, 25)
 
PDNS_TICKET_DATE = date(2021, 1, 24)
 

	
 
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))
pinaxcon/templates/403_csrf.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% load i18n %}
 

	
 
{% block body_class %}template-blogpage{% endblock %}
 

	
 
{% block head_title %}{{ page.title }}{% endblock %}
 

	
 
{% block body %}
 
  {% block content %}
 
    <div class="l-content-page">
 
      <div class="l-content-page--richtext">
 
        <h2>{{ title }} <span>(403)</span></h2>
 

	
 
        <p>{{ main }}</p>
 

	
 
        {% if bad_token and request.user.is_authenticated %}
 
          <p>You are already logged in. If you saw this issue whilst attempting
 
            to log in, you can to go to the
 
            <a href='{% url "dashboard" %}'>Dashboard</a> and continue using
 
            the site.</p>
 
        {% endif %}
 

	
 
        {% if no_referer %}
 
          <p>{{ no_referer1 }}</p>
 
          <p>{{ no_referer2 }}</p>
 
        {% endif %}
 
        {% if no_cookie %}
 
          <p>{{ no_cookie1 }}</p>
 
          <p>{{ no_cookie2 }}</p>
 
        {% endif %}
 

	
 
        {% if DEBUG %}
 
          <h2>Help</h2>
 
            {% if reason %}
 
            <p>Reason given for failure:</p>
 
            <pre>
 
            {{ reason }}
 
            </pre>
 
            {% endif %}
 
          <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
 
          <a
 
          href="https://docs.djangoproject.com/en/{{ docs_version }}/ref/csrf/">Django's
 
          CSRF mechanism</a> has not been used correctly.  For POST forms, you need to
 
          ensure:</p>
 
          <ul>
 
            <li>Your browser is accepting cookies.</li>
 
            <li>The view function passes a <code>request</code> to the template's <a
 
            href="https://docs.djangoproject.com/en/dev/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a>
 
            method.</li>
 
            <li>In the template, there is a <code>{% templatetag openblock %} csrf_token
 
            {% templatetag closeblock %}</code> template tag inside each POST form that
 
            targets an internal URL.</li>
 
            <li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
 
            <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
 
            template tag, as well as those that accept the POST data.</li>
 
          </ul>
 
          <p>You're seeing the help section of this page because you have <code>DEBUG =
 
          True</code> in your Django settings file. Change that to <code>False</code>,
 
          and only the initial error message will be displayed.  </p>
 
          <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
 
        {% else %}
 
          <p><small>{{ more }}</small></p>
 
        {% endif %}
 
      </div>
 
    </div>
 
  {% endblock %}
 
{% endblock %}
pinaxcon/templates/404.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% load i18n %}
 

	
 
{% block body_class %}template-blogpage{% endblock %}
 

	
 
{% block head_title %}{{ page.title }}{% endblock %}
 

	
 
{% block body %}
 
  {% block content %}
 
    <div class="l-content-page">
 
      <div class="l-content-page--richtext ooops-hack">
 
        <h2>Ooops</h2>
 

	
 
        <p>The page you're looking for doesn't exist. Sorry!</p>
 
      </div>
 
    </div>
 
  {% endblock %}
 
{% endblock %}
pinaxcon/templates/dashboard.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load i18n %}
 

	
 
{% load review_tags %}
 
{% load teams_tags %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load staticfiles %}
 

	
 

	
 
{% block head_title %}Dashboard{% endblock %}
 
{% block page_title %}User Dashboard{% endblock %}
 

	
 
{% block alert %}
 
{% endblock %}
 

	
 
{% block content %}
 

	
 
{% available_categories as categories %}
 
{% if categories %}
 
  {% include "symposion/dashboard/_categories.html" %}
 
{% endif %}
 

	
 
<div class="mb-4">
 
  {% include "symposion/dashboard/speaking.html" %}
 
</div>
 

	
 
{% if review_sections %}
 
<div class="mb-4">
 
  <div class="row">
 
    <h2 class="col-12 mb-3">{% trans "Reviews" %}</h2>
 
  </div>
 
  <div class="row">
 
    {% for section in review_sections %}
 
    <div class="col-md-6 col-lg-4">
 
    <div class="card card-default mb-3">
 
      <div class="card-header">
 
          <h3 class="card-title">{{ section }}</h3>
 
      </div>
 
      <div class="card-body">
 
        <ul class="list-unstyled">
 
            <li><a href="{% url "review_section" section.section.slug %}">All Reviews</a></li>
 
            {% comment %}
 
            <li><a href="{% url "review_section_assignments" section.section.slug %}">Your Assignments</a></li>
 
            {% endcomment %}
 
            <li><a href="{% url "user_reviewed" section.section.slug %}">Reviewed by you</a></li>
 
            <li><a href="{% url "user_not_reviewed" section.section.slug %}">Not Reviewed by you</a></li>
 
            <li><a href="{% url "user_random" section.section.slug %}">Random unreviewed proposal</a></li>
 
          </ul>
 

	
 
          <ul class="list-unstyled">
 
            <li><a href="{% url "review_status" section.section.slug %}">Voting Status</a></li>
 
            {% if section in manage_sections %}
 
            <li><a href="{% url "review_bulk_update" section.section.slug %}">Bulk Update</a></li>
 
            <li><a href="{% url "result_notification" section.section.slug "accepted" %}">Result notifications</a></li>
 
            <li><a href="{% url "review_admin" section.section.slug %}">Reviewer Stats</a></li>
 
            {% endif %}
 
          </ul>
 
      </div>
 
    </div>
 
    </div>
 
    {% endfor %}
 
  </div>
 
</div>
 
{% endif %}
 

	
 
{% available_teams as available_teams %}
 
{% if user.memberships.exists or available_teams %}
 
<div class="mb-4">
 
  <div class="row">
 
    <h2 class="col-12 mb-3">{% trans "Teams "%}</h2>
 
  </div>
 
  {% if user.memberships.exists %}
 
  <div class="row">
 
    <h3 class="col-12">Your Teams</h3>
 
  </div>
 
  <div class="row">
 
    {% for membership in user.memberships.all %}
 
    <div class="col-md-6">
 
      {% include "symposion/teams/_team_row.html" with team=membership.team %}
 
    </div>
 
    {% endfor %}
 
  </div>
 
  {% endif %}
 
  {% if available_teams %}
 
  <div class="row">
 
    <h3 class="col-12">Available Teams</h3>
 
  </div>
 
  <div class="row">
 
    {% for team in available_teams %}
 
    <div class="col-md-6">
 
      {% include "symposion/teams/_team_row.html" %}
 
    </div>
 
    {% endfor %}
 
  </div>
 
  {% endif %}
 
</div>
 
{% endif %}
 

	
 
{% endblock %} <!-- block content -->
 

	
 
{% block scripts_extra %}
 
<script type="text/javascript">
 
  var _toggleVoidInvoices = function() {
 
    var visible = false;
 
    function toggleVoidInvoices() {
 
      $btn = $("#toggle-void-invoices");
 
      $invoices = $(".void-invoice")
 

	
 
      if (visible) {
 
        $invoices.hide();
 
        btnText = "Show void invoices";
 
      } else {
 
        $invoices.show();
 
        btnText = "Hide void invoices";
 
      }
 
      $btn.text(btnText);
 
      visible = !visible;
 
      return true;
 
    }
 
    return toggleVoidInvoices;
 
  }
 
  var toggleVoidInvoices = _toggleVoidInvoices();
 
  _toggleVoidInvoices = undefined;
 
</script>
 
{% endblock %}
pinaxcon/templates/raffle.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% block head_title %}Raffle Tickets{% endblock %}
 
{% block page_title %}Raffle Tickets{% endblock %}
 

	
 
{% block content %}
 
<p>
 
  All of the raffles you are entered into and the tickets you have for each of them.
 
  If available, you will be able to purchase additional raffle tickets from the <a href="{% url "dashboard" %}">Dashboard</a>.
 
</p>
 

	
 
<h2>Your Raffle Tickets</h2>
 
{% for raffle in raffles %}
 
{% if raffle.tickets %}
 
  <h3 class="mt-3">{{ raffle }}</h3>
 
  <ul>
 
   {% for id, numbers in raffle.tickets %}
 
    <li>
 
      <strong>Ticket {{ id }}</strong>
 
      <p>{% for number in numbers %}{{ number }}{% if not forloop.last %}, {% endif %}{% endfor %}</p>
 
    </li>
 
  {% endfor %}
 
  </ul>
 
{% endif %}
 
{% empty %}
 
<p>You do not have tickets in any raffles.</p>
 
{% endfor %}
 
{% endblock %}
pinaxcon/templates/raffle_draw.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% block head_title %}Raffle Winners{% endblock %}
 
{% block page_title %}Raffle Winners{% endblock %}
 

	
 
{% block content %}
 
{% for raffle in raffles %}
 
{% if raffle.hidden %}
 
{% else %}
 
<h2 class="mt-3"><a href="{% url "raffle-draw" raffle.id %}">{{ raffle }}</a></h2>
 

	
 
<dl class="row my-4">
 
  {% for prize in raffle.prizes.all %}
 
  <dt class="col-sm-3 text-truncate">{{ prize }}</dt>
 
  <dd class="col-sm-9">
 
    {% if prize.winning_ticket %}
 
    {% with prize.winning_ticket as winner %}
 
    {# this should be attendee name #}
 
    {% with winner.lineitem.invoice.user.attendee.attendeeprofilebase as profile %}
 
    <p><strong>Winning ticket {{ winner.ticket }}, <a href="{% url "attendee" winner.lineitem.invoice.user.id %}">{{ profile.attendee_name }}</a></strong><br />
 
      Drawn by {{ winner.draw.drawn_by }}, {{ winner.draw.drawn_time}}
 
    </p>
 
    {% endwith %}
 
    <div class="alert alert-danger">
 
      <form method="POST" action="{% url 'raffle-redraw' winner.id %}">
 
        {% csrf_token %}
 
        {# This should have a `reason` field that can be passed through to the Audit log #}
 
        <p>
 
          Re-draw <em>{{ prize }}</em>
 
          <button type="submit" class="btn btn-danger float-right">Re-draw</button>
 
        </p>
 
        <div class="clearfix"></div>
 
      </form>
 
    </div>
 
    {% endwith %}
 
    {% else %}
 
    Not drawn
 
    {% endif %}
 
  </dd>
 
  {% endfor %}
 
</dl>
 

	
 
{% if raffle.is_open %}
 
<form method="POST" action="{% url 'raffle-draw' raffle_id=raffle.id %}">
 
    {% csrf_token %}
 
    <button type="submit" class="btn btn-success">Draw tickets</button>
 
    <div class="clearfix"></div>
 
  </form>
 
{% endif %}
 
{% if not forloop.last %}<hr>{% endif %}
 
{% endif %}
 
{% endfor %}
 

	
 
{% endblock %}
pinaxcon/templates/registrasion/base.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load lca2018_tags %}
 
{% load i18n %}
 

	
 
{% block head_title %}{% block page_title %}{% endblock %}{% endblock %}
 

	
 
{% block content_base %}
 
{% block content %}
 
  <div class="jumbotron rego-content">
 
    {% block proposals_body %}
 
    {% endblock %}
 
  </div>
 
{% endblock content %}
 
{% endblock %}
pinaxcon/templates/registrasion/invoice.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% block head_title %}Tax Invoice/Statement #{{ invoice.id }}{% endblock %}
 
{% block page_title %}{% conference_name %}{% endblock %}
 

	
 
{% block proposals_body %}
 
{% include "registrasion/_invoice_details.html" %}
 
<div class="d-print-none mb-4 pb-4">
 
  {% if invoice.is_unpaid %}
 
  <p>
 
    <strong>NOTICE:</strong> The above invoice is automatically generated, and
 
    will be voided if you amend your selections before payment, or if discounts
 
    or products contained in the invoice become unavailable. The products and
 
    discounts are only reserved until the invoice due time, please pay before then
 
    to guarantee your selection. Late payments are accepted only if the products
 
    and discounts are still available.</p>
 

	
 
  {% url "invoice_access" invoice.user.attendee.access_code as access_url %}
 
  <p>Your most recent unpaid invoice will be available at
 
    <a href="{{ access_url }}">{{ request.scheme }}://{{ request.get_host }}{{ access_url }}</a>
 
    You can print that page for your records, or give this URL to your accounts department to pay for this invoice
 
  </p>
 

	
 
  <a class="btn btn-primary" href='{% url "registripe_card" invoice.id invoice.user.attendee.access_code %}'>Pay this invoice by card</a>
 

	
 
  {% if user.is_staff %}
 
  <a class="btn btn-secondary" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
 
  {% endif %}
 

	
 
  {% elif invoice.is_paid %}
 
  {% if user.is_staff %}
 
  <a class="btn btn-primary" href="{% url "manual_payment" invoice.id %}">Apply manual payment/refund</a>
 
  <a class="btn btn-secondary" href="{% url "refund" invoice.id %}">Refund by issuing credit note</a>
 
  {% endif %}
 
  {% endif %}
 

	
 
  {% if user.is_staff %}
 
  <a class="btn btn-info" href="{% url "attendee" invoice.user.id %}">View attendee</a>
 
  <a class="btn btn-light" href="{% url "invoice_update" invoice.id %}">Refresh recipient</a>
 
  {% endif %}
 
</div>
 

	
 
{% endblock %}
pinaxcon/templates/site_base.html
Show inline comments
 
{% load staticfiles %}
 
{% load static %}
 
{% load i18n %}
 
{% load sitetree %}
 
{% load sass_tags %}
 
{% load capture_tags %}
 

	
 
{% capture as head_title silent %}{% block head_title_base %}{% if SITE_NAME %}{{ SITE_NAME }} | {% endif %}{% block head_title %}{% endblock %}{% endblock %}{% endcapture %}
 

	
 
<!DOCTYPE html>
 

	
 
<html lang="en">
 
<head>
 
  <meta charset="utf-8">
 
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
 
  <meta name="description" content="">
 
  <meta name="viewport" content="width=device-width, initial-scale=1">
 

	
 
  <title>{{ head_title }}</title>
 

	
 
  <meta property="og:type" content="website" />
 

	
 
  <!-- Cards -->
 
  <meta property="og:title" content="{{ head_title }}">
 
  <meta property="og:description" content="linux.conf.au 2021 - Jan 23-25 2021, Online, Worldwide" />
 
  <meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}">
 
  <meta name="twitter:site" content="@linuxconfau">
 
  <meta name="twitter:image:alt" content="{{ head_title }}" />
 
  <meta name="twitter:card" content="summary">
 
  <meta name="twitter:image" content="{{ request.scheme }}://{{ request.get_host }}/media/img/card/lca_badge.0e10614e.png" />
 
  <meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}/media/img/card/lca_badge.0e10614e.png" />
 
  <meta property="og:image:width" content="400" />
 
  <meta property="og:image:height" content="400" />
 

	
 
  {% block styles %}
 
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
 
  <link href="{% sass_src 'scss/app.scss' %}" rel="stylesheet" type="text/css" />
 
  {% block extra_style %}{% endblock %}
 
  {% endblock %}
 

	
 
  {% block extra_head_base %}
 
  {% block extra_head %}{% endblock %}
 
  {% endblock %}
 
</head>
 
<body class="{% block body_class %}{% endblock %}">
 
  {% block template_overrides %}{% endblock %}
 
  <header class="clearfix d-print-none">
 
    {% block alert %}{% endblock %}
 
    {% block navbar %}{% include 'nav.html' %}{% endblock %}
 
  </header>
 

	
 
  {% if messages %}
 
  <div class="container my-5 alert alert-primary d-print-none">
 
    <ul class="messagelist list-unstyled">
 
      {% for message in messages %}
 
      <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
 
      {% endfor %}
 
    </ul>
 
  </div>
 
  {% endif %}
 

	
 
  <main role="main" class="{% block main_class %}container{% endblock %}">
 
    <div class="row">
 
      <div class="col page-header">
 
        <h1 class="page-title">{% block page_title %}{% endblock %}</h1>
 
        <p class="lead">{% block page_lead %}{% endblock %}</p>
 
      </div>
 
    </div>
 

	
 
    {% block body_base %}
 
    {% block body_out %}
 
    <div class="row">
 
      <div class="col-md-12">
 
        {% block body_outer %}
 
        {% endblock %}
 
      </div>
 
    </div>
 
    {% block content %}
 
    {% endblock %}
 
    {% endblock %}
 
    {% endblock %}
 

	
 
    {% block footer_base %}
 
    {% block footer %}
 
    {% endblock %}
 
    {% endblock %}
 

	
 
    {% block scripts %}
 
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
 
    <script src="{% static 'js/app.js' %}" type="text/javascript"></script>
 
    <script src="{% static 'js/jquery.formset.js' %}"></script>
 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
 
    <script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
 
    {% if settings.ANALYTICS_KEY %}
 
    <!-- Global site tag (gtag.js) - Google Analytics -->
 
    <script async src="https://www.googletagmanager.com/gtag/js?id={{ settings.ANALYTICS_KEY }}"></script>
 
    <script>
 
      window.dataLayer = window.dataLayer || [];
 
      function gtag(){dataLayer.push(arguments);}
 
      gtag('js', new Date());
 

	
 
      gtag('config', '{{ settings.ANALYTICS_KEY }}');
 
    </script>
 
    {% else %}
 
    <!--no-analytics-->
 
    {% endif %}
 
    {% block extra_script %}
 
    {% endblock %}
 
    {% block scripts_extra %}{% endblock %}
 
    {% endblock %}
 

	
 
    {% block extra_body_base %}
 
    {% block extra_body %}
 
    {% endblock %}
 
    {% endblock %}
 
  </main>
 

	
 
  <footer class="footer mt-4 d-print-none">
 
    <div class="container py-4">
 
      <div class="row">
 
        <div class="col-md-4 pb-4">
 
          <strong>linux.conf.au 2021</strong> <br>
 
          Jan 23-25 2021 <br>
 
          Online, Worldwide <br>
 
          <a href="mailto:contact@lca2021.linux.org.au" alt="Email"><i class="far fa-envelope"></i></a>&nbsp;&nbsp;
 
          <a href="https://twitter.com/linuxconfau" alt="Twitter"><i class="fab fa-twitter"></i></a>&nbsp;&nbsp;
 
          <a href="https://www.facebook.com/linuxconferenceaustralia/" alt="Facebook"><i class="fab fa-facebook"></i></a>
 
        </div>
 
        <div class="col-md-4 pb-4 text-center">
 
          <img src="{% static 'lca/lca_horiz_colour.svg' %}" alt="linux.conf.au logo" class="footer-logo">
 
          <a href="https://linux.org.au"><img src="{% static 'lca/la_logo.svg' %}" alt="Linux Australia logo" class="footer-image"></a>
 
        </div>
 
        <div class="col-md-4 pb-4 text-right">
 
          <small>
 
            <a href="#">Back to top</a><br>
 
            &copy; 2020 linux.conf.au and <a href="http://linux.org.au/">Linux Australia</a><br>
 
            Linux is a registered trademark of Linus Torvalds <br>
 
            <a href="/colophon/">Colophon</a>
 
          </small>
 
        </div>
 
      </div>
 
    </div>
 
  </footer>
 

	
 
</body>
 
</html>
pinaxcon/templates/symposion/dashboard/_categories.html
Show inline comments
 
{% load i18n %}
 
{% load proposal_tags %}
 
{% load review_tags %}
 
{% load teams_tags %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load lca2019_tags %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load waffle_tags %}
 

	
 
{% if user.is_staff %}
 
<div class="mb-4">
 
  <div class="row">
 
    <div class="col-12">
 
      <h2>{% trans "Administration" %}</h2>
 
      <p>The following administrative tools are available to you:
 
        <ul class="list-unstyled">
 
          <li><a href="{% url "reports_list" %}">Reports</a></li>
 
        </ul>
 
      </p>
 
    </div>
 
  </div>
 
</div>
 
{% endif %}
 

	
 
<div class="mb-4">
 
  <div class="row">
 
    <div class="col-12">
 
      <h2>{% trans "Attend" %} {% conference_name %}</h2>
 
    </div>
 
  </div>
 

	
 
  {% if not user.attendee.completed_registration %}
 
  <div class="row">
 
    <div class="col-12">
 
      <h3>Register</h3>
 
      <p>To attend the conference, you must create an attendee profile and purchase your ticket</p>
 
      <div>
 
        <a class="btn btn-lg btn-primary" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
 
      </div>
 
    </div>
 
  </div>
 
  {% else %}
 
  <div class="row">
 
    <div class="col-md-6 mb-3 mb-md-0">
 
      <h3>Attendee Profile</h3>
 
      <p>If you would like to change the details on your badge or your attendee statistics, you may edit your attendee profile here.</p>
 
      <div>
 
        <a class="btn btn-primary" role="button" href="{% url "attendee_edit" %}">Edit attendee profile</a>
 
        {% flag "badge_preview" %}
 
        <a class="btn btn-info" role="button" href="{% url "user_badge" %}">Preview my badge</a>
 
        {% endflag %}
 
      </div>
 
    </div>
 
    <div class="col-md-6 mb-3 mb-md-0">
 
      <h3>Account Management</h3>
 
      <p>If you would like to change your registered email address or password, you can use our self-service account management portal</p>
 
      <div>
 
        <a class="btn btn-primary" role="button" href="https://login.linux.conf.au/manage/">Account Management</a>
 
      </div>
 
    </div>
 
  </div>
 

	
 
  {% items_pending as pending %}
 
  <div class="row">
 
    <div class="col-12">
 
      <h3 class="my-3">Account</h3>
 
    </div>
 
  </div>
 

	
 
  <div class="row">
 
    {% if pending %}
 
    <div class="col-md-6 mb-3">
 
      <h4>Items pending payment</h4>
 
      {% include "registrasion/_items_list.html" with items=pending %}
 
      <a class="btn btn-primary" role="button" href="{% url "checkout" %}"><i class="fa fa-credit-card"></i> Check out and pay</a>
 
      <a class="btn btn-secondary" role="button" href="{% url "voucher_code" %}">Apply voucher</a>
 
    </div>
 
    {% endif %}
 

	
 
    {% items_purchased as purchased %}
 
    {% if purchased %}
 
    <div class="col-md-6 mb-3">
 
      <h4>Paid Items</h4>
 
      {% include "registrasion/_items_list.html" with items=purchased %}
 

	
 
      {% if not pending %}
 
      <a class="btn btn-secondary" role="button" href="{% url "voucher_code" %}">Apply voucher</a>
 
      {% endif %}
 
    </div>
 
    {% endif %}
 

	
 
    <div class="col-md-6 mb-3">
 
      <h4>Add/Update Items</h4>
 
      {% missing_categories as missing %}
 
      {% if missing %}
 
      <div class="alert alert-warning my-4 pb-4">
 
        <h5 class="alert-heading">You have empty categories</h5>
 
        <p>You have <em>not</em> selected anything from the following
 
          categories. If your ticket includes any of these, you still need to
 
          make a selection:
 
        </p>
 

	
 
        {% include "registrasion/_category_list.html" with categories=missing %}
 
      </div>
 
      {% endif %}
 

	
 
      {% available_categories as available %}
 
      {% if available|contains_items_not_in:missing %}
 
      <p><strong>You can change your selection from these categories:</strong></p>
 
      {% include "registrasion/_category_list.html" with categories=available exclude=missing %}
 
      {% endif %}
 
    </div>
 

	
 
    {% invoices as invoices %}
 
    {% if invoices %}
 
    <div class="col-md-6 mb-3">
 
      <h4>Invoices</h4>
 
      <ul>
 
        {% for invoice in invoices %}
 
        <li{% if invoice.is_void %} class="void-invoice" style="display: none;"{% endif %}>
 
          <a href="{% url "invoice" invoice.id %}" >Invoice {{ invoice.id }}</a> - ${{ invoice.value }} ({{ invoice.get_status_display }})
 
        </li>
 
        {% endfor %}
 
      </ul>
 
      {% if invoices|any_is_void %}
 
      <div class="mt-auto">
 
        <button type="button" class="btn btn-sm btn-outline-dark" id="toggle-void-invoices" onclick="toggleVoidInvoices();">Show void invoices</button>
 
      </div>
 
      {% endif %}
 
    </div>
 
    {% endif %}
 

	
 
    {% flag "raffle_dashboard" %}
 
    <div class="col-md-6 mb-3">
 
      <h4>Raffle Tickets</h4>
 

	
 
      <p><a href="{% url "raffle-tickets" %}">View raffle tickets</a></p>
 
    </div>
 
    {% endflag %}
 

	
 
    {% available_credit as credit %}
 
    {% if credit %}
 
    <div class="col-md-6 mb-3">
 
      <h4>Credit</h4>
 
      <p>You have ${{ credit }} leftover from refunded invoices. This credit will be automatically applied to new invoices. Contact the conference organisers if you wish to arrange a refund to your original payment source.</p>
 
    </div>
 
    {% endif %}
 
  </div>
 
  {% endif %} {# user.attendee.completed_registration #}
 
</div>
pinaxcon/templates/symposion/proposals/base.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 

	
 
{% block body_outer %}
 
  {% block proposals_body %}
 
  {% endblock %}
 
{% endblock %}
 

	
pinaxcon/templates/symposion/reviews/base.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% load i18n %}
 

	
 
{% block body_class %}reviews{% endblock %}
 
{% block main_class %}container-fluid{% endblock %}
 

	
 
{% block body_outer %}
 
<div class="container-fluid">
 
    <div class="row">
 
        <div class="col-md-2 col-sm-4">
 
            {% block sidebar %}
 
            {% for section in review_sections %}
 

	
 
            <div class="card card-default mb-3">
 
                <div class="card-header">
 
                    <h3 class="card-title">{{ section }}</h3>
 
                </div>
 
                <div class="list-group">
 
                    <a class="list-group-item review-list" href="{% url "review_section" section.section.slug %}">
 
                        {% trans "All Reviews" %}
 
                    </a>
 
                    {% comment %}
 
                    <a class="list-group-item" href="{% url "review_section_assignments" section.section.slug %}">
 
                        {% trans "Your Assignments" %}
 
                    </a>
 
                    {% endcomment %}
 
                    <a class="list-group-item user-reviewed" href="{% url "user_reviewed" section.section.slug %}">
 
                        {% trans "Reviewed by you" %}
 
                    </a>
 
                    <a class="list-group-item user-not-reviewed" href="{% url "user_not_reviewed" section.section.slug %}">
 
                        {% trans "Not Reviewed by you" %}
 
                    </a>
 
                    <a class="list-group-item user-random" href="{% url "user_random" section.section.slug %}">
 
                        {% trans "Random unreviewed proposal" %}
 
                    </a>
 
                    <a class="list-group-item voting-status" href="{% url "review_status" section.section.slug %}">
 
                        {% trans "Voting Status" %}
 
                    </a>
 
                    {% if section in manage_sections %}
 
                    <a class="list-group-item" href="{% url "review_bulk_update" section.section.slug %}">
 
                        Bulk Update
 
                    </a>
 
                    <a class="list-group-item review-results" href="{% url "result_notification" section.section.slug 'accepted' %}">
 
                        Result Notifications
 
                    </a>
 
                    <a class="list-group-item" href="{% url "review_admin" section.section.slug %}">
 
                        Reviewer Stats
 
                    </a>
 
                    {% endif %}
 
                </div>
 
            </div>
 
            {% endfor %}
 
            {% endblock %}
 
        </div>
 
        <div class="col-md-10 col-sm-8">
 
            {% block body %}
 
            {% endblock %}
 
        </div>
 
    </div>
 
</div>
 
{% endblock %}
 

	
 
{% block extra_style %}
 
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/jszip-2.5.0/dt-1.10.18/b-1.5.6/b-colvis-1.5.6/b-html5-1.5.6/b-print-1.5.6/cr-1.5.0/fc-3.2.5/fh-3.1.4/kt-2.5.0/r-2.2.2/rg-1.1.0/datatables.min.css"/>
 
{% endblock %}
 

	
 
{% block extra_script %}
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
 
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/jszip-2.5.0/dt-1.10.18/b-1.5.6/b-colvis-1.5.6/b-html5-1.5.6/b-print-1.5.6/cr-1.5.0/fc-3.2.5/fh-3.1.4/kt-2.5.0/r-2.2.2/rg-1.1.0/datatables.min.js"></script>
 
<script type="text/javascript">
 
    var reviewtable = $("table.table-reviews").DataTable({
 
            "stateSave": true,
 
            "autoWidth": false,
 
            "lengthMenu": [[10, 50, 100, -1], [10, 50, 100, "All"]],
 
            "pageLength": 100,
 
            "colReorder": true,
 
            "columnDefs": [
 
                { targets: [6, 7, 8, 9, 10], visible: false }
 
            ],
 
            "buttons": [{
 
                extend: 'collection',
 
                text: 'Export',
 
                buttons: ["copy", "csv", "print", "pdf"]
 
            },
 
            {
 
                extend: 'collection',
 
                text: 'Columns',
 
                buttons: [
 
                    {
 
                        extend: 'columnsToggle',
 
                        columns: '.toggle'
 
                    },
 
                    {
 
                        extend: 'columnToggle',
 
                        text: 'Vote details',
 
                        columns: '.votes'
 
                    }
 
                ]
 
            }
 
            ]
 
        });
 
    reviewtable.buttons().container().prependTo( $(reviewtable.table().container() ));
 
</script>
 
<script>
 
$(document).ready(function () {
 

	
 
$('#sidebarBtn').on('click', function () {
 
    $('.sidebar').toggleClass('active');
 
});
 

	
 
});
 
</script>
 
{% endblock %}
pinaxcon/templates/symposion/reviews/review_list.html
Show inline comments
 
{% extends "symposion/reviews/base.html" %}
 

	
 
{% block head_title %}Reviews - {{ section }}{% endblock %}
 

	
 
{% block body_class %}{{ block.super }}
 
	{% if reviewed == "all_reviews" %}
 
		review-list
 
	{% endif %}
 
{% endblock %}
 

	
 
{% block body %}
 
	<h3>{{ section }}</h3>
 
	{% if reviewed == 'all_reviews' %}
 
    	<h4>All proposals</h4>
 
    {% elif reviewed == 'user_reviewed' %}
 
    	<h4>Proposals you have reviewed</h4>
 
		<h4>Proposals you have reviewed</h4>
 
	{% elif reviewed == 'user_not_reviewed' %}
 
		<h4>Proposals you have not reviewed</h4>
 
    {% else %}
 
    	<h4>Proposals you have not yet reviewed</h4>
 
    	<h4>Proposals reviewed by selected reviewer</h4>
 
    {% endif %}
 

	
 
    {% include "symposion/reviews/_review_table.html" %}
 
{% endblock %}
pinaxcon/templates/symposion/reviews/review_review.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% load bootstrap %}
 

	
 
{% block body_class %}review{% endblock %}
 

	
 
{% block body %}
 
<div class="l-content-page">
 
<div class="l-content-page--richtext">
 
<div class="rich-text">
 
    <h1>Proposal Review</h1>
 

	
 
    <div class="proposal">
 
        <h2>{{ proposal.title }}</h2>
 

	
 
        <p>
 
            {% if proposal.cancelled %}
 
                Cancelled
 
            {% endif %}
 
        </p>
 

	
 
        <div>
 
            {{ proposal.description }}
 
        </div>
 

	
 
        <p><b>Type</b>: {{ proposal.get_session_type_display }}</p>
 

	
 
        <h3>Abstract</h3>
 
        <div class="abstract">
 
            {{ proposal.abstract_html|safe }}
 
        </div>
 

	
 
        <p><b>Audience level</b>: {{ proposal.get_audience_level_display }}</p>
 

	
 
        <p><b>Submitting speaker</b>: {{ proposal.speaker }}</p> {# @@@ bio? #}
 

	
 
        {% if proposal.additional_speakers.all %}
 
            <p><b>Additional speakers</b>:</p>
 
            <ul>
 
            {% for speaker in proposal.additional_speakers.all %}
 
                {% if speaker.user %}
 
                    <li><b>{{ speaker.name }}</b> &mdash; {{ speaker.email }}</li>
 
                {% else %}
 
                    <li>{{ speaker.email }} &mdash; pending invitation</li>
 
                {% endif %}
 
            {% endfor %}
 
            </ul>
 
        {% endif %}
 

	
 
        <h3>Additional Notes (private from submitter)</h3>
 
        <div class="additional_notes">
 
            {{ proposal.additional_notes }}
 
        </div>
 
    </div>
 

	
 
    <h2>Review</h2>
 

	
 
    <form method="POST" action="{% url "review_review" proposal.pk %}" class="form form-horizontal">
 
        {% csrf_token %}
 
        <fieldset class="inlineLabels">
 
            {{ review_form|bootstrap }}
 
            <div class="form_block">
 
                <input type="submit" value="Submit" />
 
            </div>
 
        </fieldset>
 
    </form>
 

	
 
    <h2>Comment</h2>
 

	
 
    <form method="POST" action="{% url "review_comment" proposal.pk %}" class="form form-horizontal">
 
        {% csrf_token %}
 
        <fieldset>
 
            {{ comment_form|bootstrap }}
 
            <div class="form_block">
 
                <input type="submit" value="Submit" />
 
            </div>
 
        </fieldset>
 
    </form>
 
</div></div></div>
 
{% endblock %}
pinaxcon/templates/symposion/schedule/presentation_detail.html
Show inline comments
 
{% extends "site_base.html" %}
 

	
 
{% load lca2018_tags %}
 
{% load lca2019_tags %}
 
{% load sitetree %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load thumbnail %}
 

	
 
{% block head_title %}Presentation: {{ presentation.title }}{% endblock %}
 
{% block page_title %}{{ presentation.title }}{% endblock %}
 
{% block page_lead %}
 
{% if presentation.slot %}
 
{{ presentation.slot.rooms.0 }} | {{ presentation.slot.day.date|date:"D d M" }} | {{ presentation.slot.start }}&ndash;{{ presentation.slot.end }}
 
{% else %}
 
<em>Not currently scheduled.</em>
 
{% endif %}
 
{% endblock %}
 

	
 
{% block content %}
 
  {% if presentation.unpublish %}
 
  <div class="row">
 
    <div class="col">
 
      <p><strong>Presentation not published.</strong></p>
 
    </div>
 
  </div>
 
  {% endif %}
 

	
 
  <div class="row presentation-details">
 
    <div class="col-md-3">
 
      <h2 class="mt-4">Presented by</h4>
 
      <ul class="list-unstyled">
 
        {% for speaker in presentation.speakers %}
 
        <li class="mb-4 pb-2">
 
          {% speaker_photo speaker 120 as speaker_photo_url %}
 
          <img src="{{ speaker_photo_url }}" alt="{{ speaker }}" class="rounded-circle img-fluid">
 
          <p>
 
            <strong><a href="{% url "speaker_profile" speaker.pk %}">{{ speaker }}</a></strong><br />
 
            {% if speaker.twitter_username %}
 
              <a href="https://twitter.com/{{ speaker.twitter_username }}">{{ speaker.twitter_username|twitter_handle }}</a><br />
 
            {% endif %}
 
            {% if speaker.homepage %}
 
              <a href="{{ speaker.homepage }}">{{ speaker.homepage }}</a>
 
            {% endif %}
 
          </p>
 
          <div class="bio">{{ speaker.biography_html|safe}}</div>
 
          </p>
 
        </li>
 
        {% endfor %}
 
      </ul>
 
    </div>
 

	
 
    <div class="col-md-9 presentation-abstract">
 
      <h2 class="mt-4">Abstract</h4>
 
      {% autoescape off %}
 
      <div class="abstract pb-4"><p>{{ presentation.abstract_html|safe|clean_text|urlize }}</p></div>
 
      {% endautoescape %}
 
    </div>
 
  </div>
 
{% endblock %}
pinaxcon/templates/symposion/schedule/session_detail.html
Show inline comments
 
{% extends "site_base.html" %}
 

	
 
{% load lca2018_tags %}
 
{% load sitetree %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load thumbnail %}
 
{% load i18n %}
 

	
 

	
 
{% block head_title %}Session: {{ session }}{% endblock %}
 

	
 
{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %}
 

	
 

	
 
{% block header_title %}Session: {{ session }}{% endblock %}
 

	
 
{% block header_paragraph %}
 

	
 
{% endblock %}
 

	
 
{% block content %}
 

	
 
  <dl class="dl-horizontal">
 
    <dt>{% trans "Session Chair" %}</dt>
 
    <dd>
 
        {% if chair %}
 
            {{ chair.attendee.attendeeprofilebase.attendeeprofile.name }}
 
            {% if request.user == chair %}
 
                <form method="POST">
 
                    {% csrf_token %}
 
                    <input type="hidden" name="role" value="un-chair" />
 
                    <input type="submit" value="Opt out" class="btn btn-info"/>
 
                </form>
 
            {% endif %}
 
        {% else %}
 
            {% if request.user.is_authenticated %}
 
                {% if request.user.attendee.completed_registration %}
 
                    {% if not chair_denied %}
 
                        <form method="POST">
 
                            {% csrf_token %}
 
                            <input type="hidden" name="role" value="chair" />
 
                            <input type="submit" class="btn btn-success" value="Volunteer to be session chair"/>
 
                        </form>
 
                    {% endif %}
 
                {% else %}
 
                    {% url 'guided_registration' as guided_registration %}
 
                    <a href="{{ guided_registration }}">Click here to get a ticket for the conference,</a> and enable volunteering for session roles.
 
                {% endif %}
 
            {% else %}
 
              Sign up and <a href="/saml2/login/?next={{ request.path }}">log in</a> to volunteer to be session chair.
 
            {% endif %}
 
        {% endif %}
 
    </dd>
 
    <dt>{% trans "Session Runner" %}</dt>
 
    <dd>
 
        {% if runner %}
 
          {{ runner.profile.display_name }}
 
        {% else %}
 
          {% blocktrans %}Session runner not assigned.{% endblocktrans %}
 
        {% endif %}
 
    </dd>
 
  </dl>
 

	
 
  <h2>Slots</h2>
 

	
 
  <table class="table">
 
    {% for slot in session.slots.all %}
 
      <tr>
 
          <td>{{ slot }}</td>
 
          <td>{% if slot.content %}<a href="{% url 'schedule_presentation_detail' slot.content.pk %}">{{ slot.content }}</a>{% endif %}</td>
 
      </tr>
 
    {% empty %}
 
      <li>{% trans "No slots in session." %}</li>
 
    {% endfor %}
 
  </table>
 

	
 
{% endblock %}
pinaxcon/templates/symposion/schedule/session_list.html
Show inline comments
 
{% extends "site_base.html" %}
 

	
 
{% load lca2018_tags %}
 
{% load sitetree %}
 
{% load staticfiles %}
 
{% load static %}
 
{% load thumbnail %}
 
{% load i18n %}
 

	
 

	
 
{% block head_title %}Sessions{% endblock %}
 

	
 
{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %}
 

	
 
{% block header_title %}Sessions{% endblock %}
 

	
 
{% block header_paragraph %}
 

	
 
{% endblock %}
 

	
 
{% block content %}
 
  <ul class="unstyled">
 
    {% for session in sessions %}
 
      {% if session.sorted_slots %}
 
        <li>
 
          <a href="{% url 'schedule_session_detail' session.pk %}">{% blocktrans %}Session #{% endblocktrans %}{{ forloop.counter }}</a>
 
          <div class="well">
 
            <ul class="unstyled">
 
              {% for role in session.sessionrole_set.all %}
 
                <li>
 
                  <b>{{ role.get_role_display }}</b>: {{ role.user.attendee.attendeeprofilebase.attendeeprofile.name }}
 
                </li>
 
              {% empty %}
 
                <li>
 
                  <a href="{% url 'schedule_session_detail' session.pk %}">{% blocktrans %}No volunteers signed up. Sign up!{% endblocktrans %}</a>
 
                </li>
 
              {% endfor %}
 
            </ul>
 
            <h4>{% trans "Slots" %}</h4>
 
            <table class="table">
 
              {% for slot in session.sorted_slots %}
 
                <tr>
 
                  <td>{{ slot }}</td>
 
                  <td>
 
                    {% if slot.content %}<a href="{% url 'schedule_presentation_detail' slot.content.pk %}">{{ slot.content }}</a>{% endif %}
 
                  </td>
 
                </tr>
 
              {% empty %}
 
                  <tr>{% trans "No slots in session." %}</tr>
 
              {% endfor %}
 
            </table>
 
          </div>
 
        </li>
 
      {% endif %}
 
    {% empty %}
 
      <li>{% trans "No sessions defined." %}</li>
 
    {% endfor %}
 
  </ul>
 

	
 
{% endblock %}
pinaxcon/templatetags/lca2018_tags.py
Show inline comments
 
import hashlib
 

	
 
from decimal import Decimal
 
from django import template
 
from django.conf import settings
 
from django.contrib.staticfiles.templatetags import staticfiles
 
from easy_thumbnails.files import get_thumbnailer
 
from registrasion.templatetags import registrasion_tags
 
from symposion.conference import models as conference_models
 
from symposion.schedule.models import Track
 

	
 
CONFERENCE_ID = settings.CONFERENCE_ID
 
GST_RATE = settings.GST_RATE
 

	
 
register = template.Library()
 

	
 

	
 
@register.assignment_tag()
 
@register.simple_tag()
 
def classname(ob):
 
    return ob.__class__.__name__
 

	
 

	
 
@register.simple_tag(takes_context=True)
 
def can_manage(context, proposal):
 
    return proposal_permission(context, "manage", proposal)
 

	
 

	
 
@register.simple_tag(takes_context=True)
 
def can_review(context, proposal):
 
    return proposal_permission(context, "review", proposal)
 

	
 

	
 
def proposal_permission(context, permname, proposal):
 
    slug = proposal.kind.section.slug
 
    perm = "reviews.can_%s_%s" % (permname, slug)
 
    return context.request.user.has_perm(perm)
 

	
 

	
 
@register.simple_tag(takes_context=True)
 
def speaker_photo(context, speaker, size):
 
    ''' Provides the speaker profile, or else fall back to libravatar or gravatar. '''
 

	
 
    if speaker.photo:
 
        thumbnailer = get_thumbnailer(speaker.photo)
 
        thumbnail_options = {'crop': True, 'size': (size, size)}
 
        thumbnail = thumbnailer.get_thumbnail(thumbnail_options)
 
        return thumbnail.url
 
    else:
 
        email = speaker.user.email.encode("utf-8")
 
        md5sum = hashlib.md5(email.strip().lower()).hexdigest()
 
        fallback_image = ("https://linux.conf.au/site_media/static/lca2017"
 
                          "/images/speaker-fallback-devil.jpg")
 
        url = "https://secure.gravatar.com/avatar/%s?s=%d&d=%s" % (md5sum, size, "mp")
 

	
 
        return url
 

	
 

	
 
@register.simple_tag()
 
def define(value):
 
    return value
 

	
 

	
 
@register.simple_tag()
 
def presentation_bg_number(presentation, count):
 
    return sum(ord(i) for i in presentation.title) % count
 

	
 

	
 
@register.filter()
 
def gst(amount):
 
    value_no_gst = Decimal(amount / (1 + GST_RATE))
 
    return Decimal(amount - value_no_gst).quantize(Decimal('0.01'))
 

	
 

	
 
@register.simple_tag()
 
def conference_name():
 
    return conference_models.Conference.objects.get(id=CONFERENCE_ID).title
 

	
 

	
 
@register.simple_tag()
 
def conference_start_date():
 
    return conference_models.Conference.objects.get(id=CONFERENCE_ID).start_date
 

	
 

	
 
@register.simple_tag()
 
def conference_end_date():
 
    return conference_models.Conference.objects.get(id=CONFERENCE_ID).end_date
 

	
 

	
 
@register.simple_tag()
 
def conference_timezone():
 
    return conference_models.Conference.objects.get(id=CONFERENCE_ID).timezone
 

	
 

	
 
@register.filter()
 
def day_has_tracks(timetable, day):
 
    try:
 
        track_names = day.track_set.all()
 
        has_tracks = True
 
    except Track.DoesNotExist:
 
        has_tracks = False
 
    return len(track_names)
 

	
 

	
 
@register.filter()
 
def trackname(room, day):
 
    if not room:
 
        return None
 

	
 
    try:
 
        track_name = room.track_set.get(day=day).name
 
    except Track.DoesNotExist:
 
        track_name = None
 
    return track_name
 

	
 

	
 
@register.simple_tag(takes_context=True)
 
def ticket_type(context):
 

	
 
    # Default to purchased ticket type (only item from category 1)
 
    items = registrasion_tags.items_purchased(context, 1)
 

	
 
    if not items:
 
        return "NO TICKET"
 

	
 
    item = next(iter(items))
 
    name = item.product.name
 
    if name == "Conference Volunteer":
 
        return "Volunteer"
 
    elif name == "Conference Organiser":
 
        return "Organiser"
 
    else:
 
        ticket_type = name
 

	
 

	
 
    # Miniconfs are secion 2
 
    # General sessions are section 1
 

	
 
    user = registrasion_tags.user_for_context(context)
 

	
 
    if hasattr(user, "speaker_profile"):
 
        best = 0
 
        for presentation in user.speaker_profile.presentations.all():
 
            if presentation.section.id == 1:
 
                best = 1
 
            if best == 0 and presentation.section.id == 2:
 
                best = 2
 
        if best == 1:
 
            return "Speaker"
 
        elif best == 2:
 
            return "Miniconf Org"
 

	
 
    if name == "Sponsor":
 
        return "Professional"
 
    elif name == "Fairy Penguin Sponsor":
 
        return "Professional"
 
    elif name == "Monday and Tuesday Only":
 
        return "Mon/Tue Only"
 

	
 
    # Default to product type
 
    return ticket_type
pinaxcon/urls.py
Show inline comments
 
import debug_toolbar
 
from django.conf import settings
 
from django.conf.urls import include, url
 
from django.conf.urls.static import static
 
from django.views.generic import RedirectView
 
from django.views.generic import TemplateView
 
from django.urls import include, path
 
from django.contrib.flatpages.views import flatpage
 

	
 
from django.contrib import admin
 

	
 
import symposion.views
 

	
 

	
 
urlpatterns = [
 
    url(r'^saml2/', include('djangosaml2.urls')),
 
    url(r"^admin/", include(admin.site.urls)),
 
    path('saml2/', include('djangosaml2.urls')),
 
    path('admin/', admin.site.urls),
 

	
 
    url(r"^speaker/", include("symposion.speakers.urls")),
 
    url(r"^proposals/", include("symposion.proposals.urls")),
 
    url(r"^reviews/", include("symposion.reviews.urls")),
 
    url(r"^schedule/", include("symposion.schedule.urls")),
 
    url(r"^conference/", include("symposion.conference.urls")),
 
    path("speaker/", include("symposion.speakers.urls")),
 
    path("proposals/", include("symposion.proposals.urls")),
 
    path("reviews/", include("symposion.reviews.urls")),
 
    path("schedule/", include("symposion.schedule.urls")),
 
    path("conference/", include("symposion.conference.urls")),
 

	
 
    url(r"^teams/", include("symposion.teams.urls")),
 
    url(r'^raffle/', include("pinaxcon.raffle.urls")),
 
    path("teams/", include("symposion.teams.urls")),
 
    path('raffle/', include("pinaxcon.raffle.urls")),
 

	
 
    # Required by registrasion
 
    url(r'^tickets/payments/', include('registripe.urls')),
 
    url(r'^tickets/', include('registrasion.urls')),
 
    url(r'^nested_admin/', include('nested_admin.urls')),
 
    url(r'^checkin/', include('regidesk.urls')),
 
    url(r'^pages/', include('django.contrib.flatpages.urls')),
 

	
 
    url(r'^dashboard/', symposion.views.dashboard, name="dashboard"),
 
    url(r'^boardingpass', RedirectView.as_view(pattern_name="regidesk:boardingpass")),
 
]
 
    path('tickets/payments/', include('registripe.urls')),
 
    path('tickets/', include('registrasion.urls')),
 
    path('nested_admin/', include('nested_admin.urls')),
 
    path('checkin/', include('regidesk.urls')),
 
    path('pages/', include('django.contrib.flatpages.urls')),
 

	
 
    path('dashboard/', symposion.views.dashboard, name="dashboard"),
 
    path('boardingpass', RedirectView.as_view(pattern_name="regidesk:boardingpass")),
 

	
 
if settings.DEBUG:
 
    import debug_toolbar
 
    urlpatterns.insert(0, url(r'^__debug__/', include(debug_toolbar.urls)))
 
    # Debug Toolbar. Always include to ensure tests work.
 
    path('__debug__/', include(debug_toolbar.urls)),
 
]
 

	
 
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)#
...
 
\ No newline at end of file
 
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
pinaxcon/wsgi.py
Show inline comments
 
import os
 

	
 
os.environ.setdefault("DEBUG", "0")
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pinaxcon.settings")
 

	
 
from django.core.wsgi import get_wsgi_application  # noqa
 

	
 
from dj_static import Cling, MediaCling  # noqa
 

	
 
application = Cling(MediaCling(get_wsgi_application()))
 
application = get_wsgi_application()
requirements.txt
Show inline comments
 
asn1crypto==0.24.0
 
bleach==2.1.3
 
cachetools==2.1.0
 
cairocffi==0.8.1
 
CairoSVG==2.1.2
 
certifi==2018.4.16
 
cffi==1.11.5
 
chardet==3.0.4
 
coverage==4.0.3
 
cryptography==2.3
 
cssselect2==0.2.1
 
dataclasses==0.6
 
decorator==4.3.0
 
defusedxml==0.5.0
 
dj-database-url==0.4.2
 
dj-static==0.0.6
 
Django==1.11.25
 
django-appconf==1.0.1
 
Django>=2.2
 
pinax-theme-bootstrap==8.0.1
 
pinax-eventlog[django-lts]==5.1.0
 
django-formset-js==0.5.0
 
whitenoise==5.2.0
 
dj-database-url==0.5.0
 
pylibmc==1.6.1
 
django-debug-toolbar==3.1.1
 
django-bootstrap-form==3.4
 
django-settings-export~=1.2.1
 
django-capture-tag==1.0
 
django-compressor==2.3
 
django-countries==5.3.1
 
django-crispy-forms==1.7.2
 
django-debug-toolbar==1.9.1
 
django-formset-js==0.5.0
 
django-gapc-storage==0.5.1
 
django-ical==1.4
 
django-jquery-js==3.1.1
 
django-model-utils==3.1.2
 
django-nested-admin==2.2.6
 
django-nose==1.4.5
 
django-reversion==1.10.1
 
django-sass-processor==0.7.3
 
django-settings-export==1.2.1
 
django-sitetree==1.10.0
 
django-taggit==0.18.0
 
django-timezone-field==2.1
 
django-waffle==0.14.0
 
djangosaml2==0.17.2
 
easy-thumbnails==2.5
 
future==0.16.0
 
google-api-python-client==1.7.0
 
google-auth==1.5.1
 
google-auth-httplib2==0.0.3
 
html5lib==1.0.1
 
httplib2==0.11.3
 
icalendar==4.0.2
 
idna==2.7
 
jsonfield==2.0.2
 
libsass==0.19.3
 
lxml==4.0.0
 
mysqlclient==1.3.13
 
nose==1.3.7
 
oauth2client==4.1.2
 
Paste==2.0.3
 
Pillow==5.2.0
 
pinax-eventlog==1.1.1
 
pinax-stripe==3.2.1
 
pinax-theme-bootstrap==7.10.2
 
pyasn1==0.4.4
 
pyasn1-modules==0.2.2
 
pycparser==2.18
 
pycryptodomex==3.6.4
 
pylibmc==1.5.1
 
pyOpenSSL==18.0.0
 
pypng==0.0.18
 
PyQRCode==1.2.1
 
pysaml2==4.8.0
 
python-dateutil==2.7.3
 
pytz==2018.4
 
rcssmin==1.0.6
 
repoze.who==2.3
 
requests==2.19.1
 
rjsmin==1.1.0
 
rsa==3.4.2
 
six==1.11.0
 
sqlparse==0.2.4
 
static3==0.7.0
 
stripe==1.38.0
 
tinycss2==0.6.1
 
uritemplate==3.0.0
 
urllib3==1.23
 
uWSGI==2.0.17.1
 
webencodings==0.5.1
 
WebOb==1.8.2
 
zope.interface==4.5.0
 
djangosaml2==0.50.0
 
django-gapc-storage==0.5.2
 
django-waffle==2.0.0
 

	
 
# database
 
mysqlclient==2.0.1
 

	
 
# For testing
 
django-nose==1.4.7
 
coverage==5.3
 
factory_boy==3.1.0
 

	
 
# Symposion reqs
 
django-appconf==1.0.4
 
django-model-utils==4.0.0
 
django-reversion==3.0.8
 
django-sitetree==1.16.0
 
django-taggit==1.3.0
 
django-timezone-field==4.0
 
easy-thumbnails==2.7.0
 
bleach==3.2.1
 
pytz>=2020.1
 
django-ical==1.7.1
 

	
 
# Registrasion reqs
 
django-nested-admin==3.3.2
 
CairoSVG==2.4.2
 

	
 
# Registripe
 
django-countries>=6.1.3
 
pinax-stripe==4.4.0
 
requests==2.24.0
 
stripe==2.55.0
 

	
 
# SASS Compiler and template tags
 
libsass==0.20.1
 
django-sass-processor==0.8.2
 
django-compressor==2.4
 

	
 
django-crispy-forms==1.9.2
vendor/regidesk/regidesk/forms.py
Show inline comments
 
import copy
 
from regidesk import models
 

	
 
from django import forms
 
from django.core.urlresolvers import reverse
 
import functools
 

	
 
from django import forms
 
from django.core.urlresolvers import reverse
 
from django.core.exceptions import ValidationError
 
from django.db.models import F, Q
 
from django.forms import widgets
 
from django.urls import reverse
 
from django.utils import timezone
 

	
 
from django_countries import countries
 
from django_countries.fields import LazyTypedChoiceField
 
from django_countries.widgets import CountrySelectWidget
 

	
vendor/regidesk/regidesk/management/commands/print_badges.py
Show inline comments
 
#!/usr/bin/env python
 

	
 
from django.core.management.base import BaseCommand, CommandError
 

	
 
from django.contrib.auth.models import User
 
from django.contrib.auth import get_user_model
 
from registrasion.views import _convert_img as convert_img
 
from registrasion.views import render_badge_svg
 

	
 
User = get_user_model()
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 

	
 
        users = User.objects.filter(
 
            checkin__checked_in_bool=True).filter(
 
            checkin__badge_printed=False
 
        )
 

	
 
        for user in users:
 

	
 
            try:
 
                svg = render_badge_svg(user, overlay=True)
 
                rendered = convert_img(svg, outformat="pdf")
 
                with open("/app/badge_out/{}.pdf".format(user.checkin.checkin_code), "wb") as outfile:
 
                    outfile.write(rendered)
 
                user.checkin.mark_badge_printed()
 
            except Exception:
 
                pass
vendor/regidesk/regidesk/models.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
import base64
 
from datetime import datetime
 
from decimal import Decimal
 
from io import BytesIO
 

	
 
from django.core.exceptions import ValidationError
 
from django.utils import timezone
 

	
 
from django.db import models
 
from django.db.models import Q, F
 
from django.db.models import Case, When, Value
 
from django.db.models import Count
 
from django.db.models.signals import post_save
 
from django.contrib.auth.models import User
 
from django.contrib.auth import get_user_model
 
import pyqrcode
 

	
 
from symposion import constants
 
from symposion.text_parser import parse
 
from registrasion.models import commerce
 
from registrasion.util import generate_access_code as generate_code
 

	
 
User = get_user_model()
 

	
 

	
 
class BoardingPassTemplate(models.Model):
 

	
 
    label = models.CharField(max_length=100, verbose_name="Label")
 
    from_address = models.EmailField(verbose_name="From address")
 
    subject = models.CharField(max_length=100, verbose_name="Subject")
 
    body = models.TextField(verbose_name="Body")
 
    html_body = models.TextField(verbose_name="HTML Body",null=True)
 

	
 
    class Meta:
 
        verbose_name = ("Boarding Pass template")
 
        verbose_name_plural = ("Boarding Pass templates")
 

	
 
class BoardingPass(models.Model):
 

	
 
    template = models.ForeignKey(BoardingPassTemplate, null=True, blank=True,
 
                                 on_delete=models.SET_NULL, verbose_name="Template")
 
    created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
 
    sent = models.DateTimeField(null=True, verbose_name="Sent")
 
    to_address = models.EmailField(verbose_name="To address")
 
    from_address = models.EmailField(verbose_name="From address")
 
    subject = models.CharField(max_length=255, verbose_name="Subject")
 
    body = models.TextField(verbose_name="Body")
 
    html_body = models.TextField(verbose_name="HTML Body", null=True)
 

	
 
    class Meta:
 
        permissions = (
 
            ("view_boarding_pass", "Can view sent boarding passes"),
 
            ("send_boarding_pass", "Can send boarding passes"),
 
        )
 

	
 
    def __unicode__(self):
 
        return self.checkin.attendee.attendeeprofilebase.attendeeprofile.name + ' ' + self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
 

	
 
    @property
 
    def email_args(self):
 
        return (self.subject, self.body, self.from_address, self.user.email)
 

	
 
class CheckIn(models.Model):
 

	
 
    user = models.OneToOneField(User)
 
    user = models.OneToOneField(User, on_delete=models.CASCADE)
 
    boardingpass = models.OneToOneField(BoardingPass, null=True,
 
                                        blank=True, on_delete=models.SET_NULL)
 
    seen = models.DateTimeField(null=True,blank=True)
 
    checked_in = models.DateTimeField(null=True,blank=True)
 
    checkin_code = models.CharField(
 
        max_length=6,
 
        unique=True,
 
        db_index=True,
 
    )
 
    _checkin_code_png=models.TextField(max_length=512,null=True,blank=True)
 
    badge_printed = models.BooleanField(default=False)
 
    schwag_given = models.BooleanField(default=False)
 
    checked_in_bool = models.BooleanField(default=False)
 
    needs_review = models.BooleanField(default=False)
 
    review_text = models.TextField(blank=True)
 

	
 
    class Meta:
 
        permissions = (
 
            ("view_checkin_details", "Can view the details of other user's checkins"),
 
        )
 

	
 
    def save(self, *a, **k):
 
        while not self.checkin_code:
 
            checkin_code = generate_code()
 
            if CheckIn.objects.filter(checkin_code=checkin_code).count() == 0:
 
                self.checkin_code = checkin_code
 
        return super(CheckIn, self).save(*a, **k)
 

	
 
    def mark_checked_in(self):
 
        self.checked_in_bool = True
 
        self.checked_in = timezone.now()
 
        self.save()
 

	
 
    def mark_badge_printed(self):
 
        self.badge_printed = True
 
        self.save()
 

	
 
    def unset_badge(self):
 
        self.badge_printed = False
 
        self.save()
 

	
 
    def mark_schwag_given(self):
 
        self.schwag_given = True
 
        self.save()
 

	
 
    def bulk_mark_given(self):
 
        self.checked_in_bool = True
 
        self.schwag_given = True
 
        self.save()
 

	
 
    def set_exception(self, text):
 
        self.needs_review = True
 
        self.review_text = text
 
        self.save()
 

	
 
    @property
 
    def code(self):
 
        return self.checkin_code
 

	
 
    @property
 
    def qrcode(self):
 
        """Returns the QR Code for this checkin's code.
 

	
 
        If this is the first time the QR code has been generated, cache it on the object.
 
        If a code has already been cached, serve that.
 

	
 
        Returns the raw PNG blob, unless b64=True, in which case the return value
 
        is the base64encoded PNG blob."""
 

	
 
        if not self.code:
 
            return None
 
        if not self._checkin_code_png:
 
            qrcode = pyqrcode.create(self.code)
 
            qr_io = BytesIO()
 
            qrcode.png(qr_io, scale=6)
 
            qr_io.seek(0)
 
            self._checkin_code_png = base64.b64encode(qr_io.read()).decode('UTF-8')
 
            self.save()
 

	
 
        return self._checkin_code_png
vendor/regidesk/regidesk/templates/regidesk/base.html
Show inline comments
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load static %}
 

	
 
{% load i18n %}
 

	
 
{% block head_title %}Checkin{% endblock head_title %}
 

	
 
{% block content %}
 
{% block body %}
 
{% endblock %}
 
{% endblock %}
 

	
 
{% block extra_script %}
 
<link rel="stylesheet" type="text/css" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" />
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/pdfmake.min.js"></script>
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/vfs_fonts.js"></script>
 
<script type="text/javascript" src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js  "></script>
 
<script type="text/javascript">
 
  $("table.table-data").dataTable({
 
    "dom": "<'row'<'col-md-3'l><'col-md-3'B><'col-md-4'f>r>t<'row'<'col-md-3'i><'col-md-5'p>>",
 
    "stateSave": true,
 
    "lengthMenu": [[10, 50, 100, -1], [10, 50, 100, "All"]],
 
    "pageLength": 100,
 
    "colReorder": true,
 
    "buttons": [{
 
      extend: 'collection',
 
      text: 'Export',
 
      buttons: ["copy", "csv", "print"]
 
    },
 
    {
 
      extend: 'collection',
 
      text: 'Columns',
 
      buttons: [
 
        {
 
          extend: 'columnsToggle',
 
          columns: '.toggle'
 
        },
 
      ]
 
    }]
 
  });
 
</script>
 
{% endblock %}
...
 
\ No newline at end of file
 
{% endblock %}
vendor/regidesk/regidesk/views.py
Show inline comments
 
import base64
 
import logging
 
from datetime import datetime
 
from email.mime.image import MIMEImage
 

	
 
from django.core.exceptions import ValidationError
 
from django.core.mail import EmailMultiAlternatives
 
from django.conf import settings
 
from django.contrib import messages
 
from django.contrib.auth.decorators import permission_required, user_passes_test, login_required
 
from django.contrib.auth.mixins import PermissionRequiredMixin
 
from django.contrib.auth.models import User, Group
 
from django.contrib.auth.models import Group
 
from django.contrib.auth import get_user_model
 
from django.contrib.sites.models import Site
 
from django.db import transaction
 
from django.db.models import F, Q
 
from django.db.models import Count, Max, Sum
 
from django.http import Http404
 
from django.http import HttpResponse, HttpResponseBadRequest
 
from django.shortcuts import redirect, render
 
from django.template import Template, Context
 
from django.urls import reverse
 
from django.views.decorators.csrf import csrf_exempt
 
from django.views.generic import TemplateView
 

	
 
from registrasion import util
 
from registrasion.views import render_badge
 
from registrasion.models import commerce, people
 
from registrasion.templatetags.registrasion_tags import items_purchased, items_pending
 
from registrasion.templatetags.registrasion_tags import invoices, missing_categories
 
from symposion.conference.models import Conference
 

	
 
from regidesk import forms
 
from regidesk.models import BoardingPass, BoardingPassTemplate, CheckIn
 

	
 

	
 

	
 
User = get_user_model()
 
AttendeeProfile = util.get_object_from_name(settings.ATTENDEE_PROFILE_MODEL)
 

	
 

	
 
def _staff_only(user):
 
    ''' Returns true if the user is staff. '''
 
    return user.is_staff
 

	
 
@login_required
 
def boardingpass(request):
 

	
 
    user=request.user
 
    checkin = CheckIn.objects.get_or_create(user=user)[0]
 
    if not checkin.boardingpass:
 
        templates = BoardingPassTemplate.objects.all()
 
        if not templates:
 
            messages.add_message(request, messages.WARNING,
 
                                 'Your boarding pass has not been prepared and I can\'t find a '
 
                                 'default template to use. This page has similar information to '
 
                                 'the boarding pass - please check back later.')
 
            return redirect('/tickets/review')
 
        prepare_boarding_pass(request.user, templates[0])
 
        checkin = CheckIn.objects.get_or_create(user=user)[0]
 

	
 
    boardingpass = checkin.boardingpass
 
    qrcode_url = request.build_absolute_uri(reverse("regidesk:checkin_png", args=[checkin.code]))
 
    qrcode = checkin.qrcode
 
    qrcode_string ='<img src="data:image/png;base64,' + qrcode + '"/>'
 
    not_qrcode_string = '<img src="cid:qrcode.png"/>'
 

	
 
    boardingpass_body = boardingpass.html_body.replace(not_qrcode_string, qrcode_string)
 
    ctx = { 'attendee': user.attendee,
 
            'boardingpass_body': boardingpass_body,
 
            'boardingpass': boardingpass
 
    }
 

	
 
    response = render(request, "regidesk/boardingpass.html", ctx)
 
    return response
 

	
 

	
 
@permission_required("regidesk.view_boarding_pass")
 
def boarding_overview(request, boarding_state="pending"):
 

	
 
    tickets = commerce.LineItem.objects.select_related(
 
        "invoice","invoice__user__attendee","product__category"
 
    ).filter(
 
        invoice__status=commerce.Invoice.STATUS_PAID,
 
        product__category=settings.TICKET_PRODUCT_CATEGORY,
 
        price__gte=0
 
    )
 

	
 
    print(datetime.now())
 
    ticketholders = ( ticket.invoice.user for ticket in tickets )
 
    print(datetime.now())
 

	
 
    attendees = people.Attendee.objects.select_related(
 
            "attendeeprofilebase",
 
            "attendeeprofilebase__attendeeprofile",
 
            "user",
 
            "user__checkin"
 
        ).filter(user__in=ticketholders)
 

	
 
    profiles = AttendeeProfile.objects.filter(
 
        attendee__in=attendees
 
    ).select_related(
 
        "attendee", "attendee__user",
 
    )
 
    profiles_by_attendee = dict((i.attendee, i) for i in profiles)
 

	
 
    bp_templates = BoardingPassTemplate.objects.all()
 

	
 
    ctx = {
 
        "boarding_state": boarding_state,
 
        "attendees": attendees,
 
        "profiles": profiles_by_attendee,
 
        "templates": bp_templates,
 
    }
 

	
 
    return render(request, "regidesk/boardingpass_overview.html", ctx)
 

	
 
def checkin_png(request, checkin_code):
 

	
 
    checkin = CheckIn.objects.get(checkin_code=checkin_code)
 
    if not checkin:
 
        raise Http404()
 

	
 
    response = HttpResponse()
 
    response["Content-Type"] = "image/png"
 
    response["Content-Disposition"] = 'inline; filename="qrcode.png"'
 

	
 
    qrcode = base64.b64decode(checkin.qrcode)
 
    response.write(qrcode)
 

	
 
    return response
 

	
 

	
 
def checkin_qrcode_url(code):
 
    """ Generate the QR code URL for the current site.
 
    eg. https://rego.linux.conf.au/checkin/<code>.png
 
    """
 

	
 
    # Cannot use build_absolute_uri as external URL is not known in docker
 
    # qrcode_url = request.build_absolute_uri(
 
    #     reverse("regidesk:checkin_png", args=[code]))
 

	
 
    current_site = Site.objects.get_current()
 
    qrcode_url = "https://{}{}".format(current_site,
 
        reverse("regidesk:checkin_png", args=[code]))
 
    return qrcode_url
 

	
 

	
 
@permission_required("regidesk.send_boarding_pass")
 
def boarding_prepare(request):
 

	
 
    attendee_pks = []
 
    try:
 
        for pk in request.POST.getlist("_selected_action"):
 
            attendee_pks.append(int(pk))
 
    except ValueError:
 
        messages.warning(request, ValueError)
 
        return redirect("/checkin/overview")
 
    attendees = people.Attendee.objects.filter(pk__in=attendee_pks)
 
    attendees = attendees.select_related(
 
        "user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile")
 

	
 
    try:
 
        sample_checkin = CheckIn.objects.get_or_create(user=attendees[0].user)[0]
 
    except:
 
        messages.warning(request, "Couldn't find a sample checking - did you add a user?")
 
        return redirect("/checkin/overview")
 

	
 
    rendered_template = {}
 
    sample_ctx = {}
 

	
 
    bp_template_pk = request.POST.get("template", "")
 
    if not bp_template_pk:
 
        messages.warning(request, "Need to choose a template")
 
        return redirect("/checkin/overview")
 

	
 
    bp_template = BoardingPassTemplate.objects.get(pk=bp_template_pk)
 
    if bp_template:
 
        sample_ctx = {
 
            "user": sample_checkin.user,
 
            "boardingpass": sample_checkin.boardingpass,
 
            "code": sample_checkin.code,
 
            "qrcode": '<img src="data:image/png;base64,' + sample_checkin.qrcode + '"/>',
 
            "qrcode_url": checkin_qrcode_url(sample_checkin.code),
 
        }
 
        ctx = Context(sample_ctx)
 
        ctx["invoices"] = invoices(ctx)
 
        ctx["items_pending"] = items_pending(ctx)
 
        ctx["items_purchased"] = items_purchased(ctx)
 
        ctx["missing_categories"] = missing_categories(ctx)
 

	
 
        subject = Template(bp_template.subject).render(ctx)
 
        rendered_template['plain'] = Template(bp_template.body).render(ctx)
 
        rendered_template['html'] = Template(bp_template.html_body).render(ctx)
 
        request.session['template'] = bp_template.pk
 
    else:
 
        subject = None
 
        request.session['template'] = None
 

	
 
    ctx = {
 
        "attendees": attendees,
 
        "template": bp_template,
 
        "attendee_pks": attendee_pks,
 
        "rendered_template": rendered_template,
 
        "subject": subject,
 
        "sample": sample_ctx,
 
    }
 

	
 
    request.session.set_expiry=(300)
 
    request.session['boarding_attendees'] = attendee_pks
 
    return render(request, "regidesk/boardingpass_prepare.html", ctx)
 

	
 
def prepare_boarding_pass(user, template, attendee=None):
 

	
 
    if attendee:
 
        user = attendee.user
 
    else:
 
        user = user
 
        attendee=user.attendee
 
    checkin = CheckIn.objects.get_or_create(user=user)
 
    ctx = {
 
        "user": user,
 
        "checkin": user.checkin,
 
        "code": user.checkin.code,
 
        "qrcode": user.checkin.qrcode,
 
        "qrcode_url": checkin_qrcode_url(user.checkin.code),
 
    }
 
    ctx = Context(ctx)
 
    ctx["invoices"] = invoices(ctx)
 
    ctx["items_pending"] = items_pending(ctx)
 
    ctx["items_purchased"] = items_purchased(ctx)
 
    ctx["missing_categories"] = missing_categories(ctx)
 

	
 
    subject = Template(template.subject).render(ctx)
 
    body = Template(template.body).render(ctx)
 
    if template.html_body:
 
        html_body = Template(template.html_body).render(ctx)
 
    else:
 
        html_body = None
 

	
 
    bpass = BoardingPass(template=template, to_address=user.email,
 
                         from_address=template.from_address,
 
                         subject=subject, body=body,
 
                         html_body=html_body
 
    )
 
    bpass.save()
 

	
 
    if user.checkin.boardingpass:
 
        user.checkin.boardingpass.delete()
 
    user.checkin.boardingpass = bpass
 
    user.checkin.save()
 

	
 
    return bpass
 

	
 
def send_boarding_pass(bpass, user):
 
    msg = EmailMultiAlternatives(
 
        bpass.subject,
 
        bpass.body,
 
        bpass.from_address,
 
        [bpass.to_address,],
 
    )
 
    msg.content_subtype="plain"
 
    msg.mixed_subtype="related"
 
    if bpass.html_body:
 
        msg.attach_alternative(bpass.html_body, "text/html")
 
        qrcode = base64.b64decode(user.checkin.qrcode)
 
        msg.attach(filename="qrcode.png", content=qrcode, mimetype="image/png")
 

	
 
    msg.send()
 
    bpass.sent = datetime.now()
 
    bpass.save()
 

	
 
@permission_required("regidesk.send_boarding_pass")
 
def boarding_send(request):
 

	
 
    BOARDING_GROUP = getattr(settings, "REGIDESK_BOARDING_GROUP", None)
 
    if BOARDING_GROUP and Group.objects.filter(name=BOARDING_GROUP):
 
        boarding_users = User.objects.filter(groups__name=BOARDING_GROUP)
 
    else:
 
        boarding_users = User.objects.all()
 

	
 
    attendees = people.Attendee.objects.filter(pk__in=request.session['boarding_attendees'])
 
    attendees = attendees.select_related(
 
                "user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile")
 

	
 
    logging.debug(len(attendees))
 
    logging.debug(attendees)
 

	
 
    template_pk = request.session['template']
 
    template = BoardingPassTemplate.objects.get(pk=template_pk)
 

	
 
    for attendee in attendees:
 
        try:
 
            user = attendee.user
 
            bpass = prepare_boarding_pass(user, template, attendee)
 
            if user in boarding_users:
 
                send_boarding_pass(bpass, user)
 
                messages.success(request, "Sent boarding pass to %s" % attendee)
 
        except Exception as err:
 
            logging.error("Error sending boarding pass for %s: %s", (attendee, err))
 
            messages.warning(request, "Could not send boarding pass to %s" % attendee)
 

	
 
        request.session['boarding_attendees'].remove(attendee.pk)
 

	
 
    return redirect("regidesk:boarding_overview")
 

	
 

	
 
class CheckInLanding(PermissionRequiredMixin, TemplateView):
 
    template_name = 'regidesk/ci_landing.html'
 
    permission_required = 'regidesk.view_boarding_pass'
 

	
 

	
 
@csrf_exempt
 
@permission_required("regidesk.view_boarding_pass")
 
def check_in_overview(request, access_code):
 
    check_in = CheckIn.objects.filter(
 
        checkin_code=access_code,
 
    )
 
    if not check_in:
 
        # yea it's a 200...
 
        return render(request, "regidesk/ci_code_404.html", {})
 
    check_in = check_in[0]
 
    if request.method == 'POST':
 
        if 'checkin' in request.POST:
 
            check_in.mark_checked_in()
 
        elif 'badge' in request.POST:
 
            check_in.mark_badge_printed()
 
        elif 'schwag' in request.POST:
 
            check_in.mark_schwag_given()
 
        elif 'bulk' in request.POST:
 
            check_in.bulk_mark_given()
 
        elif 'exception' in request.POST:
 
            check_in.set_exception(request.POST['exception'])
 
        elif 'unbadge' in request.POST:
 
            check_in.unset_badge()
 
        return redirect(request.path)
 
    ctx = {
 
        'check_in': check_in,
 
        'user': check_in.user,
 
    }
 
    return render(request, "regidesk/ci_overview.html", ctx)
 

	
 

	
 
@permission_required("regidesk.view_boarding_pass")
 
def checken_in_badge(request, access_code):
 
    check_in = CheckIn.objects.filter(
 
        checkin_code=access_code,
 
    )
 
    if not check_in:
 
        # yea it's a 200...
 
        return render(request, "regidesk/ci_code_404.html", {})
 
    badge = render_badge(check_in[0].user, format="svg", overlay=True)
 
    return badge
 

	
 
@login_required
 
def redir_main(request):
 
    if request.user.has_perm('regidesk.view_boarding_pass'):
 
        return redirect(reverse('regidesk:boarding_overview'))
 
    return redirect(reverse('regidesk:boardingpass'))
vendor/regidesk/requirements.txt
Show inline comments
 
django-countries>=4.0
 
requests>=2.11.1
 
django-countries>=6.1.3
 
requests>=2.24.0
 
pypng
 
pyqrcode
vendor/registrasion/registrasion/admin.py
Show inline comments
 
from django.contrib import admin
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.utils.translation import ugettext_lazy as _
 

	
 
import nested_admin
 

	
 
from registrasion.models import conditions
 
from registrasion.models import inventory
 

	
 

	
 
class EffectsDisplayMixin(object):
 
    def effects(self, obj):
 
        return list(obj.effects())
 

	
 

	
 
# Inventory admin
 

	
 

	
 
class ProductInline(admin.TabularInline):
 
    model = inventory.Product
 

	
 

	
 
@admin.register(inventory.Category)
 
class CategoryAdmin(admin.ModelAdmin):
 
    model = inventory.Category
 
    fields = ("name", "description", "required", "render_type",
 
              "limit_per_user", "order",)
 
    list_display = ("name", "description")
 
    inlines = [
 
        ProductInline,
 
    ]
 

	
 

	
 
@admin.register(inventory.Product)
 
class ProductAdmin(admin.ModelAdmin):
 
    model = inventory.Product
 
    list_display = ("name", "category", "description")
 
    list_filter = ("category", )
 

	
 

	
 
# Discounts
 

	
 
class DiscountForProductInline(admin.TabularInline):
 
    model = conditions.DiscountForProduct
 
    verbose_name = _("Product included in discount")
 
    verbose_name_plural = _("Products included in discount")
 
    sortable_options = []
 

	
 

	
 
class DiscountForCategoryInline(admin.TabularInline):
 
    model = conditions.DiscountForCategory
 
    verbose_name = _("Category included in discount")
 
    verbose_name_plural = _("Categories included in discount")
 
    sortable_options = []
 

	
 

	
 
@admin.register(conditions.TimeOrStockLimitDiscount)
 
class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 
    list_display = (
 
        "description",
 
        "start_time",
 
        "end_time",
 
        "limit",
 
        "effects",
 
    )
 
    ordering = ("start_time", "end_time", "limit")
 

	
 
    inlines = [
 
        DiscountForProductInline,
 
        DiscountForCategoryInline,
 
    ]
 

	
 

	
 
@admin.register(conditions.IncludedProductDiscount)
 
class IncludedProductDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 

	
 
    def enablers(self, obj):
 
        return list(obj.enabling_products.all())
 

	
 
    list_display = ("description", "enablers", "effects")
 

	
 
    inlines = [
 
        DiscountForProductInline,
 
        DiscountForCategoryInline,
 
    ]
 

	
 

	
 
@admin.register(conditions.SpeakerDiscount)
 
class SpeakerDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 

	
 
    fields = ("description", "is_presenter", "is_copresenter", "proposal_kind")
 

	
 
    list_display = ("description", "is_presenter", "is_copresenter", "effects")
 

	
 
    ordering = ("-is_presenter", "-is_copresenter")
 

	
 
    inlines = [
 
        DiscountForProductInline,
 
        DiscountForCategoryInline,
 
    ]
 

	
 

	
 
@admin.register(conditions.GroupMemberDiscount)
 
class GroupMemberDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 

	
 
    fields = ("description", "group")
 

	
 
    list_display = ("description", "effects")
 

	
 
    inlines = [
 
        DiscountForProductInline,
 
        DiscountForCategoryInline,
 
    ]
 

	
 

	
 
# Vouchers
 

	
 
class VoucherDiscountInline(nested_admin.NestedStackedInline):
 
    model = conditions.VoucherDiscount
 
    verbose_name = _("Discount")
 

	
 
    # TODO work out why we're allowed to add more than one?
 
    max_num = 1
 
    extra = 1
 
    inlines = [
 
        DiscountForProductInline,
 
        DiscountForCategoryInline,
 
    ]
 

	
 

	
 
class VoucherFlagInline(nested_admin.NestedStackedInline):
 
    model = conditions.VoucherFlag
 
    verbose_name = _("Product and category enabled by voucher")
 
    verbose_name_plural = _("Products and categories enabled by voucher")
 

	
 
    # TODO work out why we're allowed to add more than one?
 
    max_num = 1
 
    extra = 1
 

	
 

	
 
@admin.register(inventory.Voucher)
 
class VoucherAdmin(nested_admin.NestedAdmin):
 
class VoucherAdmin(nested_admin.NestedModelAdmin):
 

	
 
    def effects(self, obj):
 
        ''' List the effects of the voucher in the admin. '''
 
        out = []
 

	
 
        try:
 
            voucher_condition = obj.voucherflag.condition
 
        except ObjectDoesNotExist:
 
            voucher_condition = None
 

	
 
        try:
 
            discount_effects = obj.voucherdiscount.effects()
 
        except ObjectDoesNotExist:
 
            discount_effects = None
 

	
 
        try:
 
            enabling_effects = obj.voucherflag.effects()
 
        except ObjectDoesNotExist:
 
            enabling_effects = None
 

	
 
        if discount_effects:
 
            out.append("Discounts: " + str(list(discount_effects)))
 
        if voucher_condition:
 
            out.append("Condition: " + obj.voucherflag.get_condition_display())
 
        if enabling_effects:
 
            out.append("Enables: " + str(list(enabling_effects)))
 

	
 
        return "\n".join(out)
 

	
 
    model = inventory.Voucher
 
    list_display = ("recipient", "code", "effects")
 
    inlines = [
 
        VoucherDiscountInline,
 
        VoucherFlagInline,
 
    ]
 

	
 

	
 
# Enabling conditions
 
@admin.register(conditions.ProductFlag)
 
class ProductFlagAdmin(
 
        nested_admin.NestedAdmin,
 
        nested_admin.NestedModelAdmin,
 
        EffectsDisplayMixin):
 

	
 
    def enablers(self, obj):
 
        return list(obj.enabling_products.all())
 

	
 
    model = conditions.ProductFlag
 
    fields = ("description", "enabling_products", "condition", "products",
 
              "categories"),
 

	
 
    list_display = ("description", "condition", "enablers", "effects")
 

	
 

	
 
# Enabling conditions
 
@admin.register(conditions.CategoryFlag)
 
class CategoryFlagAdmin(
 
        nested_admin.NestedAdmin,
 
        nested_admin.NestedModelAdmin,
 
        EffectsDisplayMixin):
 

	
 
    model = conditions.CategoryFlag
 
    fields = ("description", "enabling_category", "condition", "products",
 
              "categories"),
 

	
 
    list_display = ("description", "condition", "enabling_category", "effects")
 
    ordering = ("enabling_category",)
 

	
 

	
 
@admin.register(conditions.SpeakerFlag)
 
class SpeakerFlagAdmin(nested_admin.NestedAdmin, EffectsDisplayMixin):
 
class SpeakerFlagAdmin(nested_admin.NestedModelAdmin, EffectsDisplayMixin):
 

	
 
    model = conditions.SpeakerFlag
 
    fields = ("description", "is_presenter", "is_copresenter", "proposal_kind",
 
              "products", "categories")
 

	
 
    list_display = ("description", "is_presenter", "is_copresenter", "effects")
 

	
 
    ordering = ("-is_presenter", "-is_copresenter")
 

	
 

	
 
@admin.register(conditions.GroupMemberFlag)
 
class GroupMemberFlagAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 

	
 
    fields = ("description", "group", "products", "categories")
 

	
 
    list_display = ("description", "condition", "effects")
 

	
 

	
 
@admin.register(conditions.TimeOrStockLimitFlag)
 
class TimeOrStockLimitFlagAdmin(admin.ModelAdmin, EffectsDisplayMixin):
 
    list_display = (
 
        "description",
 
        "condition",
 
        "start_time",
 
        "end_time",
 
        "limit",
 
        "effects",
 
    )
 
    ordering = ("start_time", "end_time", "limit")
vendor/registrasion/registrasion/contrib/badger.py
Show inline comments
 
'''
 
Generate Conference Badges
 
==========================
 

	
 
Nearly all of the code in this was written by Richard Jones for the 2016 conference.
 
That code relied on the user supplying the attendee data in a CSV file, which Richard's
 
code then processed.
 

	
 
The main (and perhaps only real) difference, here, is that the attendee data are taken
 
directly from the database.  No CSV file is required.
 

	
 
This is now a library with functions / classes referenced by the generate_badges
 
management command, and by the tickets/badger and tickets/badge API functions.
 
'''
 
import sys
 
import os
 
import csv
 
from lxml import etree
 
import tempfile
 
from copy import deepcopy
 
import subprocess
 

	
 
import pdb
 

	
 
from django.core.management.base import BaseCommand
 

	
 
from django.contrib.auth.models import User, Group
 
from django.contrib.auth.models import Group
 
from django.contrib.auth import get_user_model
 
from django.db.utils import OperationalError, ProgrammingError
 
from pinaxcon.registrasion.models import AttendeeProfile
 
from registrasion.controllers.cart import CartController
 
from registrasion.controllers.invoice import InvoiceController
 
from registrasion.models import Voucher
 
from registrasion.models import Attendee
 
from registrasion.models import Product
 
from registrasion.models import Invoice
 
from symposion.speakers.models import Speaker
 

	
 
User = get_user_model()
 

	
 
# A few unicode encodings ...
 
GLYPH_PLUS = '+'
 
GLYPH_GLASS = u'\ue001'
 
GLYPH_DINNER = u'\ue179'
 
GLYPH_SPEAKER = u'\ue122'
 
GLYPH_SPRINTS = u'\ue254'
 
GLYPH_CROWN = u'\ue211'
 
GLYPH_SNOWMAN = u'\u2603'
 
GLYPH_STAR = u'\ue007'
 
GLYPH_FLASH = u'\ue162'
 
GLYPH_EDU = u'\ue233'
 

	
 
# Some company names are too long to fit on the badge, so, we
 
# define abbreviations here.
 
overrides = {
 
 "Optiver Pty. Ltd.": "Optiver",
 
 "IRESS Market Tech": "IRESS",
 
 "The Bureau of Meteorology": "BoM",
 
 "Google Australia": "Google",
 
 "Facebook Inc.": "Facebook",
 
 "Rhapsody Solutions Pty Ltd": "Rhapsody Solutions",
 
 "PivotNine Pty Ltd": "PivotNine",
 
 "SEEK Ltd.": "SEEK",
 
 "UNSW Australia": "UNSW",
 
 "Dev Demand Co": "Dev Demand",
 
 "Cascode Labs Pty Ltd": "Cascode Labs",
 
 "CyberHound Pty Ltd": "CyberHound",
 
 "Self employed Contractor": "",
 
 "Data Processors Pty Lmt": "Data Processors",
 
 "Bureau of Meterology": "BoM",
 
 "Google Australia Pty Ltd": "Google",
 
 # "NSW Rural Doctors Network": "",
 
 "Sense of Security Pty Ltd": "Sense of Security",
 
 "Hewlett Packard Enterprose": "HPE",
 
 "Hewlett Packard Enterprise": "HPE",
 
 "CISCO SYSTEMS INDIA PVT LTD": "CISCO",
 
 "The University of Melbourne": "University of Melbourne",
 
 "Peter MacCallum Cancer Centre": "Peter Mac",
 
 "Commonwealth Bank of Australia": "CBA",
 
 "VLSCI, University of Melbourne": "VLSCI",
 
 "Australian Bureau of Meteorology": "BoM",
 
 "Bureau of Meteorology": "BoM",
 
 "Australian Synchrotron | ANSTO": "Australian Synchrotron",
 
 "Bureau of Meteorology, Australia": "BoM",
 
 "QUT Digital Media Research Centre": "QUT",
 
 "Dyn - Dynamic Network Services Inc": "Dyn",
 
 "The Australian National University": "ANU",
 
 "Murdoch Childrens Research Institute": "MCRI",
 
 "Centenary Institute, University of Sydney": "Centenary Institute",
 
 "Synchrotron Light Source Australia Pty Ltd": "Australian Synchrotron",
 
 "Australian Communication and Media Authority": "ACMA",
 
 "Dept. of Education - Camden Haven High School": "Camden Haven High School",
 
 "Australian Government - Bureau of Meteorology": "BoM",
 
 "The Walter and Eliza Hall Institute of Medical Research": "WEHI",
 
 "Dept. Parliamentary Services, Australian Parliamentary Library": "Dept. Parliamentary Services",
 
}
 

	
 

	
 
def text_size(text, prev=9999):
 
    '''
 
    Calculate the length of a text string as it relates to font size.
 
    '''
 
    n = len(text)
 
    size = int(min(48, max(28, 28 + 30 * (1 - (n-8) / 11.))))
 
    return min(prev, size)
 

	
 

	
 
def set_text(soup, text_id, text, resize=None):
 
    '''
 
    Set the text value of an element (via beautiful soup calls).
 
    '''
 
    elem = soup.find(".//*[@id='%s']/{http://www.w3.org/2000/svg}tspan" % text_id)
 
    if elem is None:
 
        raise ValueError('could not find tag id=%s' % text_id)
 
    elem.text = text
 
    if resize:
 
        style = elem.get('style')
 
        elem.set('style', style.replace('font-size:60px', 'font-size:%dpx' % resize))
 

	
 

	
 
def set_colour(soup, slice_id, colour):
 
    '''
 
    Set colour of an element (using beautiful soup calls).
 
    '''
 
    elem = soup.find(".//*[@id='%s']" % slice_id)
 
    if elem is None:
 
        raise ValueError('could not find tag id=%s' % slice_id)
 
    style = elem.get('style')
 
    elem.set('style', style.replace('fill:#316a9a', 'fill:#%s' % colour))
 

	
 
## It's possible that this script will be run before the database has been populated
 
try:
 
    Volunteers = Group.objects.filter(name='Conference volunteers').first().user_set.all()
 
    Organisers = Group.objects.filter(name='Conference organisers').first().user_set.all()
 
except (OperationalError, AttributeError, ProgrammingError):
 
    Volunteers = []
 
    Organisers = []
 

	
 
def is_volunteer(attendee):
 
    '''
 
    Returns True if attendee is in the Conference volunteers group.
 
    False otherwise.
 
    '''
 
    return attendee.user in Volunteers
 

	
 
def is_organiser(attendee):
 
    '''
 
    Returns True if attendee is in the Conference volunteers group.
 
    False otherwise.
 
    '''
 
    return attendee.user in Organisers
 

	
 

	
 
def svg_badge(soup, data, n):
 
    '''
 
    Do the actual "heavy lifting" to create the badge SVG
 
    '''
 

	
 
    # Python2/3 compat ...
 
    try:
 
        xx = filter(None, [1, 2, None, 3])[2]
 
        filter_None = lambda lst: filter(None, lst)
 
    except (TypeError,):
 
        filter_None = lambda lst: list(filter(None, lst))
 

	
 
    side = 'lr'[n]
 
    for tb in 'tb':
 
        part = tb + side
 
        lines = [data['firstname'], data['lastname']]
 
        if data['promote_company']:
 
            lines.append(data['company'])
 
        lines.extend([data['line1'], data['line2']])
 
        lines = filter_None(lines)[:4]
 

	
 
        lines.extend('' for n in range(4-len(lines)))
 
        prev = 9999
 
        for m, line in enumerate(lines):
 
            size = text_size(line, prev)
 
            set_text(soup, 'line-%s-%s' % (part, m), line, size)
 
            prev = size
 

	
 
        lines = []
 
        if data['organiser']:
 
            lines.append('Organiser')
 
            set_colour(soup, 'colour-' + part, '319a51')
 
        elif data['volunteer']:
 
            lines.append('Volunteer')
 
            set_colour(soup, 'colour-' + part, '319a51')
 
        if data['speaker']:
 
            lines.append('Speaker')
 

	
 
        special = bool(lines)
 

	
 
        if 'Friday Only' in data['ticket']:
 
            # lines.append('Friday Only')
 
            set_colour(soup, 'colour-' + part, 'a83f3f')
 

	
 
        if 'Contributor' in data['ticket']:
 
            lines.append('Contributor')
 
        elif 'Professional' in data['ticket'] and not data['organiser']:
 
            lines.append('Professional')
 
        elif 'Sponsor' in data['ticket'] and not data['organiser']:
 
            lines.append('Sponsor')
 
        elif 'Enthusiast' in data['ticket'] and not data['organiser']:
 
            lines.append('Enthusiast')
 
        elif data['ticket'] == 'Speaker' and not data['speaker']:
 
            lines.append('Speaker')
 
        elif not special:
 
            if data['ticket']:
 
                lines.append(data['ticket'])
 
            elif data['friday']:
 
                lines.append('Friday Only')
 
                set_colour(soup, 'colour-' + part, 'a83f3f')
 
            else:
 
                lines.append('Tutorial Only')
 
                set_colour(soup, 'colour-' + part, 'a83f3f')
 

	
 
        if data['friday'] and data['ticket'] and not data['organiser']:
 
            lines.append('Fri, Sat and Sun')
 
            if not data['volunteer']:
 
                set_colour(soup, 'colour-' + part, '71319a')
 

	
 
        if len(lines) > 3:
 
            raise ValueError('lines = %s' % (lines,))
 

	
 
        for n in range(3 - len(lines)):
 
            lines.insert(0, '')
 
        for m, line in enumerate(lines):
 
            size = text_size(line)
 
            set_text(soup, 'tags-%s-%s' % (part, m), line, size)
 

	
 
        icons = []
 
        if data['sprints']:
 
            icons.append(GLYPH_SPRINTS)
 
        if data['tutorial']:
 
            icons.append(GLYPH_EDU)
 

	
 
        set_text(soup, 'icons-' + part, ' '.join(icons))
 
        set_text(soup, 'shirt-' + side, '; '.join(data['shirts']))
 
        set_text(soup, 'email-' + side, data['email'])
 

	
 

	
 
def collate(options):
 
    # If specific usernames were given on the command line, just use those.
 
    # Otherwise, use the entire list of attendees.
 
    users = User.objects.filter(invoice__status=Invoice.STATUS_PAID)
 
    if options['usernames']:
 
        users = users.filter(username__in=options['usernames'])
 

	
 
    # Iterate through the attendee list to generate the badges.
 
    for n, user in enumerate(users.distinct()):
 
        ap = user.attendee.attendeeprofilebase.attendeeprofile
 
        data = dict()
 

	
 
        at_nm = ap.name.split()
 
        if at_nm[0].lower() in 'mr dr ms mrs miss'.split():
 
            at_nm[0] = at_nm[0] + ' ' + at_nm[1]
 
            del at_nm[1]
 
        if at_nm:
 
            data['firstname'] = at_nm[0]
 
            data['lastname'] = ' '.join(at_nm[1:])
 
        else:
 
            print ("ERROR:", ap.attendee.user, 'has no name')
 
            continue
 

	
 
        data['line1'] = ap.free_text_1
 
        data['line2'] = ap.free_text_2
 

	
 
        data['email'] = user.email
 
        data['over18'] = ap.of_legal_age
 
        speaker = Speaker.objects.filter(user=user).first()
 
        if speaker is None:
 
            data['speaker'] = False
 
        else:
 
            data['speaker'] = bool(speaker.proposals.filter(result__status='accepted'))
 

	
 
        data['paid'] = data['friday'] = data['sprints'] = data['tutorial'] = False
 
        data['shirts'] = []
 
        data['ticket'] = ''
 

	
 
        # look over all the invoices, yes
 
        for inv in Invoice.objects.filter(user_id=ap.attendee.user.id):
 
            if not inv.is_paid:
 
                continue
 
            cart = inv.cart
 
            if cart is None:
 
                continue
 
            data['paid'] = True
 
            if cart.productitem_set.filter(product__category__name__startswith="Specialist Day").exists():
 
                data['friday'] = True
 
            if cart.productitem_set.filter(product__category__name__startswith="Sprint Ticket").exists():
 
                data['sprints'] = True
 
            if cart.productitem_set.filter(product__category__name__contains="Tutorial").exists():
 
                data['tutorial'] = True
 
            t = cart.productitem_set.filter(product__category__name__startswith="Conference Ticket")
 
            if t.exists():
 
                product = t.first().product.name
 
                if 'SOLD OUT' not in product:
 
                    data['ticket'] = product
 
            elif cart.productitem_set.filter(product__category__name__contains="Specialist Day Only").exists():
 
                data['ticket'] = 'Specialist Day Only'
 

	
 
            data['shirts'].extend(ts.product.name for ts in cart.productitem_set.filter(
 
                product__category__name__startswith="T-Shirt"))
 

	
 
        if not data['paid']:
 
            print ("INFO:", ap.attendee.user, 'not paid!')
 
            continue
 

	
 
        if not data['ticket'] and not (data['friday'] or data['tutorial']):
 
            print ("ERROR:", ap.attendee.user, 'no conference ticket!')
 
            continue
 

	
 
        data['company'] = overrides.get(ap.company, ap.company).strip()
 

	
 
        data['volunteer'] = is_volunteer(ap.attendee)
 
        data['organiser'] = is_organiser(ap.attendee)
 

	
 
        if 'Specialist Day Only' in data['ticket']:
 
            data['ticket'] = 'Friday Only'
 

	
 
        if 'Conference Organiser' in data['ticket']:
 
            data['ticket'] = ''
 

	
 
        if 'Conference Volunteer' in data['ticket']:
 
            data['ticket'] = ''
 

	
 
        data['promote_company'] = (
 
            data['organiser'] or data['volunteer'] or data['speaker'] or
 
            'Sponsor' in data['ticket'] or
 
            'Contributor' in data['ticket'] or
 
            'Professional' in data['ticket']
 
        )
 

	
 
        yield data
 

	
 

	
 
def generate_stats(options):
 
    stats = {
 
        'firstname': [],
 
        'lastname': [],
 
        'company': [],
 
    }
 
    for badge in collate(options):
 
        stats['firstname'].append((len(badge['firstname']), badge['firstname']))
 
        stats['lastname'].append((len(badge['lastname']), badge['lastname']))
 
        if badge['promote_company']:
 
            stats['company'].append((len(badge['company']), badge['company']))
 

	
 
    stats['firstname'].sort()
 
    stats['lastname'].sort()
 
    stats['company'].sort()
 

	
 
    for l, s in stats['firstname']:
 
        print ('%2d %s' % (l, s))
 
    for l, s in stats['lastname']:
 
        print ('%2d %s' % (l, s))
 
    for l, s in stats['company']:
 
        print ('%2d %s' % (l, s))
 

	
 

	
 
def generate_badges(options):
 
    names = list()
 

	
 
    orig = etree.parse(options['template'])
 
    tree = deepcopy(orig)
 
    root = tree.getroot()
 

	
 
    for n, data in enumerate(collate(options)):
 
        svg_badge(root, data, n % 2)
 
        if n % 2:
 
            name = os.path.abspath(
 
                os.path.join(options['out_dir'], 'badge-%d.svg' % n))
 
            tree.write(name)
 
            names.append(name)
 
            tree = deepcopy(orig)
 
            root = tree.getroot()
 

	
 
    if not n % 2:
 
        name = os.path.abspath(
 
            os.path.join(options['out_dir'], 'badge-%d.svg' % n))
 
        tree.write(name)
 
        names.append(name)
 

	
 
    return 0
 

	
 
class InvalidTicketChoiceError(Exception):
 
    '''
 
    Exception thrown when they chosen ticket isn't valid.  This
 
    happens either if the ticket choice is 0 (default: Chose a ticket),
 
    or is greater than the index if the last ticket choice in the
 
    dropdown list.
 
    '''
 
    def __init__(self, message="Please choose a VALID ticket."):
 
        super(InvalidTicketChoiceError, self).__init__(message,)
vendor/registrasion/registrasion/controllers/batch.py
Show inline comments
 
import contextlib
 
import functools
 

	
 
from django.contrib.auth.models import User
 
from django.contrib.auth import get_user_model
 

	
 
User = get_user_model()
 

	
 

	
 
class BatchController(object):
 
    ''' Batches are sets of operations where certain queries for users may be
 
    repeated, but are also unlikely change within the boundaries of the batch.
 

	
 
    Batches are keyed per-user. You can mark the edge of the batch with the
 
    ``batch`` context manager. If you nest calls to ``batch``, only the
 
    outermost call will have the effect of ending the batch.
 

	
 
    Batches store results for functions wrapped with ``memoise``. These results
 
    for the user are flushed at the end of the batch.
 

	
 
    If a return for a memoised function has a callable attribute called
 
    ``end_batch``, that attribute will be called at the end of the batch.
 

	
 
    '''
 

	
 
    _user_caches = {}
 
    _NESTING_KEY = "nesting_count"
 

	
 
    @classmethod
 
    @contextlib.contextmanager
 
    def batch(cls, user):
 
        ''' Marks the entry point for a batch for the given user. '''
 

	
 
        cls._enter_batch_context(user)
 
        try:
 
            yield
 
        finally:
 
            # Make sure we clean up in case of errors.
 
            cls._exit_batch_context(user)
 

	
 
    @classmethod
 
    def _enter_batch_context(cls, user):
 
        if user not in cls._user_caches:
 
            cls._user_caches[user] = cls._new_cache()
 

	
 
        cache = cls._user_caches[user]
 
        cache[cls._NESTING_KEY] += 1
 

	
 
    @classmethod
 
    def _exit_batch_context(cls, user):
 
        cache = cls._user_caches[user]
 
        cache[cls._NESTING_KEY] -= 1
 

	
 
        if cache[cls._NESTING_KEY] == 0:
 
            cls._call_end_batch_methods(user)
 
            del cls._user_caches[user]
 

	
 
    @classmethod
 
    def _call_end_batch_methods(cls, user):
 
        cache = cls._user_caches[user]
 
        ended = set()
 
        while True:
 
            keys = set(cache.keys())
 
            if ended == keys:
 
                break
 
            keys_to_end = keys - ended
 
            for key in keys_to_end:
 
                item = cache[key]
 
                if hasattr(item, 'end_batch') and callable(item.end_batch):
 
                    item.end_batch()
 
            ended = ended | keys_to_end
 

	
 
    @classmethod
 
    def memoise(cls, func):
 
        ''' Decorator that stores the result of the stored function in the
 
        user's results cache until the batch completes. Keyword arguments are
 
        not yet supported.
 

	
 
        Arguments:
 
            func (callable(*a)): The function whose results we want
 
                to store. The positional arguments, ``a``, are used as cache
 
                keys.
 

	
 
        Returns:
 
            callable(*a): The memosing version of ``func``.
 

	
 
        '''
 

	
 
        @functools.wraps(func)
 
        def f(*a):
 

	
 
            for arg in a:
 
                if isinstance(arg, User):
 
                    user = arg
 
                    break
 
            else:
 
                raise ValueError("One position argument must be a User")
 

	
 
            func_key = (func, tuple(a))
 
            cache = cls.get_cache(user)
 

	
 
            if func_key not in cache:
 
                cache[func_key] = func(*a)
 

	
 
            return cache[func_key]
 

	
 
        return f
 

	
 
    @classmethod
 
    def get_cache(cls, user):
 
        if user not in cls._user_caches:
 
            # Return blank cache here, we'll just discard :)
 
            return cls._new_cache()
 

	
 
        return cls._user_caches[user]
 

	
 
    @classmethod
 
    def _new_cache(cls):
 
        ''' Returns a new cache dictionary. '''
 
        cache = {}
 
        cache[cls._NESTING_KEY] = 0
 
        return cache
vendor/registrasion/registrasion/controllers/item.py
Show inline comments
 
''' NEEDS TESTS '''
 

	
 
import operator
 
from functools import reduce
 

	
 
from registrasion.models import commerce
 
from registrasion.models import inventory
 

	
 
from collections import Iterable
 
from collections.abc import Iterable
 
from collections import namedtuple
 
from django.db.models import Case
 
from django.db.models import Q
 
from django.db.models import Sum
 
from django.db.models import When
 
from django.db.models import Value
 

	
 
_ProductAndQuantity = namedtuple("ProductAndQuantity", ["product", "quantity"])
 

	
 

	
 
class ProductAndQuantity(_ProductAndQuantity):
 
    ''' Class that holds a product and a quantity.
 

	
 
    Attributes:
 
        product (models.inventory.Product)
 

	
 
        quantity (int)
 

	
 
    '''
 
    pass
 

	
 

	
 
class ItemController(object):
 

	
 
    def __init__(self, user):
 
        self.user = user
 

	
 
    def _items(self, cart_status, category=None):
 
        ''' Aggregates the items that this user has purchased.
 

	
 
        Arguments:
 
            cart_status (int or Iterable(int)): etc
 
            category (Optional[models.inventory.Category]): the category
 
                of items to restrict to.
 

	
 
        Returns:
 
            [ProductAndQuantity, ...]: A list of product-quantity pairs,
 
                aggregating like products from across multiple invoices.
 

	
 
        '''
 

	
 
        if not isinstance(cart_status, Iterable):
 
            cart_status = [cart_status]
 

	
 
        status_query = (
 
            Q(productitem__cart__status=status) for status in cart_status
 
        )
 

	
 
        in_cart = Q(productitem__cart__user=self.user)
 
        in_cart = in_cart & reduce(operator.__or__, status_query)
 

	
 
        quantities_in_cart = When(
 
            in_cart,
 
            then="productitem__quantity",
 
        )
 

	
 
        quantities_or_zero = Case(
 
            quantities_in_cart,
 
            default=Value(0),
 
        )
 

	
 
        products = inventory.Product.objects
 

	
 
        if category:
 
            products = products.filter(category=category)
 

	
 
        products = products.select_related("category")
 
        products = products.annotate(quantity=Sum(quantities_or_zero))
 
        products = products.filter(quantity__gt=0)
 

	
 
        out = []
 
        for prod in products:
 
            out.append(ProductAndQuantity(prod, prod.quantity))
 
        return out
 

	
 
    def items_pending_or_purchased(self):
 
        ''' Returns the items that this user has purchased or has pending. '''
 
        status = [commerce.Cart.STATUS_PAID, commerce.Cart.STATUS_ACTIVE]
 
        return self._items(status)
 

	
 
    def items_purchased(self, category=None):
 
        ''' Aggregates the items that this user has purchased.
 

	
 
        Arguments:
 
            category (Optional[models.inventory.Category]): the category
 
                of items to restrict to.
 

	
 
        Returns:
 
            [ProductAndQuantity, ...]: A list of product-quantity pairs,
 
                aggregating like products from across multiple invoices.
 

	
 
        '''
 
        return self._items(commerce.Cart.STATUS_PAID, category=category)
 

	
 
    def items_pending(self):
 
        ''' Gets all of the items that the user has reserved, but has not yet
 
        paid for.
 

	
 
        Returns:
 
            [ProductAndQuantity, ...]: A list of product-quantity pairs for the
 
                items that the user has not yet paid for.
 

	
 
        '''
 

	
 
        return self._items(commerce.Cart.STATUS_ACTIVE)
 

	
 
    def items_released(self):
 
        ''' Gets all of the items that the user previously paid for, but has
 
        since refunded.
 

	
 
        Returns:
 
            [ProductAndQuantity, ...]: A list of product-quantity pairs for the
 
                items that the user has not yet paid for.
 

	
 
        '''
 

	
 
        return self._items(commerce.Cart.STATUS_RELEASED)
vendor/registrasion/registrasion/models/commerce.py
Show inline comments
 
from . import conditions
 
from . import inventory
 

	
 
from django.contrib.auth.models import User
 
from django.contrib.auth import get_user_model
 
from django.core.exceptions import ValidationError
 
from django.db import models
 
from django.db.models import F, Q, Sum
 
from django.utils import timezone
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from model_utils.managers import InheritanceManager
 

	
 
User = get_user_model()
 

	
 

	
 
# Commerce Models
 

	
 
@python_2_unicode_compatible
 
class Cart(models.Model):
 
    ''' Represents a set of product items that have been purchased, or are
 
    pending purchase. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        index_together = [
 
            ("status", "time_last_updated"),
 
            ("status", "user"),
 
        ]
 

	
 
    def __str__(self):
 
        return "%d rev #%d" % (self.id, self.revision)
 

	
 
    STATUS_ACTIVE = 1
 
    STATUS_PAID = 2
 
    STATUS_RELEASED = 3
 

	
 
    STATUS_TYPES = [
 
        (STATUS_ACTIVE, _("Active")),
 
        (STATUS_PAID, _("Paid")),
 
        (STATUS_RELEASED, _("Released")),
 
    ]
 

	
 
    user = models.ForeignKey(User)
 
    user = models.ForeignKey(User, on_delete=models.CASCADE)
 
    # ProductItems (foreign key)
 
    vouchers = models.ManyToManyField(inventory.Voucher, blank=True)
 
    time_last_updated = models.DateTimeField(
 
        db_index=True,
 
    )
 
    reservation_duration = models.DurationField()
 
    revision = models.PositiveIntegerField(default=1)
 
    status = models.IntegerField(
 
        choices=STATUS_TYPES,
 
        db_index=True,
 
        default=STATUS_ACTIVE,
 
    )
 

	
 
    @classmethod
 
    def reserved_carts(cls):
 
        ''' Gets all carts that are 'reserved' '''
 
        return Cart.objects.filter(
 
            (Q(status=Cart.STATUS_ACTIVE) &
 
                Q(time_last_updated__gt=(
 
                    timezone.now()-F('reservation_duration')
 
                                        ))) |
 
            Q(status=Cart.STATUS_PAID)
 
        )
 

	
 

	
 
@python_2_unicode_compatible
 
class ProductItem(models.Model):
 
    ''' Represents a product-quantity pair in a Cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("product", )
 

	
 
    def __str__(self):
 
        return "product: %s * %d in Cart: %s" % (
 
            self.product, self.quantity, self.cart)
 

	
 
    cart = models.ForeignKey(Cart)
 
    product = models.ForeignKey(inventory.Product)
 
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
 
    product = models.ForeignKey(inventory.Product, on_delete=models.CASCADE)
 
    quantity = models.PositiveIntegerField(db_index=True)
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountItem(models.Model):
 
    ''' Represents a discount-product-quantity relation in a Cart. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("product", )
 

	
 
    def __str__(self):
 
        return "%s: %s * %d in Cart: %s" % (
 
            self.discount, self.product, self.quantity, self.cart)
 

	
 
    cart = models.ForeignKey(Cart)
 
    product = models.ForeignKey(inventory.Product)
 
    discount = models.ForeignKey(conditions.DiscountBase)
 
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
 
    product = models.ForeignKey(inventory.Product, on_delete=models.CASCADE)
 
    discount = models.ForeignKey(conditions.DiscountBase,
 
            on_delete=models.CASCADE)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
@python_2_unicode_compatible
 
class Invoice(models.Model):
 
    ''' An invoice. Invoices can be automatically generated when checking out
 
    a Cart, in which case, it is attached to a given revision of a Cart.
 

	
 
    Attributes:
 

	
 
        user (User): The owner of this invoice.
 

	
 
        cart (commerce.Cart): The cart that was used to generate this invoice.
 

	
 
        cart_revision (int): The value of ``cart.revision`` at the time of this
 
            invoice's creation. If a change is made to the underlying cart,
 
            this invoice is automatically void -- this change is detected
 
            when ``cart.revision != cart_revision``.
 

	
 
        status (int): One of ``STATUS_UNPAID``, ``STATUS_PAID``,
 
            ``STATUS_REFUNDED``, OR ``STATUS_VOID``. Call
 
            ``get_status_display`` for a human-readable representation.
 

	
 
        recipient (str): A rendered representation of the invoice's recipient.
 

	
 
        issue_time (datetime): When the invoice was issued.
 

	
 
        due_time (datetime): When the invoice is due.
 

	
 
        value (Decimal): The total value of the line items attached to the
 
            invoice.
 

	
 
        lineitem_set (Queryset[LineItem]): The set of line items that comprise
 
            this invoice.
 

	
 
        paymentbase_set(Queryset[PaymentBase]): The set of PaymentBase objects
 
            that have been applied to this invoice.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    STATUS_UNPAID = 1
 
    STATUS_PAID = 2
 
    STATUS_REFUNDED = 3
 
    STATUS_VOID = 4
 

	
 
    STATUS_TYPES = [
 
        (STATUS_UNPAID, _("Unpaid")),
 
        (STATUS_PAID, _("Paid")),
 
        (STATUS_REFUNDED, _("Refunded")),
 
        (STATUS_VOID, _("VOID")),
 
    ]
 

	
 
    def __str__(self):
 
        return "Invoice #%d (to: %s, due: %s, value: %s)" % (
 
            self.id, self.user.email, self.due_time, self.value
 
        )
 

	
 
    def clean(self):
 
        if self.cart is not None and self.cart_revision is None:
 
            raise ValidationError(
 
                "If this is a cart invoice, it must have a revision")
 

	
 
    @property
 
    def is_unpaid(self):
 
        return self.status == self.STATUS_UNPAID
 

	
 
    @property
 
    def is_void(self):
 
        return self.status == self.STATUS_VOID
 

	
 
    @property
 
    def is_paid(self):
 
        return self.status == self.STATUS_PAID
 

	
 
    @property
 
    def is_refunded(self):
 
        return self.status == self.STATUS_REFUNDED
 

	
 
    def total_payments(self):
 
        ''' Returns the total amount paid towards this invoice. '''
 

	
 
        payments = PaymentBase.objects.filter(invoice=self)
 
        total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0
 
        return total_paid
 

	
 
    def balance_due(self):
 
        ''' Returns the total balance remaining towards this invoice. '''
 
        return self.value - self.total_payments()
 

	
 
    # Invoice Number
 
    user = models.ForeignKey(User)
 
    cart = models.ForeignKey(Cart, null=True)
 
    user = models.ForeignKey(User, on_delete=models.CASCADE)
 
    cart = models.ForeignKey(Cart, null=True, on_delete=models.CASCADE)
 
    cart_revision = models.IntegerField(
 
        null=True,
 
        db_index=True,
 
    )
 
    # Line Items (foreign key)
 
    status = models.IntegerField(
 
        choices=STATUS_TYPES,
 
        db_index=True,
 
    )
 
    recipient = models.CharField(max_length=1024)
 
    issue_time = models.DateTimeField()
 
    due_time = models.DateTimeField()
 
    value = models.DecimalField(max_digits=8, decimal_places=2)
 

	
 

	
 
@python_2_unicode_compatible
 
class LineItem(models.Model):
 
    ''' Line items for an invoice. These are denormalised from the ProductItems
 
    and DiscountItems that belong to a cart (for consistency), but also allow
 
    for arbitrary line items when required.
 

	
 
    Attributes:
 

	
 
        invoice (commerce.Invoice): The invoice to which this LineItem is
 
            attached.
 

	
 
        description (str): A human-readable description of the line item.
 

	
 
        quantity (int): The quantity of items represented by this line.
 

	
 
        price (Decimal): The per-unit price for this line item.
 

	
 
        product (Optional[inventory.Product]): The product that this LineItem
 
            applies to. This allows you to do reports on sales and applied
 
            discounts to individual products.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        ordering = ("id", )
 

	
 
    def __str__(self):
 
        return "Line: %s * %d @ %s" % (
 
            self.description, self.quantity, self.price)
 

	
 
    @property
 
    def total_price(self):
 
        ''' price * quantity '''
 
        return self.price * self.quantity
 

	
 
    invoice = models.ForeignKey(Invoice)
 
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
 
    description = models.CharField(max_length=255)
 
    quantity = models.PositiveIntegerField()
 
    price = models.DecimalField(max_digits=8, decimal_places=2)
 
    product = models.ForeignKey(inventory.Product, null=True, blank=True)
 
    product = models.ForeignKey(inventory.Product, null=True, blank=True,
 
            on_delete=models.CASCADE)
 

	
 

	
 
@python_2_unicode_compatible
 
class PaymentBase(models.Model):
 
    ''' The base payment type for invoices. Payment apps should subclass this
 
    class to handle implementation-specific issues.
 

	
 
    Attributes:
 
        invoice (commerce.Invoice): The invoice that this payment applies to.
 

	
 
        time (datetime): The time that this payment was generated. Note that
 
            this will default to the current time when the model is created.
 

	
 
        reference (str): A human-readable reference for the payment, this will
 
            be displayed alongside the invoice.
 

	
 
        amount (Decimal): The amount the payment is for.
 

	
 
    '''
 

	
 
    class Meta:
 
        ordering = ("time", )
 

	
 
    objects = InheritanceManager()
 

	
 
    def __str__(self):
 
        return "Payment: ref=%s amount=%s" % (self.reference, self.amount)
 

	
 
    invoice = models.ForeignKey(Invoice)
 
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
 
    time = models.DateTimeField(default=timezone.now)
 
    reference = models.CharField(max_length=255)
 
    amount = models.DecimalField(max_digits=8, decimal_places=2)
 

	
 

	
 
class ManualPayment(PaymentBase):
 
    ''' Payments that are manually entered by staff. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    entered_by = models.ForeignKey(User)
 
    entered_by = models.ForeignKey(User, on_delete=models.CASCADE)
 

	
 

	
 
class CreditNote(PaymentBase):
 
    ''' Credit notes represent money accounted for in the system that do not
 
    belong to specific invoices. They may be paid into other invoices, or
 
    cashed out as refunds.
 

	
 
    Each CreditNote may either be used to pay towards another Invoice in the
 
    system (by attaching a CreditNoteApplication), or may be marked as
 
    refunded (by attaching a CreditNoteRefund).'''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    @classmethod
 
    def unclaimed(cls):
 
        return cls.objects.filter(
 
            creditnoteapplication=None,
 
            creditnoterefund=None,
 
        )
 

	
 
    @classmethod
 
    def refunded(cls):
 
        return cls.objects.exclude(creditnoterefund=None)
 

	
 
    @property
 
    def status(self):
 
        if self.is_unclaimed:
 
            return "Unclaimed"
 

	
 
        if hasattr(self, 'creditnoteapplication'):
 
            destination = self.creditnoteapplication.invoice.id
 
            return "Applied to invoice %d" % destination
 

	
 
        elif hasattr(self, 'creditnoterefund'):
 
            reference = self.creditnoterefund.reference
 
            return "Refunded with reference: %s" % reference
 

	
 
        raise ValueError("This should never happen.")
 

	
 
    @property
 
    def is_unclaimed(self):
 
        return not (
 
            hasattr(self, 'creditnoterefund') or
 
            hasattr(self, 'creditnoteapplication')
 
        )
 

	
 
    @property
 
    def value(self):
 
        ''' Returns the value of the credit note. Because CreditNotes are
 
        implemented as PaymentBase objects internally, the amount is a
 
        negative payment against an invoice. '''
 
        return -self.amount
 

	
 

	
 
class CleanOnSave(object):
 

	
 
    def save(self, *a, **k):
 
        self.full_clean()
 
        super(CleanOnSave, self).save(*a, **k)
 

	
 

	
 
class CreditNoteApplication(CleanOnSave, PaymentBase):
 
    ''' Represents an application of a credit note to an Invoice. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def clean(self):
 
        if not hasattr(self, "parent"):
 
            return
 
        if hasattr(self.parent, 'creditnoterefund'):
 
            raise ValidationError(
 
                "Cannot apply a refunded credit note to an invoice"
 
            )
 

	
 
    parent = models.OneToOneField(CreditNote)
 
    parent = models.OneToOneField(CreditNote, on_delete=models.CASCADE)
 

	
 

	
 
class CreditNoteRefund(CleanOnSave, models.Model):
 
    ''' Represents a refund of a credit note to an external payment.
 
    Credit notes may only be refunded in full. How those refunds are handled
 
    is left as an exercise to the payment app.
 

	
 
    Attributes:
 
        parent (commerce.CreditNote): The CreditNote that this refund
 
            corresponds to.
 

	
 
        time (datetime): The time that this refund was generated.
 

	
 
        reference (str): A human-readable reference for the refund, this should
 
            allow the user to identify the refund in their records.
 

	
 
    '''
 

	
 
    def clean(self):
 
        if not hasattr(self, "parent"):
 
            return
 
        if hasattr(self.parent, 'creditnoteapplication'):
 
            raise ValidationError(
 
                "Cannot refund a credit note that has been paid to an invoice"
 
            )
 

	
 
    parent = models.OneToOneField(CreditNote)
 
    parent = models.OneToOneField(CreditNote, on_delete=models.CASCADE)
 
    time = models.DateTimeField(default=timezone.now)
 
    reference = models.CharField(max_length=255)
 

	
 

	
 
class ManualCreditNoteRefund(CreditNoteRefund):
 
    ''' Credit notes that are entered by a staff member. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    entered_by = models.ForeignKey(User)
 
    entered_by = models.ForeignKey(User, on_delete=models.CASCADE)
vendor/registrasion/registrasion/models/conditions.py
Show inline comments
 
import itertools
 

	
 
from . import inventory
 

	
 
from django.contrib.auth.models import Group
 
from django.core.exceptions import ValidationError
 
from django.db import models
 
from django.db.models import F, Q
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from model_utils.managers import InheritanceManager
 

	
 
from symposion import proposals
 

	
 

	
 
# Condition Types
 

	
 
class TimeOrStockLimitCondition(models.Model):
 
    ''' Attributes for a condition that is limited by timespan or a count of
 
    purchased or reserved items.
 

	
 
    Attributes:
 
        start_time (Optional[datetime]): When the condition should start being
 
            true.
 

	
 
        end_time (Optional[datetime]): When the condition should stop being
 
            true.
 

	
 
        limit (Optional[int]): How many items may fall under the condition
 
            the condition until it stops being false -- for all users.
 
    '''
 

	
 
    class Meta:
 
        abstract = True
 

	
 
    start_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Start time"),
 
        help_text=_("When the condition should start being true"),
 
    )
 
    end_time = models.DateTimeField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("End time"),
 
        help_text=_("When the condition should stop being true."),
 
    )
 
    limit = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Limit"),
 
        help_text=_(
 
            "How many times this condition may be applied for all users."
 
        ),
 
    )
 

	
 

	
 
class VoucherCondition(models.Model):
 
    ''' A condition is met when a voucher code is in the current cart. '''
 

	
 
    class Meta:
 
        abstract = True
 

	
 
    voucher = models.OneToOneField(
 
        inventory.Voucher,
 
        on_delete=models.CASCADE,
 
        verbose_name=_("Voucher"),
 
        db_index=True,
 
    )
 

	
 

	
 
class IncludedProductCondition(models.Model):
 
    class Meta:
 
        abstract = True
 

	
 
    enabling_products = models.ManyToManyField(
 
        inventory.Product,
 
        verbose_name=_("Including product"),
 
        help_text=_("If one of these products are purchased, this condition "
 
                    "is met."),
 
    )
 

	
 

	
 
class SpeakerCondition(models.Model):
 
    ''' Conditions that are met if a user is a presenter, or copresenter,
 
    of a specific kind of presentation. '''
 

	
 
    class Meta:
 
        abstract = True
 

	
 
    is_presenter = models.BooleanField(
 
        blank=True,
 
        help_text=_("This condition is met if the user is the primary "
 
                    "presenter of a presentation."),
 
    )
 
    is_copresenter = models.BooleanField(
 
        blank=True,
 
        help_text=_("This condition is met if the user is a copresenter of a "
 
                    "presentation."),
 
    )
 
    proposal_kind = models.ManyToManyField(
 
        proposals.models.ProposalKind,
 
        "symposion_proposals.ProposalKind",
 
        help_text=_("The types of proposals that these users may be "
 
                    "presenters of."),
 
    )
 

	
 

	
 
class GroupMemberCondition(models.Model):
 
    ''' Conditions that are met if a user is a member (not declined or
 
    rejected) of a specific django auth group. '''
 

	
 
    class Meta:
 
        abstract = True
 

	
 
    group = models.ManyToManyField(
 
        Group,
 
        help_text=_("The groups a user needs to be a member of for this"
 
                    "condition to be met."),
 
    )
 

	
 

	
 
# Discounts
 

	
 
@python_2_unicode_compatible
 
class DiscountBase(models.Model):
 
    ''' Base class for discounts. This class is subclassed with special
 
    attributes which are used to determine whether or not the given discount
 
    is available to be added to the current cart.
 

	
 
    Attributes:
 
        description (str): Display text that appears on the attendee's Invoice
 
            when the discount is applied to a Product on that invoice.
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    objects = InheritanceManager()
 

	
 
    def __str__(self):
 
        return "Discount: " + self.description
 

	
 
    def effects(self):
 
        ''' Returns all of the effects of this discount. '''
 
        products = self.discountforproduct_set.all()
 
        categories = self.discountforcategory_set.all()
 
        return itertools.chain(products, categories)
 

	
 
    description = models.CharField(
 
        max_length=255,
 
        verbose_name=_("Description"),
 
        help_text=_("A description of this discount. This will be included on "
 
                    "invoices where this discount is applied."),
 
        )
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountForProduct(models.Model):
 
    ''' Represents a discount on an individual product. Each Discount can
 
    contain multiple products and categories. Discounts can either be a
 
    percentage or a fixed amount, but not both.
 

	
 
    Attributes:
 
        product (inventory.Product): The product that this discount line will
 
            apply to.
 

	
 
        percentage (Decimal): The percentage discount that will be *taken off*
 
            this product if this discount applies.
 

	
 
        price (Decimal): The currency value that will be *taken off* this
 
            product if this discount applies.
 

	
 
        quantity (int): The number of times that each user may apply this
 
            discount line. This applies across every valid Invoice that
 
            the user has.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        if self.percentage:
 
            return "%s%% off %s" % (self.percentage, self.product)
 
        elif self.price:
 
            return "$%s off %s" % (self.price, self.product)
 

	
 
    def clean(self):
 
        if self.percentage is None and self.price is None:
 
            raise ValidationError(
 
                _("Discount must have a percentage or a price."))
 
        elif self.percentage is not None and self.price is not None:
 
            raise ValidationError(
 
                _("Discount may only have a percentage or only a price."))
 

	
 
        prods = DiscountForProduct.objects.filter(
 
            discount=self.discount,
 
            product=self.product)
 
        cats = DiscountForCategory.objects.filter(
 
            discount=self.discount,
 
            category=self.product.category)
 
        if len(prods) > 1:
 
            raise ValidationError(
 
                _("You may only have one discount line per product"))
 
        if len(cats) != 0:
 
            raise ValidationError(
 
                _("You may only have one discount for "
 
                    "a product or its category"))
 

	
 
    discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE)
 
    product = models.ForeignKey(inventory.Product, on_delete=models.CASCADE)
 
    percentage = models.DecimalField(
 
        max_digits=4, decimal_places=1, null=True, blank=True)
 
    price = models.DecimalField(
 
        max_digits=8, decimal_places=2, null=True, blank=True)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
@python_2_unicode_compatible
 
class DiscountForCategory(models.Model):
 
    ''' Represents a discount for a category of products. Each discount can
 
    contain multiple products. Category discounts can only be a percentage.
 

	
 
    Attributes:
 

	
 
        category (inventory.Category): The category whose products that this
 
            discount line will apply to.
 

	
 
        percentage (Decimal): The percentage discount that will be *taken off*
 
            a product if this discount applies.
 

	
 
        quantity (int): The number of times that each user may apply this
 
            discount line. This applies across every valid Invoice that the
 
            user has.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 

	
 
    def __str__(self):
 
        return "%s%% off %s" % (self.percentage, self.category)
 

	
 
    def clean(self):
 
        prods = DiscountForProduct.objects.filter(
 
            discount=self.discount,
 
            product__category=self.category)
 
        cats = DiscountForCategory.objects.filter(
 
            discount=self.discount,
 
            category=self.category)
 
        if len(prods) != 0:
 
            raise ValidationError(
 
                _("You may only have one discount for "
 
                    "a product or its category"))
 
        if len(cats) > 1:
 
            raise ValidationError(
 
                _("You may only have one discount line per category"))
 

	
 
    discount = models.ForeignKey(DiscountBase, on_delete=models.CASCADE)
 
    category = models.ForeignKey(inventory.Category, on_delete=models.CASCADE)
 
    percentage = models.DecimalField(
 
        max_digits=4,
 
        decimal_places=1)
 
    quantity = models.PositiveIntegerField()
 

	
 

	
 
class TimeOrStockLimitDiscount(TimeOrStockLimitCondition, DiscountBase):
 
    ''' Discounts that are generally available, but are limited by timespan or
 
    usage count. This is for e.g. Early Bird discounts.
 

	
 
    Attributes:
 
        start_time (Optional[datetime]): When the discount should start being
 
            offered.
 

	
 
        end_time (Optional[datetime]): When the discount should stop being
 
            offered.
 

	
 
        limit (Optional[int]): How many times the discount is allowed to be
 
            applied -- to all users.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (time/stock limit)")
 
        verbose_name_plural = _("discounts (time/stock limit)")
 

	
 

	
 
class VoucherDiscount(VoucherCondition, DiscountBase):
 
    ''' Discounts that are enabled when a voucher code is in the current
 
    cart. These are normally configured in the Admin page at the same time as
 
    creating a Voucher object.
 

	
 
    Attributes:
 
        voucher (inventory.Voucher): The voucher that enables this discount.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (enabled by voucher)")
 
        verbose_name_plural = _("discounts (enabled by voucher)")
 

	
 

	
 
class IncludedProductDiscount(IncludedProductCondition, DiscountBase):
 
    ''' Discounts that are enabled because another product has been purchased.
 
    e.g. A conference ticket includes a free t-shirt.
 

	
 
    Attributes:
 
        enabling_products ([inventory.Product, ...]): The products that enable
 
            the discount.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (product inclusions)")
 
        verbose_name_plural = _("discounts (product inclusions)")
 

	
 

	
 
class SpeakerDiscount(SpeakerCondition, DiscountBase):
 
    ''' Discounts that are enabled because the user is a presenter or
 
    co-presenter of a kind of presentation.
 

	
 
    Attributes:
 
        is_presenter (bool): The condition should be met if the user is a
 
            presenter of a presentation.
 

	
 
        is_copresenter (bool): The condition should be met if the user is a
 
            copresenter of a presentation.
 

	
 
        proposal_kind ([symposion.proposals.models.ProposalKind, ...]): The
 
            kinds of proposals that the user may be a presenter or
 
            copresenter of for this condition to be met.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (speaker)")
 
        verbose_name_plural = _("discounts (speaker)")
 

	
 

	
 
class GroupMemberDiscount(GroupMemberCondition, DiscountBase):
 
    ''' Discounts that are enabled because the user is a member of a specific
 
    django auth Group.
 

	
 
    Attributes:
 
        group ([Group, ...]): The condition should be met if the user is a
 
            member of one of these groups.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("discount (group member)")
 
        verbose_name_plural = _("discounts (group member)")
 

	
 

	
 
# Flags
 

	
 
@python_2_unicode_compatible
 
class FlagBase(models.Model):
 
    ''' This defines a condition which allows products or categories to
 
    be made visible, or be prevented from being visible.
 

	
 
    Attributes:
 
        description (str): A human-readable description that is used to
 
            identify the flag to staff in the admin interface. It's not seen
 
            anywhere else in Registrasion.
 

	
 
        condition (int): This determines the effect of this flag's condition
 
            being met. There are two types of condition:
 

	
 
            ``ENABLE_IF_TRUE`` conditions switch on the products and
 
            categories included under this flag if *any* such condition is met.
 

	
 
            ``DISABLE_IF_FALSE`` conditions *switch off* the products and
 
            categories included under this flag is any such condition
 
            *is not* met.
 

	
 
            If you have both types of conditions attached to a Product, every
 
            ``DISABLE_IF_FALSE`` condition must be met, along with one
 
            ``ENABLE_IF_TRUE`` condition.
 

	
 
        products ([inventory.Product, ...]):
 
            The Products affected directly by this flag.
 

	
 
        categories ([inventory.Category, ...]):
 
            The Categories whose Products are affected by this flag.
 

	
 
        all_products ([inventory.Product, ...]):
 
            All products affected by this flag, either by being listed directly
 
            or by having their category listed.
 

	
 
    '''
 

	
 
    objects = InheritanceManager()
 

	
 
    DISABLE_IF_FALSE = 1
 
    ENABLE_IF_TRUE = 2
 

	
 
    def __str__(self):
 
        return self.description
 

	
 
    def effects(self):
 
        ''' Returns all of the items affected by this condition. '''
 
        return itertools.chain(self.products.all(), self.categories.all())
 

	
 
    @property
 
    def is_disable_if_false(self):
 
        return self.condition == FlagBase.DISABLE_IF_FALSE
 

	
 
    @property
 
    def is_enable_if_true(self):
 
        return self.condition == FlagBase.ENABLE_IF_TRUE
 

	
 
    description = models.CharField(max_length=255)
 
    condition = models.IntegerField(
 
        default=ENABLE_IF_TRUE,
 
        choices=(
 
            (DISABLE_IF_FALSE, _("Disable if false")),
 
            (ENABLE_IF_TRUE, _("Enable if true")),
 
        ),
 
        help_text=_("If there is at least one 'disable if false' flag "
 
                    "defined on a product or category, all such flag "
 
                    " conditions must be met. If there is at least one "
 
                    "'enable if true' flag, at least one such condition must "
 
                    "be met. If both types of conditions exist on a product, "
 
                    "both of these rules apply."
 
                    ),
 
    )
 
    products = models.ManyToManyField(
 
        inventory.Product,
 
        blank=True,
 
        help_text=_("Products affected by this flag's condition."),
 
        related_name="flagbase_set",
 
    )
 
    categories = models.ManyToManyField(
 
        inventory.Category,
 
        blank=True,
 
        help_text=_("Categories whose products are affected by this flag's "
 
                    "condition."
 
                    ),
 
        related_name="flagbase_set",
 
    )
 

	
 
    @property
 
    def all_products(self):
 
        all_products = inventory.Product.objects.all()
 
        all_products = all_products.filter(
 
            (
 
                Q(id__in=self.products.all()) |
 
                Q(category_id__in=self.categories.all())
 
            )
 
        )
 
        return all_products
 

	
 

	
 
class TimeOrStockLimitFlag(TimeOrStockLimitCondition, FlagBase):
 
    ''' Product groupings that can be used to enable a product during a
 
    specific date range, or when fewer than a limit of products have been
 
    sold.
 

	
 
    Attributes:
 
        start_time (Optional[datetime]): This condition is only met after this
 
            time.
 

	
 
        end_time (Optional[datetime]): This condition is only met before this
 
            time.
 

	
 
        limit (Optional[int]): The number of products that *all users* can
 
            purchase under this limit, regardless of their per-user limits.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (time/stock limit)")
 
        verbose_name_plural = _("flags (time/stock limit)")
 

	
 

	
 
@python_2_unicode_compatible
 
class ProductFlag(IncludedProductCondition, FlagBase):
 
    ''' The condition is met because a specific product is purchased.
 

	
 
    Attributes:
 
        enabling_products ([inventory.Product, ...]): The products that cause
 
            this condition to be met.
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on product)")
 
        verbose_name_plural = _("flags (dependency on product)")
 

	
 
    def __str__(self):
 
        return "Enabled by products: " + str(self.enabling_products.all())
 

	
 

	
 
@python_2_unicode_compatible
 
class CategoryFlag(FlagBase):
 
    ''' The condition is met because a product in a particular product is
 
    purchased.
 

	
 
    Attributes:
 
        enabling_category (inventory.Category): The category that causes this
 
            condition to be met.
 
     '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on product from category)")
 
        verbose_name_plural = _("flags (dependency on product from category)")
 

	
 
    def __str__(self):
 
        return "Enabled by product in category: " + str(self.enabling_category)
 

	
 
    enabling_category = models.ForeignKey(
 
        inventory.Category,
 
        help_text=_("If a product from this category is purchased, this "
 
                    "condition is met."),
 
        on_delete=models.CASCADE,
 
    )
 

	
 

	
 
@python_2_unicode_compatible
 
class VoucherFlag(VoucherCondition, FlagBase):
 
    ''' The condition is met because a Voucher is present. This is for e.g.
 
    enabling sponsor tickets. '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (dependency on voucher)")
 
        verbose_name_plural = _("flags (dependency on voucher)")
 

	
 
    def __str__(self):
 
        return "Enabled by voucher: %s" % self.voucher
 

	
 

	
 
class SpeakerFlag(SpeakerCondition, FlagBase):
 
    ''' Conditions that are enabled because the user is a presenter or
 
    co-presenter of a kind of presentation.
 

	
 
    Attributes:
 
        is_presenter (bool): The condition should be met if the user is a
 
            presenter of a presentation.
 

	
 
        is_copresenter (bool): The condition should be met if the user is a
 
            copresenter of a presentation.
 

	
 
        proposal_kind ([symposion.proposals.models.ProposalKind, ...]): The
 
            kinds of proposals that the user may be a presenter or
 
            copresenter of for this condition to be met.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (speaker)")
 
        verbose_name_plural = _("flags (speaker)")
 

	
 

	
 
class GroupMemberFlag(GroupMemberCondition, FlagBase):
 
    ''' Flag whose conditions are metbecause the user is a member of a specific
 
    django auth Group.
 

	
 
    Attributes:
 
        group ([Group, ...]): The condition should be met if the user is a
 
            member of one of these groups.
 

	
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("flag (group member)")
 
        verbose_name_plural = _("flags (group member)")
vendor/registrasion/registrasion/models/inventory.py
Show inline comments
 
import datetime
 

	
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 

	
 

	
 
# Inventory Models
 

	
 
@python_2_unicode_compatible
 
class Category(models.Model):
 
    ''' Registration product categories, used as logical groupings for Products
 
    in registration forms.
 

	
 
    Attributes:
 
        name (str): The display name for the category.
 

	
 
        description (str): Some explanatory text for the category. This is
 
            displayed alongside the forms where your attendees choose their
 
            items.
 

	
 
        required (bool): Requires a user to select an item from this category
 
            during initial registration. You can use this, e.g., for making
 
            sure that the user has a ticket before they select whether they
 
            want a t-shirt.
 

	
 
        render_type (int): This is used to determine what sort of form the
 
            attendee will be presented with when choosing Products from this
 
            category. These may be either of the following:
 

	
 
            ``RENDER_TYPE_RADIO`` presents the Products in the Category as a
 
            list of radio buttons. At most one item can be chosen at a time.
 
            This works well when setting limit_per_user to 1.
 

	
 
            ``RENDER_TYPE_QUANTITY`` shows each Product next to an input field,
 
            where the user can specify a quantity of each Product type. This is
 
            useful for additional extras, like Dinner Tickets.
 

	
 
            ``RENDER_TYPE_ITEM_QUANTITY`` shows a select menu to select a
 
            Product type, and an input field, where the user can specify the
 
            quantity for that Product type. This is useful for categories that
 
            have a lot of options, from which the user is not going to select
 
            all of the options.
 

	
 
            ``RENDER_TYPE_CHECKBOX`` shows a checkbox beside each product.
 

	
 
        limit_per_user (Optional[int]): This restricts the number of items
 
            from this Category that each attendee may claim. This extends
 
            across multiple Invoices.
 

	
 
        order (int): An ascending order for displaying the Categories
 
            available. By convention, your Category for ticket types should
 
            have the lowest display order.
 
    '''
 

	
 
    class Meta:
 
        app_label = "registrasion"
 
        verbose_name = _("inventory - category")
 
        verbose_name_plural = _("inventory - categories")
 
        ordering = ("order", )
 

	
 
    def __str__(self):
 
        return self.name
 

	
 
    RENDER_TYPE_RADIO = 1
 
    RENDER_TYPE_QUANTITY = 2