update tickets app with list and detail views

Also this commit allows users to download their ticket as PDF.
This commit is contained in:
Stephan Telling 2017-08-19 22:06:32 +02:00
parent 410905f3ce
commit 984b82ba11
No known key found for this signature in database
GPG key ID: D4892289F36ADA9B
16 changed files with 197 additions and 57 deletions

View file

@ -1,4 +1,3 @@
from django.conf.urls.static import static
from allauth.account.views import ( from allauth.account.views import (
LoginView, LoginView,
LogoutView, LogoutView,
@ -13,12 +12,17 @@ from program.views import *
from sponsors.views import * from sponsors.views import *
from teams.views import * from teams.views import *
from people.views import * from people.views import *
from tickets.views import ShopTicketListView
urlpatterns = [ urlpatterns = [
url( url(
r'^profile/', r'^profile/',
include('profiles.urls', namespace='profiles') include('profiles.urls', namespace='profiles')
), ),
url(
r'^tickets/',
include('tickets.urls', namespace='tickets')
),
url( url(
r'^shop/', r'^shop/',
include('shop.urls', namespace='shop') include('shop.urls', namespace='shop')

View file

@ -11,11 +11,9 @@
<a href="{% url 'shop:order_list' %}" class="btn btn-black"> <a href="{% url 'shop:order_list' %}" class="btn btn-black">
Orders Orders
</a> </a>
{% if has_tickets %} <a href="{% url 'tickets:shopticket_list' %}" class="btn btn-black">
<a href="{% url 'shop:ticket_list' %}" class="btn btn-black">
Tickets Tickets
</a> </a>
{% endif %}
{% if user.creditnotes.exists %} {% if user.creditnotes.exists %}
<a href="{% url 'shop:creditnote_list' %}" class="btn btn-black"> <a href="{% url 'shop:creditnote_list' %}" class="btn btn-black">
Credit Notes Credit Notes

View file

@ -13,6 +13,7 @@ from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.core.exceptions import ValidationError
from decimal import Decimal from decimal import Decimal
from datetime import timedelta from datetime import timedelta
from unidecode import unidecode from unidecode import unidecode
@ -312,6 +313,12 @@ class Product(CreatedUpdatedModel, UUIDModel):
self.price, self.price,
) )
def clean(self):
if self.category.name == 'Tickets' and not self.ticket_type:
raise ValidationError(
'Products with category Tickets need a ticket_type'
)
def is_available(self): def is_available(self):
now = timezone.now() now = timezone.now()
return now in self.available_in return now in self.available_in
@ -324,7 +331,7 @@ class Product(CreatedUpdatedModel, UUIDModel):
def is_upcoming(self): def is_upcoming(self):
now = timezone.now() now = timezone.now()
return self.available_in.lower > now return self.available_in.lower > now
class OrderProductRelation(CreatedUpdatedModel): class OrderProductRelation(CreatedUpdatedModel):

View file

@ -17,7 +17,7 @@
<li class="pull-right"><a href="{% url 'shop:creditnote_list' %}">Credit Notes</a></li> <li class="pull-right"><a href="{% url 'shop:creditnote_list' %}">Credit Notes</a></li>
{% endif %} {% endif %}
{% if has_tickets %} {% if has_tickets %}
<li class="pull-right"><a href="{% url 'shop:ticket_list' %}">Tickets</a></li> <li class="pull-right"><a href="{% url 'tickets:shopticket_list' %}">Tickets</a></li>
{% endif %} {% endif %}
<li class="pull-right"><a href="{% url 'shop:order_list' %}">Orders</a></li> <li class="pull-right"><a href="{% url 'shop:order_list' %}">Orders</a></li>
<li class="pull-right no-before"> <li class="pull-right no-before">

View file

@ -1,7 +0,0 @@
{% extends 'shop_base.html' %}
{% load bootstrap3 %}
{% load shop_tags %}
{% block shop_content %}
<h2>Under maintenance</h2>
{% endblock %}

View file

@ -1,5 +1,6 @@
from django.conf.urls import url from django.conf.urls import url
from .views import * from .views import *
from tickets.views import ShopTicketListView
urlpatterns = [ urlpatterns = [
url(r'^$', ShopIndexView.as_view(), name='index'), url(r'^$', ShopIndexView.as_view(), name='index'),
@ -23,9 +24,6 @@ urlpatterns = [
url(r'orders/(?P<pk>[0-9]+)/pay/cash/$', CashView.as_view(), name='cash'), url(r'orders/(?P<pk>[0-9]+)/pay/cash/$', CashView.as_view(), name='cash'),
url(r'tickets/$', TicketListView.as_view(), name='ticket_list'),
url(r'tickets/(?P<pk>\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)$', TicketDetailView.as_view(), name='ticket_detail'),
url(r'creditnotes/$', CreditNoteListView.as_view(), name='creditnote_list'), url(r'creditnotes/$', CreditNoteListView.as_view(), name='creditnote_list'),
url(r'creditnotes/(?P<pk>[0-9]+)/pdf/$', DownloadCreditNoteView.as_view(), name='download_creditnote'), url(r'creditnotes/(?P<pk>[0-9]+)/pdf/$', DownloadCreditNoteView.as_view(), name='download_creditnote'),
] ]

View file

@ -375,34 +375,6 @@ class DownloadCreditNoteView(LoginRequiredMixin, EnsureUserOwnsCreditNoteMixin,
return response return response
class TicketListView(LoginRequiredMixin, ListView):
model = Ticket
template_name = 'ticket_list.html'
context_object_name = 'tickets'
def get_queryset(self):
tickets = super(TicketListView, self).get_queryset()
user = self.request.user
return tickets.filter(order__user=user)
class TicketDetailView(LoginRequiredMixin, UpdateView, DetailView):
model = Ticket
template_name = 'ticket_detail.html'
context_object_name = 'ticket'
fields = ['name', 'email']
def form_valid(self, form):
messages.info(self.request, 'Ticket updated!')
return super(TicketDetailView, self).form_valid(form)
def dispatch(self, request, *args, **kwargs):
ticket = self.get_object()
if ticket.order.user != request.user:
raise Http404
return super(TicketDetailView, self).dispatch(request, *args, **kwargs)
class OrderMarkAsPaidView(LoginRequiredMixin, SingleObjectMixin, View): class OrderMarkAsPaidView(LoginRequiredMixin, SingleObjectMixin, View):
model = Order model = Order

View file

@ -8,7 +8,6 @@ from django.db import models
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.models import ( from utils.models import (
UUIDModel, UUIDModel,
CreatedUpdatedModel CreatedUpdatedModel
@ -60,7 +59,7 @@ class BaseTicket(CreatedUpdatedModel, UUIDModel):
return 'data:image/png;base64,{}'.format(self.qrcode_base64) return 'data:image/png;base64,{}'.format(self.qrcode_base64)
def generate_pdf(self): def generate_pdf(self):
generate_pdf_letter( return generate_pdf_letter(
filename='ticket_{}.pdf'.format(self.pk), filename='ticket_{}.pdf'.format(self.pk),
formatdict={'ticket': self}, formatdict={'ticket': self},
template='pdf/ticket.html' template='pdf/ticket.html'
@ -124,5 +123,6 @@ class ShopTicket(BaseTicket):
super(ShopTicket, self).save(**kwargs) super(ShopTicket, self).save(**kwargs)
def get_absolute_url(self): def get_absolute_url(self):
return str(reverse_lazy('shop:ticket_detail', kwargs={'pk': self.pk})) return str(
reverse_lazy('tickets:shopticket_edit', kwargs={'pk': self.pk})
)

View file

@ -7,14 +7,9 @@
<div class="well"> <div class="well">
<div class="row"> <div class="row">
<div class="col-md-5" style="text-align: center;">
<h3>{{ ticket.product.name }}</h3>
<div style="margin:0px auto;width:250px;height:250px;background-image:url({{ ticket.get_qr_code_url }});"></div>
</div>
<div class="col-md-7"> <div class="col-md-7">
<div class="pull-right"> <div class="pull-right">
<h2>{% if ticket.checked_in %}This ticket has been used{% else %}This ticket is unused{% endif %}</h2> <h2>{% if ticket.checked_in %}This ticket has been used{% else %}This ticket is unused{% endif %}</h2>
<form method="POST" class="form"> <form method="POST" class="form">
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.name %} {% bootstrap_field form.name %}

View file

@ -0,0 +1,55 @@
{% extends 'tickets_base.html' %}
{% load bootstrap3 %}
{% load tickets_tags %}
{% block tickets_content %}
{% if tickets %}
<h3>Tickets</h3>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>
Ticket owner
<th>
Product name
<th>
Price
<th>
Checked in
<th>
Actions
<tbody>
{% for ticket in tickets %}
<tr>
<td>
{% if ticket.name %}
{{ ticket.name }}
{% else %}
Anonymous
{% endif %}
<td>
{{ ticket.product.name }}
<td>
{{ ticket.product.price|currency }}
<td>
{% if ticket.checked_in %}
Yes
{% else %}
Not yet
{% endif %}
<td>
<a href="{% url 'tickets:shopticket_download' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-download" aria-hidden="true"></i> Download PDF</a>
{% if not ticket.name %}
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i> Set name</a>
{% else %}
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i> Edit name</a>
{% endif %}
{% endfor %}
</table>
{% else %}
<h3> You dont have any tickets yet. We hope to see you at the next BornHack!</h3>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'tickets:shopticket_list' %}">Tickets</a></li>
<li class="pull-right no-before">
</li>
</ol>
</div>
</div>
{% block tickets_content %}
{% endblock %}
{% endblock %}

View file

@ -0,0 +1,12 @@
from django import template
from decimal import Decimal
register = template.Library()
@register.filter
def currency(value):
try:
return "{0:.2f} DKK".format(Decimal(value))
except ValueError:
return False

25
src/tickets/urls.py Normal file
View file

@ -0,0 +1,25 @@
from django.conf.urls import url
from .views import (
ShopTicketListView,
ShopTicketDownloadView,
ShopTicketDetailView
)
urlpatterns = [
url(
r'tickets/$',
ShopTicketListView.as_view(),
name='shopticket_list'
),
url(
r'tickets/(?P<pk>\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)/download$',
ShopTicketDownloadView.as_view(),
name='shopticket_download'
),
url(
r'tickets/(?P<pk>\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)/edit$',
ShopTicketDetailView.as_view(),
name='shopticket_edit'
),
]

View file

@ -1,3 +1,64 @@
from django.shortcuts import render import logging
# Create your views here. from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import (
DetailView,
UpdateView,
ListView,
View
)
from django.http import (
HttpResponse,
Http404
)
from .models import ShopTicket
logger = logging.getLogger("bornhack.%s" % __name__)
class ShopTicketListView(LoginRequiredMixin, ListView):
model = ShopTicket
template_name = 'ticket_list.html'
context_object_name = 'tickets'
def get_queryset(self):
tickets = super(ShopTicketListView, self).get_queryset()
user = self.request.user
return tickets.filter(order__user=user)
class ShopTicketDownloadView(LoginRequiredMixin, SingleObjectMixin, View):
model = ShopTicket
def dispatch(self, request, *args, **kwargs):
if not request.user == self.get_object().order.user:
raise Http404("Ticket not found")
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="ticket_{pk}.pdf"'.format(
pk=self.get_object().pk
)
response.write(self.get_object().generate_pdf().getvalue())
return response
class ShopTicketDetailView(LoginRequiredMixin, UpdateView, DetailView):
model = ShopTicket
template_name = 'ticket_detail.html'
context_object_name = 'ticket'
fields = ['name', 'email']
def form_valid(self, form):
messages.info(self.request, 'Ticket updated!')
return super().form_valid(form)
def dispatch(self, request, *args, **kwargs):
ticket = self.get_object()
if ticket.order.user != request.user:
raise Http404("Ticket not found")
return super().dispatch(request, *args, **kwargs)

View file

@ -64,7 +64,6 @@ def generate_pdf_letter(filename, template, formatdict):
finalpdf.write(fh) finalpdf.write(fh)
logger.info('Saved pdf to archive: %s' % fullpath) logger.info('Saved pdf to archive: %s' % fullpath)
# return a file object with the data
returnfile = io.BytesIO() returnfile = io.BytesIO()
finalpdf.write(returnfile) finalpdf.write(returnfile)
return returnfile return returnfile