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)
This commit is contained in:
		
							parent
							
								
									44cdd088be
								
							
						
					
					
						commit
						e726ff21a8
					
				
					 16 changed files with 746 additions and 11 deletions
				
			
		|  | @ -209,11 +209,14 @@ INSTALLED_APPS = [ | ||||||
|     # Registrasion |     # Registrasion | ||||||
|     "registrasion", |     "registrasion", | ||||||
| 
 | 
 | ||||||
|     # Registrasion-stipe |     # Registrasion-stripe | ||||||
|     "pinax.stripe", |     "pinax.stripe", | ||||||
|     "django_countries", |     "django_countries", | ||||||
|     "registripe", |     "registripe", | ||||||
| 
 | 
 | ||||||
|  |     #registrasion-desk | ||||||
|  |     "regidesk", | ||||||
|  | 
 | ||||||
|     # admin - required by registrasion ?? |     # admin - required by registrasion ?? | ||||||
|     "nested_admin", |     "nested_admin", | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ urlpatterns = [ | ||||||
|     url(r'^tickets/payments/', include('registripe.urls')), |     url(r'^tickets/payments/', include('registripe.urls')), | ||||||
|     url(r'^tickets/', include('registrasion.urls')), |     url(r'^tickets/', include('registrasion.urls')), | ||||||
|     url(r'^nested_admin/', include('nested_admin.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'^pages/', include('django.contrib.flatpages.urls')), | ||||||
| 
 | 
 | ||||||
|     url(r'^dashboard/', RedirectView.as_view(url='/')), |     url(r'^dashboard/', RedirectView.as_view(url='/')), | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								vendor/regidesk/MANIFEST.in
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/regidesk/MANIFEST.in
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | recursive-include regidesk/templates * | ||||||
							
								
								
									
										23
									
								
								vendor/regidesk/regidesk/admin.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								vendor/regidesk/regidesk/admin.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,26 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| 
 | 
 | ||||||
| # Register your models here. | # 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'] | ||||||
|  | ) | ||||||
|  |  | ||||||
							
								
								
									
										99
									
								
								vendor/regidesk/regidesk/migrations/0001_initial.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								vendor/regidesk/regidesk/migrations/0001_initial.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | ||||||
|  | # -*- 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, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										0
									
								
								vendor/regidesk/regidesk/migrations/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								vendor/regidesk/regidesk/migrations/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										108
									
								
								vendor/regidesk/regidesk/models.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										108
									
								
								vendor/regidesk/regidesk/models.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,4 +1,110 @@ | ||||||
| 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 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.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 | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								vendor/regidesk/regidesk/templates/regidesk/_bp_prepare_help.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/regidesk/regidesk/templates/regidesk/_bp_prepare_help.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  |     <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><img src="data:image/png;base64,{% templatetag openvariable %} qrcode {% templatetag closevariable %}" /></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> | ||||||
							
								
								
									
										46
									
								
								vendor/regidesk/regidesk/templates/regidesk/base.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/regidesk/regidesk/templates/regidesk/base.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | {% 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 %} | ||||||
							
								
								
									
										179
									
								
								vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | ||||||
|  | {% 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 %} | ||||||
							
								
								
									
										74
									
								
								vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | {% 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 %} | ||||||
							
								
								
									
										6
									
								
								vendor/regidesk/regidesk/urls.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/regidesk/regidesk/urls.py
									
										
									
									
										vendored
									
									
								
							|  | @ -2,5 +2,11 @@ from django.conf.urls import url | ||||||
| 
 | 
 | ||||||
| from regidesk import views | from regidesk import views | ||||||
| 
 | 
 | ||||||
|  | app_name='regidesk' | ||||||
| urlpatterns = [ | 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") | ||||||
| ] | ] | ||||||
|  |  | ||||||
							
								
								
									
										199
									
								
								vendor/regidesk/regidesk/views.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										199
									
								
								vendor/regidesk/regidesk/views.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,20 +1,207 @@ | ||||||
| from regidesk import forms | import base64 | ||||||
| from regidesk import models | import logging | ||||||
|  | from datetime import datetime | ||||||
| 
 | 
 | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
|  | from django.core.mail import EmailMultiAlternatives | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib import messages | 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 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 Http404 | ||||||
| from django.http import HttpResponse | from django.http import HttpResponse, HttpResponseBadRequest | ||||||
| from django.shortcuts import redirect, render | 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 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): | def _staff_only(user): | ||||||
|     ''' Returns true if the user is staff. ''' |     ''' Returns true if the user is staff. ''' | ||||||
|     return 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") | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								vendor/regidesk/requirements.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/regidesk/requirements.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,4 @@ | ||||||
| django-countries>=4.0 | django-countries>=4.0 | ||||||
| requests>=2.11.1 | requests>=2.11.1 | ||||||
| 
 | pypng | ||||||
|  | pyqrcode | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								vendor/regidesk/setup.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/regidesk/setup.py
									
										
									
									
										vendored
									
									
								
							|  | @ -18,7 +18,7 @@ setup( | ||||||
|     name="registrasion-desk", |     name="registrasion-desk", | ||||||
|     author="James Polley", |     author="James Polley", | ||||||
|     author_email="jamezpolley@gmail", |     author_email="jamezpolley@gmail", | ||||||
|     version=registripe.__version__, |     version=regidesk.__version__, | ||||||
|     description="Registration desk functionality for registrasion", |     description="Registration desk functionality for registrasion", | ||||||
|     url="http://gitlab.com/lca2018/registrasion-desk/", |     url="http://gitlab.com/lca2018/registrasion-desk/", | ||||||
|     packages=find_packages(), |     packages=find_packages(), | ||||||
|  |  | ||||||
|  | @ -1,2 +1,3 @@ | ||||||
| vendor/registrasion | vendor/registrasion | ||||||
| vendor/registripe | vendor/registripe | ||||||
|  | vendor/regidesk | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 James Polley
						James Polley