d9e8a72d14
Determine traits based on ticket type. Set different expiry of token for miniconf only tickets.
357 lines
12 KiB
Python
357 lines
12 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
|
|
qrcode_url = request.build_absolute_uri(reverse("regidesk:checkin_png", args=[checkin.code]))
|
|
qrcode = checkin.qrcode
|
|
qrcode_string ='<img src="data:image/png;base64,' + qrcode + '"/>'
|
|
not_qrcode_string = '<img src="cid:qrcode.png"/>'
|
|
|
|
boardingpass_body = boardingpass.html_body.replace(not_qrcode_string, qrcode_string)
|
|
ctx = { 'attendee': user.attendee,
|
|
'boardingpass_body': boardingpass_body,
|
|
'boardingpass': boardingpass
|
|
}
|
|
|
|
response = render(request, "regidesk/boardingpass.html", ctx)
|
|
return response
|
|
|
|
|
|
@permission_required("regidesk.view_boarding_pass")
|
|
def boarding_overview(request, boarding_state="pending"):
|
|
|
|
tickets = commerce.LineItem.objects.select_related(
|
|
"invoice","invoice__user__attendee","product__category"
|
|
).filter(
|
|
invoice__status=commerce.Invoice.STATUS_PAID,
|
|
product__category=settings.TICKET_PRODUCT_CATEGORY,
|
|
price__gte=0
|
|
)
|
|
|
|
#print(datetime.now())
|
|
ticketholders = ( ticket.invoice.user for ticket in tickets )
|
|
#print(datetime.now())
|
|
|
|
attendees = people.Attendee.objects.select_related(
|
|
"attendeeprofilebase",
|
|
"attendeeprofilebase__attendeeprofile",
|
|
"user",
|
|
"user__checkin"
|
|
).filter(user__in=ticketholders)
|
|
|
|
profiles = AttendeeProfile.objects.filter(
|
|
attendee__in=attendees
|
|
).select_related(
|
|
"attendee", "attendee__user",
|
|
)
|
|
profiles_by_attendee = dict((i.attendee, i) for i in profiles)
|
|
|
|
bp_templates = BoardingPassTemplate.objects.all()
|
|
|
|
ctx = {
|
|
"boarding_state": boarding_state,
|
|
"attendees": attendees,
|
|
"profiles": profiles_by_attendee,
|
|
"templates": bp_templates,
|
|
}
|
|
|
|
return render(request, "regidesk/boardingpass_overview.html", ctx)
|
|
|
|
def checkin_png(request, checkin_code):
|
|
|
|
checkin = CheckIn.objects.get(checkin_code=checkin_code)
|
|
if not checkin:
|
|
raise Http404()
|
|
|
|
response = HttpResponse()
|
|
response["Content-Type"] = "image/png"
|
|
response["Content-Disposition"] = 'inline; filename="qrcode.png"'
|
|
|
|
qrcode = base64.b64decode(checkin.qrcode)
|
|
response.write(qrcode)
|
|
|
|
return response
|
|
|
|
|
|
def checkin_qrcode_url(code):
|
|
""" Generate the QR code URL for the current site.
|
|
eg. https://rego.linux.conf.au/checkin/<code>.png
|
|
"""
|
|
|
|
# Cannot use build_absolute_uri as external URL is not known in docker
|
|
# qrcode_url = request.build_absolute_uri(
|
|
# reverse("regidesk:checkin_png", args=[code]))
|
|
|
|
current_site = Site.objects.get_current()
|
|
qrcode_url = "https://{}{}".format(current_site,
|
|
reverse("regidesk:checkin_png", args=[code]))
|
|
return qrcode_url
|
|
|
|
|
|
@permission_required("regidesk.send_boarding_pass")
|
|
def boarding_prepare(request):
|
|
|
|
attendee_pks = []
|
|
try:
|
|
for pk in request.POST.getlist("_selected_action"):
|
|
attendee_pks.append(int(pk))
|
|
except ValueError:
|
|
messages.warning(request, ValueError)
|
|
return redirect("/checkin/overview")
|
|
attendees = people.Attendee.objects.filter(pk__in=attendee_pks)
|
|
attendees = attendees.select_related(
|
|
"user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile")
|
|
|
|
try:
|
|
sample_checkin = CheckIn.objects.get_or_create(user=attendees[0].user)[0]
|
|
except:
|
|
messages.warning(request, "Couldn't find a sample checking - did you add a user?")
|
|
return redirect("/checkin/overview")
|
|
|
|
rendered_template = {}
|
|
sample_ctx = {}
|
|
|
|
bp_template_pk = request.POST.get("template", "")
|
|
if not bp_template_pk:
|
|
messages.warning(request, "Need to choose a template")
|
|
return redirect("/checkin/overview")
|
|
|
|
bp_template = BoardingPassTemplate.objects.get(pk=bp_template_pk)
|
|
if bp_template:
|
|
sample_ctx = {
|
|
"user": sample_checkin.user,
|
|
"boardingpass": sample_checkin.boardingpass,
|
|
"code": sample_checkin.code,
|
|
"qrcode": '<img src="data:image/png;base64,' + sample_checkin.qrcode + '"/>',
|
|
"qrcode_url": checkin_qrcode_url(sample_checkin.code),
|
|
}
|
|
ctx = Context(sample_ctx)
|
|
ctx["invoices"] = invoices(ctx)
|
|
ctx["items_pending"] = items_pending(ctx)
|
|
ctx["items_purchased"] = items_purchased(ctx)
|
|
ctx["missing_categories"] = missing_categories(ctx)
|
|
|
|
subject = Template(bp_template.subject).render(ctx)
|
|
rendered_template['plain'] = Template(bp_template.body).render(ctx)
|
|
rendered_template['html'] = Template(bp_template.html_body).render(ctx)
|
|
request.session['template'] = bp_template.pk
|
|
else:
|
|
subject = None
|
|
request.session['template'] = None
|
|
|
|
ctx = {
|
|
"attendees": attendees,
|
|
"template": bp_template,
|
|
"attendee_pks": attendee_pks,
|
|
"rendered_template": rendered_template,
|
|
"subject": subject,
|
|
"sample": sample_ctx,
|
|
}
|
|
|
|
request.session.set_expiry=(300)
|
|
request.session['boarding_attendees'] = attendee_pks
|
|
return render(request, "regidesk/boardingpass_prepare.html", ctx)
|
|
|
|
def prepare_boarding_pass(user, template, attendee=None):
|
|
|
|
if attendee:
|
|
user = attendee.user
|
|
else:
|
|
user = user
|
|
attendee=user.attendee
|
|
checkin = CheckIn.objects.get_or_create(user=user)
|
|
ctx = {
|
|
"user": user,
|
|
"checkin": user.checkin,
|
|
"code": user.checkin.code,
|
|
"qrcode": user.checkin.qrcode,
|
|
"qrcode_url": checkin_qrcode_url(user.checkin.code),
|
|
}
|
|
ctx = Context(ctx)
|
|
ctx["invoices"] = invoices(ctx)
|
|
ctx["items_pending"] = items_pending(ctx)
|
|
ctx["items_purchased"] = items_purchased(ctx)
|
|
ctx["missing_categories"] = missing_categories(ctx)
|
|
|
|
subject = Template(template.subject).render(ctx)
|
|
body = Template(template.body).render(ctx)
|
|
if template.html_body:
|
|
html_body = Template(template.html_body).render(ctx)
|
|
else:
|
|
html_body = None
|
|
|
|
bpass = BoardingPass(template=template, to_address=user.email,
|
|
from_address=template.from_address,
|
|
subject=subject, body=body,
|
|
html_body=html_body
|
|
)
|
|
bpass.save()
|
|
|
|
if user.checkin.boardingpass:
|
|
user.checkin.boardingpass.delete()
|
|
user.checkin.boardingpass = bpass
|
|
user.checkin.save()
|
|
|
|
return bpass
|
|
|
|
def send_boarding_pass(bpass, user):
|
|
msg = EmailMultiAlternatives(
|
|
bpass.subject,
|
|
bpass.body,
|
|
bpass.from_address,
|
|
[bpass.to_address,],
|
|
)
|
|
msg.content_subtype="plain"
|
|
msg.mixed_subtype="related"
|
|
if bpass.html_body:
|
|
msg.attach_alternative(bpass.html_body, "text/html")
|
|
qrcode = base64.b64decode(user.checkin.qrcode)
|
|
msg.attach(filename="qrcode.png", content=qrcode, mimetype="image/png")
|
|
|
|
msg.send()
|
|
bpass.sent = datetime.now()
|
|
bpass.save()
|
|
|
|
@permission_required("regidesk.send_boarding_pass")
|
|
def boarding_send(request):
|
|
|
|
BOARDING_GROUP = getattr(settings, "REGIDESK_BOARDING_GROUP", None)
|
|
if BOARDING_GROUP and Group.objects.filter(name=BOARDING_GROUP):
|
|
boarding_users = User.objects.filter(groups__name=BOARDING_GROUP)
|
|
else:
|
|
boarding_users = User.objects.all()
|
|
|
|
attendees = people.Attendee.objects.filter(pk__in=request.session['boarding_attendees'])
|
|
attendees = attendees.select_related(
|
|
"user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile")
|
|
|
|
logging.debug(len(attendees))
|
|
logging.debug(attendees)
|
|
|
|
template_pk = request.session['template']
|
|
template = BoardingPassTemplate.objects.get(pk=template_pk)
|
|
|
|
for attendee in attendees:
|
|
try:
|
|
user = attendee.user
|
|
bpass = prepare_boarding_pass(user, template, attendee)
|
|
if user in boarding_users:
|
|
send_boarding_pass(bpass, user)
|
|
messages.success(request, "Sent boarding pass to %s" % attendee)
|
|
except Exception as err:
|
|
logging.error("Error sending boarding pass for %s: %s", (attendee, err))
|
|
messages.warning(request, "Could not send boarding pass to %s" % attendee)
|
|
|
|
request.session['boarding_attendees'].remove(attendee.pk)
|
|
|
|
return redirect("regidesk:boarding_overview")
|
|
|
|
|
|
class CheckInLanding(PermissionRequiredMixin, TemplateView):
|
|
template_name = 'regidesk/ci_landing.html'
|
|
permission_required = 'regidesk.view_boarding_pass'
|
|
|
|
|
|
@csrf_exempt
|
|
@permission_required("regidesk.view_boarding_pass")
|
|
def check_in_overview(request, access_code):
|
|
check_in = CheckIn.objects.filter(
|
|
checkin_code=access_code,
|
|
)
|
|
if not check_in:
|
|
# yea it's a 200...
|
|
return render(request, "regidesk/ci_code_404.html", {})
|
|
check_in = check_in[0]
|
|
if request.method == 'POST':
|
|
if 'checkin' in request.POST:
|
|
check_in.mark_checked_in()
|
|
elif 'badge' in request.POST:
|
|
check_in.mark_badge_printed()
|
|
elif 'schwag' in request.POST:
|
|
check_in.mark_schwag_given()
|
|
elif 'bulk' in request.POST:
|
|
check_in.bulk_mark_given()
|
|
elif 'exception' in request.POST:
|
|
check_in.set_exception(request.POST['exception'])
|
|
elif 'unbadge' in request.POST:
|
|
check_in.unset_badge()
|
|
return redirect(request.path)
|
|
ctx = {
|
|
'check_in': check_in,
|
|
'user': check_in.user,
|
|
}
|
|
return render(request, "regidesk/ci_overview.html", ctx)
|
|
|
|
|
|
@permission_required("regidesk.view_boarding_pass")
|
|
def checken_in_badge(request, access_code):
|
|
check_in = CheckIn.objects.filter(
|
|
checkin_code=access_code,
|
|
)
|
|
if not check_in:
|
|
# yea it's a 200...
|
|
return render(request, "regidesk/ci_code_404.html", {})
|
|
badge = render_badge(check_in[0].user, format="svg", overlay=True)
|
|
return badge
|
|
|
|
@login_required
|
|
def redir_main(request):
|
|
if request.user.has_perm('regidesk.view_boarding_pass'):
|
|
return redirect(reverse('regidesk:boarding_overview'))
|
|
return redirect(reverse('regidesk:boardingpass'))
|