Initial work on a more lean infodesk.
This commit is contained in:
parent
24c95a82fd
commit
5911d2042b
|
@ -16,17 +16,14 @@
|
||||||
<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:product_handout' camp_slug=camp.slug %}" class="list-group-item">
|
<a href="{% url 'backoffice:user_interaction' camp_slug=camp.slug %}"
|
||||||
<h4 class="list-group-item-heading">Hand Out Products</h4>
|
class="list-group-item">
|
||||||
<p class="list-group-item-text">Use this view to mark products such as merchandise, cabins, fridges and so on as handed out.</p>
|
<h4 class="list-group-item-heading">
|
||||||
</a>
|
User stuff
|
||||||
<a href="{% url 'backoffice:ticket_checkin' camp_slug=camp.slug %}" class="list-group-item">
|
</h4>
|
||||||
<h4 class="list-group-item-heading">Check-In Tickets</h4>
|
<p class="list-group-item-text">
|
||||||
<p class="list-group-item-text">Use this view to check-in tickets when participants arrive.</p>
|
Use this to get everything related to a user
|
||||||
</a>
|
</p>
|
||||||
<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>
|
</a>
|
||||||
{% endif %}
|
{% 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 = [
|
urlpatterns = [
|
||||||
path("", BackofficeIndexView.as_view(), name="index"),
|
path("", BackofficeIndexView.as_view(), name="index"),
|
||||||
# infodesk
|
# infodesk
|
||||||
|
path(
|
||||||
|
"user/", include([path("", SearchForUser.as_view(), name="user_interaction")])
|
||||||
|
),
|
||||||
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"),
|
||||||
path("ticket_checkin/", TicketCheckinView.as_view(), name="ticket_checkin"),
|
path("ticket_checkin/", TicketCheckinView.as_view(), name="ticket_checkin"),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging, os
|
import logging, os
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
|
import qrcode
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.views.generic import TemplateView, ListView, DetailView
|
from django.views.generic import TemplateView, ListView, DetailView
|
||||||
|
@ -14,7 +15,7 @@ from django.conf import settings
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
|
||||||
from camps.mixins import CampViewMixin
|
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 tickets.models import ShopTicket, SponsorTicket, DiscountTicket
|
||||||
from profiles.models import Profile
|
from profiles.models import Profile
|
||||||
from program.models import SpeakerProposal, EventProposal
|
from program.models import SpeakerProposal, EventProposal
|
||||||
|
@ -345,7 +346,6 @@ class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateV
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
""" Get the user from 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"])
|
self.reimbursement_user = get_object_or_404(User, pk=kwargs["user_id"])
|
||||||
|
|
||||||
# get response now so we have self.camp available below
|
# get response now so we have self.camp available below
|
||||||
|
@ -544,3 +544,30 @@ class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
||||||
return redirect(
|
return redirect(
|
||||||
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
|
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)
|
ticket_type = models.ForeignKey("TicketType", on_delete=models.PROTECT)
|
||||||
checked_in = models.BooleanField(default=False)
|
checked_in = models.BooleanField(default=False)
|
||||||
badge_handed_out = models.BooleanField(default=False)
|
badge_handed_out = models.BooleanField(default=False)
|
||||||
|
token = models.CharField(max_length=64)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -34,10 +35,14 @@ class BaseTicket(CampRelatedModel, UUIDModel):
|
||||||
def camp(self):
|
def camp(self):
|
||||||
return self.ticket_type.camp
|
return self.ticket_type.camp
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
self.token = self._get_token()
|
||||||
|
super().save(**kwargs)
|
||||||
|
|
||||||
def _get_token(self):
|
def _get_token(self):
|
||||||
return hashlib.sha256(
|
return hashlib.sha256(
|
||||||
"{_id}{secret_key}".format(
|
"{_id}{secret_key}".format(
|
||||||
_id=self.pk, secret_key=settings.SECRET_KEY
|
_id=self.uuid, secret_key=settings.SECRET_KEY
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
).hexdigest()
|
).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