Initial work on a more lean infodesk.
This commit is contained in:
parent
24c95a82fd
commit
5911d2042b
|
@ -16,17 +16,14 @@
|
|||
<div class="list-group">
|
||||
{% if perms.camps.infoteam_permission %}
|
||||
<h3>Info Team</h3>
|
||||
<a href="{% url 'backoffice:product_handout' camp_slug=camp.slug %}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">Hand Out Products</h4>
|
||||
<p class="list-group-item-text">Use this view to mark products such as merchandise, cabins, fridges and so on as handed out.</p>
|
||||
</a>
|
||||
<a href="{% url 'backoffice:ticket_checkin' camp_slug=camp.slug %}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">Check-In Tickets</h4>
|
||||
<p class="list-group-item-text">Use this view to check-in tickets when participants arrive.</p>
|
||||
</a>
|
||||
<a href="{% url 'backoffice:badge_handout' camp_slug=camp.slug %}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">Hand Out Badges</h4>
|
||||
<p class="list-group-item-text">Use this view to mark badges as handed out.</p>
|
||||
<a href="{% url 'backoffice:user_interaction' camp_slug=camp.slug %}"
|
||||
class="list-group-item">
|
||||
<h4 class="list-group-item-heading">
|
||||
User stuff
|
||||
</h4>
|
||||
<p class="list-group-item-text">
|
||||
Use this to get everything related to a user
|
||||
</p>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
|
|
13
src/backoffice/templates/user/order_all_actions.html
Normal file
13
src/backoffice/templates/user/order_all_actions.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static from staticfiles %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
Orders
|
||||
|
||||
Tickets (incl. badges)
|
||||
|
||||
Merchandise
|
||||
|
||||
{% endblock content %}
|
||||
|
90
src/backoffice/templates/user/search.html
Normal file
90
src/backoffice/templates/user/search.html
Normal file
|
@ -0,0 +1,90 @@
|
|||
{% 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 />
|
||||
Checked in?: {{ ticket.checked_in }}
|
||||
|
||||
<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 %}
|
||||
|
|
@ -7,6 +7,9 @@ app_name = "backoffice"
|
|||
urlpatterns = [
|
||||
path("", BackofficeIndexView.as_view(), name="index"),
|
||||
# infodesk
|
||||
path(
|
||||
"user/", include([path("", SearchForUser.as_view(), name="user_interaction")])
|
||||
),
|
||||
path("product_handout/", ProductHandoutView.as_view(), name="product_handout"),
|
||||
path("badge_handout/", BadgeHandoutView.as_view(), name="badge_handout"),
|
||||
path("ticket_checkin/", TicketCheckinView.as_view(), name="ticket_checkin"),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import logging, os
|
||||
from itertools import chain
|
||||
|
||||
import qrcode
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.views.generic import TemplateView, ListView, DetailView
|
||||
|
@ -14,7 +15,7 @@ from django.conf import settings
|
|||
from django.core.files import File
|
||||
|
||||
from camps.mixins import CampViewMixin
|
||||
from shop.models import OrderProductRelation
|
||||
from shop.models import OrderProductRelation, Invoice, Order
|
||||
from tickets.models import ShopTicket, SponsorTicket, DiscountTicket
|
||||
from profiles.models import Profile
|
||||
from program.models import SpeakerProposal, EventProposal
|
||||
|
@ -345,7 +346,6 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
|
|||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get the user from kwargs """
|
||||
print("inside dispatch() with method %s" % request.method)
|
||||
self.reimbursement_user = get_object_or_404(User, pk=kwargs["user_id"])
|
||||
|
||||
# get response now so we have self.camp available below
|
||||
|
@ -544,3 +544,30 @@ class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
|||
return redirect(
|
||||
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
|
||||
)
|
||||
|
||||
|
||||
class SearchForUser(TemplateView):
|
||||
template_name = "user/search.html"
|
||||
|
||||
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.checked_in = True
|
||||
ticket_to_check_in.save()
|
||||
messages.info(request, "Ticket checked-in!")
|
||||
|
||||
return super().get(request, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
ticket_token = self.request.POST.get("ticket_token")
|
||||
if ticket_token:
|
||||
try:
|
||||
ticket = ShopTicket.objects.get(token=ticket_token[1:])
|
||||
context["ticket"] = ticket
|
||||
except ShopTicket.DoesNotExist:
|
||||
messages.warning(self.request, "Ticket not found!")
|
||||
|
||||
return context
|
||||
|
|
28
src/tickets/migrations/0006_auto_20190616_1746.py
Normal file
28
src/tickets/migrations/0006_auto_20190616_1746.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 2.2.2 on 2019-06-16 15:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0005_auto_20180318_0906'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='discountticket',
|
||||
name='token',
|
||||
field=models.CharField(max_length=64, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shopticket',
|
||||
name='token',
|
||||
field=models.CharField(max_length=64, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sponsorticket',
|
||||
name='token',
|
||||
field=models.CharField(max_length=64, null=True),
|
||||
),
|
||||
]
|
|
@ -26,6 +26,7 @@ class BaseTicket(CampRelatedModel, UUIDModel):
|
|||
ticket_type = models.ForeignKey("TicketType", on_delete=models.PROTECT)
|
||||
checked_in = models.BooleanField(default=False)
|
||||
badge_handed_out = models.BooleanField(default=False)
|
||||
token = models.CharField(max_length=64)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -34,10 +35,14 @@ class BaseTicket(CampRelatedModel, UUIDModel):
|
|||
def camp(self):
|
||||
return self.ticket_type.camp
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.token = self._get_token()
|
||||
super().save(**kwargs)
|
||||
|
||||
def _get_token(self):
|
||||
return hashlib.sha256(
|
||||
"{_id}{secret_key}".format(
|
||||
_id=self.pk, secret_key=settings.SECRET_KEY
|
||||
_id=self.uuid, secret_key=settings.SECRET_KEY
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
|
|
23
src/utils/templatetags/qrcode.py
Normal file
23
src/utils/templatetags/qrcode.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import base64
|
||||
import io
|
||||
|
||||
import qrcode
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def qr_code(value):
|
||||
stream = io.BytesIO()
|
||||
img = qrcode.make("#" + value, box_size=5)
|
||||
img.save(stream, "PNG")
|
||||
data = base64.b64encode(stream.getvalue())
|
||||
|
||||
return mark_safe(
|
||||
"<figure>"
|
||||
'<img src="data:image/png;base64,{}" alt="">'
|
||||
"<figcaption>{}</figcaption>"
|
||||
"</figure>".format(data.decode(), value)
|
||||
)
|
Loading…
Reference in a new issue