Polishing finding tickets by scanning and marking stuff as handed out/checked in.
This commit is contained in:
parent
7be4aa6545
commit
94a142f6b9
39
src/backoffice/static/js/ticket_scan.js
Normal file
39
src/backoffice/static/js/ticket_scan.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const search_form = document.getElementById("search_form");
|
||||||
|
const ticket_token_input = document.getElementById("ticket_token_input");
|
||||||
|
const scan_again = document.getElementById("scan_again");
|
||||||
|
|
||||||
|
const check_in_input = document.getElementById("check_in_input");
|
||||||
|
const hand_out_badge_input = document.getElementById("hand_out_badge_input");
|
||||||
|
const check_in_form = document.getElementById("check_in_form");
|
||||||
|
|
||||||
|
search_form.onsubmit = submit;
|
||||||
|
|
||||||
|
function submit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (ticket_token_input.value === "#clear") {
|
||||||
|
window.location.replace(window.location.pathname);
|
||||||
|
} else if (ticket_token_input.value === "#check-in") {
|
||||||
|
check_in_input.checked = true;
|
||||||
|
check_in_form.submit();
|
||||||
|
} else if (ticket_token_input.value === "#hand-out-badge") {
|
||||||
|
hand_out_badge_input.checked = true;
|
||||||
|
check_in_form.submit();
|
||||||
|
} else if (ticket_token_input.value.length === 65) {
|
||||||
|
search_form.submit();
|
||||||
|
} else {
|
||||||
|
scan_again.removeAttribute("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", event => {
|
||||||
|
if (event.key === "#") {
|
||||||
|
ticket_token_input.value = "";
|
||||||
|
ticket_token_input.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -16,13 +16,13 @@
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% if perms.camps.infoteam_permission %}
|
{% if perms.camps.infoteam_permission %}
|
||||||
<h3>Info Team</h3>
|
<h3>Info Team</h3>
|
||||||
<a href="{% url 'backoffice:user_interaction' camp_slug=camp.slug %}"
|
<a href="{% url 'backoffice:scan_tickets' camp_slug=camp.slug %}"
|
||||||
class="list-group-item">
|
class="list-group-item">
|
||||||
<h4 class="list-group-item-heading">
|
<h4 class="list-group-item-heading">
|
||||||
User stuff
|
Scan tickets
|
||||||
</h4>
|
</h4>
|
||||||
<p class="list-group-item-text">
|
<p class="list-group-item-text">
|
||||||
Use this to get everything related to a user
|
Use this to get scan tickets
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
118
src/backoffice/templates/tickets/scan.html
Normal file
118
src/backoffice/templates/tickets/scan.html
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load qrcode %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form id="search_form" method="POST" action="">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Scan the ticket!</h3>
|
||||||
|
<input type="text" name="ticket_token" id="ticket_token_input" autocomplete="off" style="color: #fff; background: #fff; border: 0; height: 0; width: 0;"/>
|
||||||
|
|
||||||
|
<div id="scan_again" hidden>
|
||||||
|
Scan again!
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{% if ticket %}
|
||||||
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Type:</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.ticket_type }}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Used?:</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.used }}
|
||||||
|
|
||||||
|
{% if ticket.ticket_type.includes_badge %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Badge handed out?:</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.badge_handed_out }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ticket.product %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Product:</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.product }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Order:</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.order }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ticket.sponsor %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Sponsor</strong>
|
||||||
|
<td>
|
||||||
|
{{ ticket.sponsor }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form id="check_in_form" method="POST" action="">{% csrf_token %}
|
||||||
|
<input type="checkbox"
|
||||||
|
name="check_in_ticket_id"
|
||||||
|
id="check_in_input"
|
||||||
|
value="{{ ticket.pk }}"
|
||||||
|
style="color: #fff; background: #fff; border: 0; height: 0; width: 0; opacity: 0;" />
|
||||||
|
|
||||||
|
<input type="checkbox"
|
||||||
|
name="badge_ticket_id"
|
||||||
|
id="hand_out_badge_input"
|
||||||
|
value="{{ ticket.pk }}"
|
||||||
|
style="color: #fff; background: #fff; border: 0; height: 0; width: 0; opacity: 0;" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% qr_code "clear" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not ticket.used %}
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% qr_code "check-in" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ticket.ticket_type.includes_badge and not ticket.badge_handed_out %}
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% qr_code "hand-out-badge" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script src="{% static 'js/ticket_scan.js' %}"></script>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% load static from staticfiles %}
|
|
||||||
{% load qrcode %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<form id="search_form" method="POST" action="">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h3>Scan the ticket!</h3>
|
|
||||||
<input type="text" name="ticket_token" id="ticket_token_input" autocomplete="off" style="color: #fff; background: #fff; border: 0; height: 0; width: 0;"/>
|
|
||||||
|
|
||||||
<span id="scan_again" hidden>Scan again!</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% if ticket %}
|
|
||||||
{{ ticket }}<br />
|
|
||||||
<br />
|
|
||||||
Used?: {{ ticket.used }}
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
<form id="check_in_form" method="POST" action="">{% csrf_token %}
|
|
||||||
<input type="input" name="check_in_ticket_id" id="check_in_input" value="{{ ticket.pk }}" style="color: #fff; background: #fff; border: 0; height: 0; width: 0;"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
{% qr_code "clear" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
{% qr_code "check-in" %}
|
|
||||||
</div>
|
|
||||||
</divc>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const search_form = document.getElementById('search_form');
|
|
||||||
const ticket_token_input = document.getElementById('ticket_token_input');
|
|
||||||
const scan_again = document.getElementById('scan_again');
|
|
||||||
|
|
||||||
const check_in_input = document.getElementById('check_in_input');
|
|
||||||
const check_in_form = document.getElementById('check_in_form');
|
|
||||||
|
|
||||||
search_form.onsubmit = submit;
|
|
||||||
|
|
||||||
function submit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
console.log(ticket_token_input.value);
|
|
||||||
|
|
||||||
if(ticket_token_input.value === "#clear") {
|
|
||||||
window.location.replace(window.location.pathname);
|
|
||||||
} else if(ticket_token_input.value === "#check-in") {
|
|
||||||
check_in_input.checked = true;
|
|
||||||
check_in_form.submit();
|
|
||||||
} else if(ticket_token_input.value.length === 65) {
|
|
||||||
search_form.submit();
|
|
||||||
} else {
|
|
||||||
scan_again.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
|
||||||
if(event.key === '#') {
|
|
||||||
ticket_token_input.value = "";
|
|
||||||
ticket_token_input.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ urlpatterns = [
|
||||||
path("", BackofficeIndexView.as_view(), name="index"),
|
path("", BackofficeIndexView.as_view(), name="index"),
|
||||||
# infodesk
|
# infodesk
|
||||||
path(
|
path(
|
||||||
"user/", include([path("", SearchForUser.as_view(), name="user_interaction")])
|
"tickets/", include([path("", ScanTicketsView.as_view(), name="scan_tickets")])
|
||||||
),
|
),
|
||||||
path("product_handout/", ProductHandoutView.as_view(), name="product_handout"),
|
path("product_handout/", ProductHandoutView.as_view(), name="product_handout"),
|
||||||
path("badge_handout/", BadgeHandoutView.as_view(), name="badge_handout"),
|
path("badge_handout/", BadgeHandoutView.as_view(), name="badge_handout"),
|
||||||
|
|
|
@ -546,28 +546,71 @@ class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchForUser(TemplateView):
|
def _ticket_getter_by_token(token):
|
||||||
template_name = "user/search.html"
|
for ticket_class in [ShopTicket, SponsorTicket, DiscountTicket]:
|
||||||
|
try:
|
||||||
|
return ticket_class.objects.get(token=token), False
|
||||||
|
except ticket_class.DoesNotExist:
|
||||||
|
try:
|
||||||
|
return ticket_class.objects.get(badge_token=token), True
|
||||||
|
except ticket_class.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
def post(self, request, **kwargs):
|
|
||||||
check_in_ticket_id = request.POST.get("check_in_ticket_id")
|
|
||||||
if check_in_ticket_id:
|
|
||||||
ticket_to_check_in = ShopTicket.objects.get(pk=check_in_ticket_id)
|
|
||||||
ticket_to_check_in.used = True
|
|
||||||
ticket_to_check_in.save()
|
|
||||||
messages.info(request, "Ticket checked-in!")
|
|
||||||
|
|
||||||
return super().get(request, **kwargs)
|
def _ticket_getter_by_pk(pk):
|
||||||
|
for ticket_class in [ShopTicket, SponsorTicket, DiscountTicket]:
|
||||||
|
try:
|
||||||
|
return ticket_class.objects.get(pk=pk)
|
||||||
|
except ticket_class.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScanTicketsView(TemplateView):
|
||||||
|
template_name = "tickets/scan.html"
|
||||||
|
|
||||||
|
ticket = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
ticket_token = self.request.POST.get("ticket_token")
|
if self.ticket:
|
||||||
if ticket_token:
|
context["ticket"] = self.ticket
|
||||||
try:
|
|
||||||
ticket = ShopTicket.objects.get(token=ticket_token[1:])
|
elif "ticket_token" in self.request.POST:
|
||||||
|
|
||||||
|
# Slice to get rid of the first character which is a '#'
|
||||||
|
ticket_token = self.request.POST.get("ticket_token")[1:]
|
||||||
|
|
||||||
|
ticket, is_badge = _ticket_getter_by_token(ticket_token)
|
||||||
|
|
||||||
|
if ticket:
|
||||||
context["ticket"] = ticket
|
context["ticket"] = ticket
|
||||||
except ShopTicket.DoesNotExist:
|
context["is_badge"] = is_badge
|
||||||
|
else:
|
||||||
messages.warning(self.request, "Ticket not found!")
|
messages.warning(self.request, "Ticket not found!")
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def post(self, request, **kwargs):
|
||||||
|
if 'check_in_ticket_id' in request.POST:
|
||||||
|
self.ticket = self.check_in_ticket(request)
|
||||||
|
elif 'badge_ticket_id' in request.POST:
|
||||||
|
self.ticket = self.hand_out_badge(request)
|
||||||
|
|
||||||
|
return super().get(request, **kwargs)
|
||||||
|
|
||||||
|
def check_in_ticket(self, request):
|
||||||
|
check_in_ticket_id = request.POST.get("check_in_ticket_id")
|
||||||
|
ticket_to_check_in = _ticket_getter_by_pk(check_in_ticket_id)
|
||||||
|
ticket_to_check_in.used = True
|
||||||
|
ticket_to_check_in.save()
|
||||||
|
messages.info(request, "Ticket checked-in!")
|
||||||
|
return ticket_to_check_in
|
||||||
|
|
||||||
|
def hand_out_badge(self, request):
|
||||||
|
badge_ticket_id = request.POST.get('badge_ticket_id')
|
||||||
|
ticket_to_handout_badge_for = _ticket_getter_by_pk(badge_ticket_id)
|
||||||
|
ticket_to_handout_badge_for.badge_handed_out = True
|
||||||
|
ticket_to_handout_badge_for.save()
|
||||||
|
messages.info(request, "Badge marked as handed out!")
|
||||||
|
return ticket_to_handout_badge_for
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<br>
|
<br>
|
||||||
{% elif ticket.sponsor %}
|
{% elif ticket.sponsor %}
|
||||||
<h3>Sponsor: {{ ticket.sponsor.name }} </h3>
|
<h3>Sponsor: {{ ticket.sponsor.name }} </h3>
|
||||||
<img src="{% static 'img/sponsors/' %}{{ sponsor.logo_filename }}"></img>
|
<img src="{% static 'img/sponsors/' %}{{ ticket.sponsor.logo_filename }}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if ticket.used %}
|
{% if ticket.used %}
|
||||||
|
|
|
@ -11,13 +11,13 @@ register = template.Library()
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def qr_code(value):
|
def qr_code(value):
|
||||||
stream = io.BytesIO()
|
stream = io.BytesIO()
|
||||||
img = qrcode.make("#" + value, box_size=5)
|
img = qrcode.make("#" + value, box_size=7)
|
||||||
img.save(stream, "PNG")
|
img.save(stream, "PNG")
|
||||||
data = base64.b64encode(stream.getvalue())
|
data = base64.b64encode(stream.getvalue())
|
||||||
|
|
||||||
return mark_safe(
|
return mark_safe(
|
||||||
"<figure>"
|
"<figure style='text-align: center;'>"
|
||||||
'<img src="data:image/png;base64,{}" alt="">'
|
'<img src="data:image/png;base64,{}" alt="">'
|
||||||
"<figcaption>{}</figcaption>"
|
"<figcaption style='text-align: center;'>{}</figcaption>"
|
||||||
"</figure>".format(data.decode(), value)
|
"</figure>".format(data.decode(), value)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue