update tickets app with list and detail views
Also this commit allows users to download their ticket as PDF.
This commit is contained in:
parent
410905f3ce
commit
984b82ba11
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{% extends 'shop_base.html' %}
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load shop_tags %}
|
|
||||||
|
|
||||||
{% block shop_content %}
|
|
||||||
<h2>Under maintenance</h2>
|
|
||||||
{% endblock %}
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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})
|
||||||
|
)
|
||||||
|
|
|
@ -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 %}
|
55
src/tickets/templates/ticket_list.html
Normal file
55
src/tickets/templates/ticket_list.html
Normal 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 %}
|
21
src/tickets/templates/tickets_base.html
Normal file
21
src/tickets/templates/tickets_base.html
Normal 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 %}
|
||||||
|
|
12
src/tickets/templatetags/tickets_tags.py
Normal file
12
src/tickets/templatetags/tickets_tags.py
Normal 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
25
src/tickets/urls.py
Normal 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'
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue