Changeset - e726ff21a8ff
[Not reviewed]
0 9 7
James Polley - 6 years ago 2018-01-06 00:38:06
jp@jamezpolley.com
Create regidesk app

Shows summary of all attendees with a paid ticket, including
boarding_pass status.

Currently, regidesk allows staff with the requisite permission the
ability to view the checkin status of attendees, and email the user
their boarding pass email.

Included is a view for the user to retrieve their own QR code (in case
they got the plain-text version of the email, they can use this to
download an image to their phone for faster checkin)
16 files changed with 746 insertions and 11 deletions:
0 comments (0 inline, 0 general)
pinaxcon/settings.py
Show inline comments
...
 
@@ -209,11 +209,14 @@ INSTALLED_APPS = [
 
    # Registrasion
 
    "registrasion",
 

	
 
    # Registrasion-stipe
 
    # Registrasion-stripe
 
    "pinax.stripe",
 
    "django_countries",
 
    "registripe",
 

	
 
    #registrasion-desk
 
    "regidesk",
 

	
 
    # admin - required by registrasion ??
 
    "nested_admin",
 

	
pinaxcon/urls.py
Show inline comments
...
 
@@ -26,7 +26,7 @@ urlpatterns = [
 
    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/', RedirectView.as_view(url='/')),
vendor/regidesk/MANIFEST.in
Show inline comments
 
new file 100644
 
recursive-include regidesk/templates *
...
 
\ No newline at end of file
vendor/regidesk/regidesk/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
# Register your models here.
 
from regidesk.models import BoardingPassTemplate, BoardingPass, CheckIn
 

	
 
admin.site.register(
 
    BoardingPassTemplate,
 
    list_display=['label','from_address','subject']
 
)
 

	
 
admin.site.register( BoardingPass,
 
                     list_display=['to_address','created','sent'],
 
                     search_fields=['to_address'],
 
                     filter_fields=['created','sent'],
 
                     readonly_fields=['created','sent',
 
                                      'template', 'to_address', 'from_address',
 
                                      'subject', 'body','html_body' ]
 
)
 

	
 
admin.site.register(
 
    CheckIn,
 
    list_display=['user','seen','checked_in','checkin_code'],
 
    search_fields=['user','checkin_code'],
 
    filter_fields=['seen','checked_in'],
 
    readonly_fields=['user','seen','checked_in','checkin_code']
 
)
vendor/regidesk/regidesk/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- coding: utf-8 -*-
 
# Generated by Django 1.11.8 on 2018-01-06 00:19
 
from __future__ import unicode_literals
 

	
 
import datetime
 

	
 
from django.conf import settings
 
from django.db import migrations, models
 
import django.db.models.deletion
 
import django.utils.timezone
 

	
 
def create_lca2018_template(apps, schema_editor):
 

	
 
    BoardingPassTemplate = apps.get_model("regidesk", "BoardingPassTemplate")
 

	
 
    body = ("This is the plain text version of your boarding pass for "
 
            "linux.conf.au 2018.\r\n\r\nWhen you check in at LCA, you'll "
 
            "need to show the QR code you can download from "
 
            "{{ qrcode_url }}, or quote registration code: {{ code }} ")
 
    html =  ("<html>\r\n    <body>\r\n        <p>This is your boarding "
 
             "pass</p>\r\n        <p>A copy of the QR Code is required "
 
             "for check in, please bring this email on either your "
 
             "phone or on a print out.</p>\r\n        "
 
             "<p><img src=\"data:image/png;base64,{{ qrcode }}\" /></p>\r\n"
 
             "        <p>Backup Code: {{ code }}</p>\r\n    </body>\r\n</html>")
 
    template = BoardingPassTemplate(label="LCA2018",
 
                                    from_address="team@lca2018.org",
 
                                    subject="Your boarding pass for LCA2018, "
 
                                    "{{ user.attendee.attendeeprofilebase.attendeeprofile.name }}",
 
                                    body=body,
 
                                    html_body=html)
 
    template.save()
 

	
 

	
 

	
 
class Migration(migrations.Migration):
 

	
 
    initial = True
 

	
 
    dependencies = [
 
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 
    ]
 

	
 
    operations = [
 
        migrations.CreateModel(
 
            name='BoardingPass',
 
            fields=[
 
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 
                ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
 
                ('sent', models.DateTimeField(null=True, verbose_name='Sent')),
 
                ('to_address', models.EmailField(max_length=254, verbose_name='To address')),
 
                ('from_address', models.EmailField(max_length=254, verbose_name='From address')),
 
                ('subject', models.CharField(max_length=255, verbose_name='Subject')),
 
                ('body', models.TextField(verbose_name='Body')),
 
                ('html_body', models.TextField(null=True, verbose_name='HTML Body')),
 
            ],
 
            options={
 
                'permissions': (('view_boarding_pass', 'Can view sent boarding passes'), ('send_boarding_pass', 'Can send boarding passes')),
 
            },
 
        ),
 
        migrations.CreateModel(
 
            name='BoardingPassTemplate',
 
            fields=[
 
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 
                ('label', models.CharField(max_length=100, verbose_name='Label')),
 
                ('from_address', models.EmailField(max_length=254, verbose_name='From address')),
 
                ('subject', models.CharField(max_length=100, verbose_name='Subject')),
 
                ('body', models.TextField(verbose_name='Body')),
 
                ('html_body', models.TextField(null=True, verbose_name='HTML Body')),
 
            ],
 
            options={
 
                'verbose_name': 'Boarding Pass template',
 
                'verbose_name_plural': 'Boarding Pass templates',
 
            },
 
        ),
 
        migrations.CreateModel(
 
            name='CheckIn',
 
            fields=[
 
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 
                ('seen', models.DateTimeField(blank=True, null=True)),
 
                ('checked_in', models.DateTimeField(blank=True, null=True)),
 
                ('checkin_code', models.CharField(db_index=True, max_length=6, unique=True)),
 
                ('_checkin_code_png', models.TextField(blank=True, max_length=512, null=True)),
 
                ('boardingpass', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='regidesk.BoardingPass')),
 
                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 
            ],
 
            options={
 
                'permissions': (('view_checkin_details', "Can view the details of other user's checkins"),),
 
            },
 
        ),
 
        migrations.AddField(
 
            model_name='boardingpass',
 
            name='template',
 
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='regidesk.BoardingPassTemplate', verbose_name='Template'),
 
        ),
 
        migrations.RunPython(
 
            code=create_lca2018_template,
 
        ),
 
    ]
vendor/regidesk/regidesk/migrations/__init__.py
Show inline comments
 
new file 100644
vendor/regidesk/regidesk/models.py
Show inline comments
 
from __future__ import unicode_literals
 
# -*- 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.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
 
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
 

	
 

	
 
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)
 
    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)
 

	
 
    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)
 

	
 
    @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/_bp_prepare_help.html
Show inline comments
 
new file 100644
 
    <b>Body</b> may include the following variables which will be substituted in the email with a value
 
    specific to each proposal:
 
<ul>
 
    <li><code>{% templatetag openvariable %} user {% templatetag closevariable %}</code> e.g. {{ sample.user }}
 
    <li><code>{% templatetag openvariable %} qrcode {% templatetag closevariable %}</code> e.g. <code>&lt;img src=&quot;data:image/png;base64,{% templatetag openvariable %} qrcode {% templatetag closevariable %}&quot; /&gt;</code> produces <img src="data:image/png;base64,{{ sample.qrcode }}" />
 
    <li><code>{% templatetag openvariable %} qrcode_url {% templatetag closevariable %}</code> e.g. {{ sample.qrcode_url }}
 
    <li><code>{% templatetag openvariable %} code {% templatetag closevariable %}</code> e.g. {{ sample.code }}
 
    <li><code>{% templatetag openvariable %} user.attendee.ticket_type {% templatetag closevariable %}</code> e.g. {{ sample.user.attendee.ticket_type }}
 
</ul>
vendor/regidesk/regidesk/templates/regidesk/base.html
Show inline comments
 
new file 100644
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 

	
 
{% load i18n %}
 

	
 
{% block body_class %}reviews{% endblock %}
 

	
 
{% block body_outer %}
 
<div class="l-content-page">
 
<div class="l-content-page--richtext">
 
<div class="rich-text">
 

	
 
  <div class="row">
 
    <div class="col-md-10">
 
      {% block body %}
 
      {% endblock %}
 
    </div>
 
  </div>
 
</div></div></div>
 
{% endblock %}
 
{% block extra_script %}
 
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs/jszip-2.5.0/dt-1.10.16/b-1.4.2/b-colvis-1.4.2/b-flash-1.4.2/b-html5-1.4.2/b-print-1.4.2/cr-1.4.1/fc-3.2.3/fh-3.1.3/r-2.2.0/rg-1.0.2/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="https://cdn.datatables.net/v/bs/jszip-2.5.0/dt-1.10.16/b-1.4.2/b-colvis-1.4.2/b-html5-1.4.2/b-print-1.4.2/cr-1.4.1/fc-3.2.3/fh-3.1.3/r-2.2.0/rg-1.0.2/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 %}
vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html
Show inline comments
 
new file 100644
 
{% extends "regidesk/base.html" %}
 

	
 
{% load i18n %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% items_purchased as purchased %}
 
{% items_pending as pending %}
 
{% items_purchased 1 as ticket %}
 
{% total_items_purchased 2 as penguin_dinner_count %}
 
{% total_items_purchased 3 as speakers_dinner_count %}
 
{% total_items_purchased 4 as pdns_count %}
 
{% ticket_type as ticket_type %}
 

	
 
{% block body_class %}{{ block.super }} review-results{% endblock %}
 

	
 
{% block extra_style %}
 
{{ block.super }}
 
    <style type="text/css">
 
        .table-striped tbody tr.selected td {
 
            background-color: #F7F4E6;
 
        }
 
    </style>
 
{% endblock %}
 

	
 
{% block body %}
 

	
 
    <h1>Boarding Pass Overview</h1>
 

	
 
    <form class="form-horizontal" method="post" action="{% url "regidesk:boarding_prepare" %}">
 

	
 
        {% csrf_token %}
 
        <p>
 
            Select one or more attendees (<span class="action-counter">0</span> currently selected)
 
            <br/>
 
            then pick an email template
 
            <select name="template">
 
                <option value="">[blank]</option>
 
                {% for template in templates %}
 
                    <option value="{{ template.pk }}">{{ template.label }}</option>
 
                {% endfor %}
 
            </select>
 
            <br/>
 
            <button id="next-button" type="submit" class="btn btn-primary" disabled>Next <i class="fa fa-chevron-right"></i></button>
 
        </p>
 

	
 
        <table class="table table-striped table-bordered dataTable">
 
            <thead>
 
                <th><input type="checkbox" id="action-toggle"></th>
 
                <th class="toggle">#</th>
 
                <th>Attendee Name</th>
 
                <th>Ticket Type</th>
 
                <th class="toggle">Attendee email</th>
 
                <th class="toggle">Checkin Code</th>
 
                <th>Notified?</th>
 
            </thead>
 

	
 
            <tbody>
 
                {% for attendee in attendees %}
 
                    <tr>
 
                        <td><input class="action-select" type="checkbox" name="_selected_action" value="{{ attendee.pk }}"></td>
 
                        <td>{{ attendee.id }}</td>
 
                        <td>{{ attendee.attendeeprofilebase.attendeeprofile.name }}</td>
 
                        <td>{{ attendee.ticket_type }}</td>
 
                        <td>{{ attendee.user.email }}</td>
 
                        <td>{{ attendee.user.checkin.code }}</td>
 
                        <td>
 
                            {% if attendee.user.checkin %}
 
                              {% if attendee.user.checkin.boardingpass %}
 
                                Boarding pass sent<br/>
 
                              {% else %}
 
                                Checkin Created
 
                              {% endif %}
 
                            {% else %}
 
                              Pending
 
                            {% endif %}
 
                        </td>
 
                    </tr>
 
                {% endfor %}
 
            </tbody>
 
        </table>
 
    </form>
 
{% endblock %}
 

	
 
{% block extra_script %}
 
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs/jszip-2.5.0/dt-1.10.16/b-1.4.2/b-colvis-1.4.2/b-flash-1.4.2/b-html5-1.4.2/b-print-1.4.2/cr-1.4.1/fc-3.2.3/fh-3.1.3/r-2.2.0/rg-1.0.2/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="https://cdn.datatables.net/v/bs/jszip-2.5.0/dt-1.10.16/b-1.4.2/b-colvis-1.4.2/b-html5-1.4.2/b-print-1.4.2/cr-1.4.1/fc-3.2.3/fh-3.1.3/r-2.2.0/rg-1.0.2/datatables.min.js"></script>
 
    <script type="text/javascript">
 
        (function($) {
 
            $.fn.actions = function(opts) {
 
                var options = $.extend({}, $.fn.actions.defaults, opts);
 
                var actionCheckboxes = $(this);
 
                checker = function(checked) {
 
                    $(actionCheckboxes).prop("checked", checked)
 
                        .parent().parent().toggleClass(options.selectedClass, checked);
 
                }
 
                updateCounter = function() {
 
                    var sel = $(actionCheckboxes).filter(":checked").length;
 
                    $(options.counterContainer).html(sel);
 
                    $(options.allToggle).prop("checked", function() {
 
                        if (sel == actionCheckboxes.length) {
 
                            value = true;
 
                        } else {
 
                            value = false;
 
                        }
 
                        return value;
 
                    });
 
                    if (sel == 0) {
 
                        $("#next-button").prop("disabled", true);
 
                    } else {
 
                        $("#next-button").prop("disabled", false);
 
                    }
 
                }
 
                // Check state of checkboxes and reinit state if needed
 
                $(this).filter(":checked").each(function(i) {
 
                    $(this).parent().parent().toggleClass(options.selectedClass);
 
                    updateCounter();
 
                });
 
                $(options.allToggle).click(function() {
 
                    checker($(this).prop("checked"));
 
                    updateCounter();
 
                });
 
                lastChecked = null;
 
                $(actionCheckboxes).click(function(event) {
 
                    if (!event) { var event = window.event; }
 
                    var target = event.target ? event.target : event.srcElement;
 
                    if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) {
 
                        var inrange = false;
 
                        $(lastChecked).prop("checked", target.checked)
 
                            .parent().parent().toggleClass(options.selectedClass, target.checked);
 
                        $(actionCheckboxes).each(function() {
 
                            if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
 
                                inrange = (inrange) ? false : true;
 
                            }
 
                            if (inrange) {
 
                                $(this).prop("checked", target.checked)
 
                                    .parent().parent().toggleClass(options.selectedClass, target.checked);
 
                            }
 
                        });
 
                    }
 
                    $(target).parent().parent().toggleClass(options.selectedClass, target.checked);
 
                    lastChecked = target;
 
                    updateCounter();
 
                });
 
            }
 
            /* Setup plugin defaults */
 
            $.fn.actions.defaults = {
 
                counterContainer: "span.action-counter",
 
                allToggle: "#action-toggle",
 
                selectedClass: "selected"
 
            }
 
        })($);
 
        $(function() {
 
            $("tr input.action-select").actions();
 
        });
 
        $('.dataTable').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"]],
 
            "drawCallback": function( settings ) {
 
                $("tr input.action-select").actions();
 
            },
 
            "pageLength": 100,
 
            "colReorder": true,
 
            "buttons": [ {
 
              extend: 'collection',
 
              text: 'Export',
 
              buttons: ["copy", "csv", "print"]
 
            },
 
            { extend: 'collection',
 
              text: 'Columns',
 
              buttons: [
 
                { extend: 'columnsToggle',
 
                  columns: '.toggle' },
 
            ]
 
        }]});
 
    </script>
 
{% endblock %}
vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html
Show inline comments
 
new file 100644
 
{% extends "regidesk/base.html" %}
 

	
 
{% load i18n %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% items_purchased as purchased %}
 
{% items_pending as pending %}
 
{% items_purchased 1 as ticket %}
 
{% total_items_purchased 2 as penguin_dinner_count %}
 
{% total_items_purchased 3 as speakers_dinner_count %}
 
{% total_items_purchased 4 as pdns_count %}
 
{% ticket_type as ticket_type %}
 

	
 

	
 
{% block body %}
 
    <h1>BoardingPass Preparation</h1>
 

	
 
    <div class="row">
 
        <div class="col-md-4">
 
            <h2>Attendees</h2>
 
            <table class="table table-striped table-compact">
 
                {% for attendee in attendees %}
 
                  {% with profile=attendee.attendeeprofilebase.attendeeprofile %}
 
                    <tr>
 
                        <td>
 
                            <strong>{{ profile.name }}</strong> ({{ attendee.user.email }})<br />
 
                            {{ attendee.ticket_type }}<br/>
 
                            {{ profile.company }}<br/>
 
                            {{ profile.free_text_1 }}<br/>
 
                            {{ profile.free_text_2 }}<br/>
 
                        </td>
 
                    </tr>
 
                    {% endwith %}
 
                {% endfor %}
 
            </table>
 
        </div>
 
        <div class="col-md-6">
 
            <h2>Email</h2>
 

	
 
            <form class="form-horizontal" method="post" action="{% url "regidesk:boarding_send" %}">
 

	
 
                {% csrf_token %}
 

	
 
                <label>From Address</label>
 
                <input type="text" name="from_address" class="span5 label-required" value="{{ template.from_address }}" />
 
                <br/>
 
                <label>Subject</label>
 
                <input type="text" name="subject" class="span5" value="{{ subject }}" />
 
                <br/>
 
                <label>Template</label>
 
                <a href=" {% url 'admin:regidesk_boardingpasstemplate_change' template.id %}">
 
                    <input type="text" readonly class="form-control-plaintext span5" value="{{ template.label }}">
 
                </a>
 
                <div class="panel panel-default">
 
                    <ul class="nav nav-tabs panel-heading" id="templates" role="tablist">
 
                        <li class="nav-item"><a class="nav-link active" id="plain_template" data-toggle="tab" href="#plain" role="tab" aria-selected="true">Plaintext</a></li>
 
                        <li class="nav-item"><a class="nav-link" id="html_template" data-toggle="tab" href="#html" role="tab" aria-selected="false">HTML</a></li>
 
                    </ul>
 
                    <div class="tab-content panel-body" id="templatesContent">
 
                        <div class="tab-pane active monospace-text" id="plain" role="tabpanel" aria-labelledby="plain_template">{{ rendered_template.plain }}</div>
 
                        <div class="tab-pane fade show" id="html" role="tabpanel" aria-labelledby="html_template">{{ rendered_template.html }}</div>
 
                    </div>
 
                </div>
 
                <input type="hidden" name="notification_template" value="{{ template.pk }}" />
 
                <input type="hidden" name="attendees" value="{{ attendees }}" />
 

	
 
                {% include "regidesk/_bp_prepare_help.html" %}
 

	
 
                <button type="submit" class="btn btn-primary">Send {{ attendees|length }} Email{{ attendees|length|pluralize }}</button>
 
                <a class="btn" href="{% url "regidesk:boarding_overview" %}">Cancel</a>
 
            </form>
 
        </div>
 
    </form>
 
{% endblock %}
vendor/regidesk/regidesk/urls.py
Show inline comments
...
 
@@ -2,5 +2,11 @@ from django.conf.urls import url
 

	
 
from regidesk import views
 

	
 
app_name='regidesk'
 
urlpatterns = [
 
    url(r"^([A-Z0-9]{6}$)", views.boarding_overview, name="checkin_detail"),
 
    url(r"^([A-Z0-9]{6}).png$", views.checkin_png, name="checkin_png"),
 
    url(r"^overview/([a-z]+)?$", views.boarding_overview, name="boarding_overview"),
 
    url(r"^prepare_passes/", views.boarding_prepare, name="boarding_prepare"),
 
    url(r"^send_passes/", views.boarding_send, name="boarding_send")
 
]
vendor/regidesk/regidesk/views.py
Show inline comments
 
from regidesk import forms
 
from regidesk import models
 
import base64
 
import logging
 
from datetime import datetime
 

	
 
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 user_passes_test
 
from django.contrib.auth.decorators import permission_required, user_passes_test, login_required
 
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
 
from django.http import HttpResponse, HttpResponseBadRequest
 
from django.shortcuts import redirect, render
 
from django.template import Template, Context
 
from django.urls import reverse
 

	
 
from registrasion.models import commerce
 

	
 
from registrasion import util
 
from registrasion.models import commerce, people
 
from symposion.conference.models import Conference
 

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

	
 

	
 

	
 
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
 

	
 
@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
 
    )
 

	
 
    ticketholders = { ticket.invoice.user: ticket.product.name for ticket in tickets }
 

	
 
    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)
 

	
 
@login_required
 
def checkin_png(request, checkin_code):
 

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

	
 
    if not request.user.has_perm("regidesk.view_checkin_details"):
 
        if request.user != checkin.user:
 
            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
 

	
 
@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:
 
        return HttpResponseBadRequest()
 
    attendees = people.Attendee.objects.filter(pk__in=attendee_pks)
 
    attendees = attendees.select_related(
 
        "user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile")
 

	
 
    sample_checkin = CheckIn.objects.get_or_create(user=attendees[0].user)[0]
 
    rendered_template = {}
 
    sample_ctx = {}
 

	
 
    bp_template_pk = request.POST.get("template", "")
 
    if bp_template_pk:
 
        bp_template = BoardingPassTemplate.objects.get(pk=bp_template_pk)
 

	
 
        sample_ctx = {
 
            "user": sample_checkin.user,
 
            "boardingpass": sample_checkin.boardingpass,
 
            "code": sample_checkin.code,
 
            "qrcode": sample_checkin.qrcode,
 
            "qrcode_url": request.build_absolute_uri(
 
                reverse("regidesk:checkin_png", args=[sample_checkin.code])),
 
        }
 
        ctx = Context(sample_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)
 
    else:
 
        bp_template = None
 
        subject = 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
 
    request.session['template'] = bp_template.pk
 
    response = render(request, "regidesk/boardingpass_prepare.html", ctx)
 

	
 
    return response
 

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

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

	
 
    logging.debug(attendees)
 

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

	
 
    for attendee in attendees:
 

	
 
        user = attendee.user
 
        checkin = CheckIn.objects.get_or_create(user=user)
 
        ctx = {
 
            "user": user,
 
            "checkin": user.checkin,
 
            "code": user.checkin.code,
 
            "qrcode": user.checkin.qrcode,
 
            "qrcode_url": request.build_absolute_uri(
 
                reverse("regidesk:checkin_png", args=[user.checkin.code])),
 
        }
 
        ctx = Context(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()
 

	
 
        with transaction.atomic():
 

	
 
            msg = EmailMultiAlternatives(
 
                bpass.subject,
 
                bpass.body,
 
                bpass.from_address,
 
                [bpass.to_address,],
 
            )
 
            if bpass.html_body:
 
                msg.attach_alternative(bpass.html_body, "text/html")
 

	
 
            msg.send()
 

	
 
            bpass.sent = datetime.now()
 
            bpass.save()
 
            messages.success(request, "Sent boarding pass to %s" % attendee)
 
            request.session['boarding_attendees'].remove(attendee.pk)
 

	
 
    return redirect("regidesk:boarding_overview")
vendor/regidesk/requirements.txt
Show inline comments
 
django-countries>=4.0
 
requests>=2.11.1
 

	
 
pypng
 
pyqrcode
vendor/regidesk/setup.py
Show inline comments
...
 
@@ -18,7 +18,7 @@ setup(
 
    name="registrasion-desk",
 
    author="James Polley",
 
    author_email="jamezpolley@gmail",
 
    version=registripe.__version__,
 
    version=regidesk.__version__,
 
    description="Registration desk functionality for registrasion",
 
    url="http://gitlab.com/lca2018/registrasion-desk/",
 
    packages=find_packages(),
vendored_requirements.txt
Show inline comments
 
vendor/registrasion
 
vendor/registripe
 
vendor/regidesk
0 comments (0 inline, 0 general)