Add Code 128 format barcode to checkin to cater for barcode scanners at registration. Add flagged link to boarding pass to dashboard.
388 lines
13 KiB
Python
388 lines
13 KiB
Python
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 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
|
|
|
|
# QR code
|
|
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"/>'
|
|
|
|
# Barcode
|
|
barcode = checkin.barcode
|
|
barcode_string ='<img src="data:image/png;base64,' + barcode + '"/>'
|
|
not_barcode_string = '<img src="cid:barcode.png"/>'
|
|
|
|
boardingpass_body = boardingpass.html_body.replace(not_qrcode_string, qrcode_string)
|
|
boardingpass_body = boardingpass.html_body.replace(not_barcode_string, barcode_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
|
|
|
|
|
|
def checkin_barcode_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="barcode.png"'
|
|
|
|
barcode = base64.b64decode(checkin.barcode)
|
|
response.write(barcode)
|
|
|
|
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:
|
|
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": sample_checkin.qrcode,
|
|
"qrcode_url": checkin_qrcode_url(sample_checkin.code),
|
|
"barcode": sample_checkin.barcode,
|
|
}
|
|
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),
|
|
"barcode": user.checkin.barcode,
|
|
}
|
|
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")
|
|
|
|
barcode = base64.b64decode(user.checkin.barcode)
|
|
msg.attach(filename="barcode.png", content=barcode, 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'))
|