diff --git a/src/bornhack/urls.py b/src/bornhack/urls.py index aa3eec00..62a89d5a 100644 --- a/src/bornhack/urls.py +++ b/src/bornhack/urls.py @@ -1,4 +1,3 @@ -from django.conf.urls.static import static from allauth.account.views import ( LoginView, LogoutView, @@ -13,12 +12,17 @@ from program.views import * from sponsors.views import * from teams.views import * from people.views import * +from tickets.views import ShopTicketListView urlpatterns = [ url( r'^profile/', include('profiles.urls', namespace='profiles') ), + url( + r'^tickets/', + include('tickets.urls', namespace='tickets') + ), url( r'^shop/', include('shop.urls', namespace='shop') diff --git a/src/profiles/templates/profile_base_buttons.html b/src/profiles/templates/profile_base_buttons.html index 38e991e0..47f2a697 100644 --- a/src/profiles/templates/profile_base_buttons.html +++ b/src/profiles/templates/profile_base_buttons.html @@ -11,11 +11,9 @@ Orders - {% if has_tickets %} - + Tickets - {% endif %} {% if user.creditnotes.exists %} Credit Notes diff --git a/src/shop/models.py b/src/shop/models.py index 5b314554..afc1cc8d 100644 --- a/src/shop/models.py +++ b/src/shop/models.py @@ -13,6 +13,7 @@ from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.core.urlresolvers import reverse_lazy +from django.core.exceptions import ValidationError from decimal import Decimal from datetime import timedelta from unidecode import unidecode @@ -312,6 +313,12 @@ class Product(CreatedUpdatedModel, UUIDModel): 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): now = timezone.now() return now in self.available_in @@ -324,7 +331,7 @@ class Product(CreatedUpdatedModel, UUIDModel): def is_upcoming(self): now = timezone.now() - return self.available_in.lower > now + return self.available_in.lower > now class OrderProductRelation(CreatedUpdatedModel): diff --git a/src/shop/templates/shop_base.html b/src/shop/templates/shop_base.html index 9f213393..389af217 100644 --- a/src/shop/templates/shop_base.html +++ b/src/shop/templates/shop_base.html @@ -17,7 +17,7 @@
  • Credit Notes
  • {% endif %} {% if has_tickets %} -
  • Tickets
  • +
  • Tickets
  • {% endif %}
  • Orders
  • diff --git a/src/shop/templates/ticket_list.html b/src/shop/templates/ticket_list.html deleted file mode 100644 index d82dd48d..00000000 --- a/src/shop/templates/ticket_list.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'shop_base.html' %} -{% load bootstrap3 %} -{% load shop_tags %} - -{% block shop_content %} -

    Under maintenance

    -{% endblock %} diff --git a/src/shop/urls.py b/src/shop/urls.py index fd34c193..78d7dc1d 100644 --- a/src/shop/urls.py +++ b/src/shop/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import url from .views import * +from tickets.views import ShopTicketListView urlpatterns = [ url(r'^$', ShopIndexView.as_view(), name='index'), @@ -23,9 +24,6 @@ urlpatterns = [ url(r'orders/(?P[0-9]+)/pay/cash/$', CashView.as_view(), name='cash'), - url(r'tickets/$', TicketListView.as_view(), name='ticket_list'), - url(r'tickets/(?P\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/(?P[0-9]+)/pdf/$', DownloadCreditNoteView.as_view(), name='download_creditnote'), ] diff --git a/src/shop/views.py b/src/shop/views.py index 43d3d92b..5d09e353 100644 --- a/src/shop/views.py +++ b/src/shop/views.py @@ -375,34 +375,6 @@ class DownloadCreditNoteView(LoginRequiredMixin, EnsureUserOwnsCreditNoteMixin, 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): model = Order diff --git a/src/tickets/models.py b/src/tickets/models.py index 5f4246b4..1789ead4 100644 --- a/src/tickets/models.py +++ b/src/tickets/models.py @@ -8,7 +8,6 @@ from django.db import models from django.conf import settings from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext_lazy as _ - from utils.models import ( UUIDModel, CreatedUpdatedModel @@ -60,7 +59,7 @@ class BaseTicket(CreatedUpdatedModel, UUIDModel): return 'data:image/png;base64,{}'.format(self.qrcode_base64) def generate_pdf(self): - generate_pdf_letter( + return generate_pdf_letter( filename='ticket_{}.pdf'.format(self.pk), formatdict={'ticket': self}, template='pdf/ticket.html' @@ -124,5 +123,6 @@ class ShopTicket(BaseTicket): super(ShopTicket, self).save(**kwargs) 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}) + ) diff --git a/src/shop/templates/ticket_detail.html b/src/tickets/templates/ticket_detail.html similarity index 62% rename from src/shop/templates/ticket_detail.html rename to src/tickets/templates/ticket_detail.html index 88df2c72..e6ff1ed3 100644 --- a/src/shop/templates/ticket_detail.html +++ b/src/tickets/templates/ticket_detail.html @@ -7,14 +7,9 @@
    -
    -

    {{ ticket.product.name }}

    -
    -
    -
    -

    {% if ticket.checked_in %}This ticket has been used{% else %}This ticket is unused{% endif %}

    +

    {% if ticket.checked_in %}This ticket has been used{% else %}This ticket is unused{% endif %}

    {% csrf_token %} {% bootstrap_field form.name %} diff --git a/src/tickets/templates/ticket_list.html b/src/tickets/templates/ticket_list.html new file mode 100644 index 00000000..07380234 --- /dev/null +++ b/src/tickets/templates/ticket_list.html @@ -0,0 +1,55 @@ +{% extends 'tickets_base.html' %} +{% load bootstrap3 %} +{% load tickets_tags %} + +{% block tickets_content %} +{% if tickets %} +

    Tickets

    + + + + + + {% for ticket in tickets %} + +
    + Ticket owner + + Product name + + Price + + Checked in + + Actions + +
    + {% if ticket.name %} + {{ ticket.name }} + {% else %} + Anonymous + {% endif %} + + {{ ticket.product.name }} + + {{ ticket.product.price|currency }} + + {% if ticket.checked_in %} + Yes + {% else %} + Not yet + {% endif %} + + Download PDF + {% if not ticket.name %} + Set name + {% else %} + Edit name + {% endif %} + {% endfor %} +
    +{% else %} +

    You dont have any tickets yet. We hope to see you at the next BornHack!

    +{% endif %} + +{% endblock %} diff --git a/src/tickets/templates/tickets_base.html b/src/tickets/templates/tickets_base.html new file mode 100644 index 00000000..0c056e83 --- /dev/null +++ b/src/tickets/templates/tickets_base.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} + +{% block content %} + +
    +
    + +
    +
    + +{% block tickets_content %} + +{% endblock %} + +{% endblock %} + diff --git a/src/tickets/pdf.py b/src/tickets/templatetags/__init__.py similarity index 100% rename from src/tickets/pdf.py rename to src/tickets/templatetags/__init__.py diff --git a/src/tickets/templatetags/tickets_tags.py b/src/tickets/templatetags/tickets_tags.py new file mode 100644 index 00000000..aaa957b6 --- /dev/null +++ b/src/tickets/templatetags/tickets_tags.py @@ -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 diff --git a/src/tickets/urls.py b/src/tickets/urls.py new file mode 100644 index 00000000..658ab01e --- /dev/null +++ b/src/tickets/urls.py @@ -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\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\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' + ), +] diff --git a/src/tickets/views.py b/src/tickets/views.py index 91ea44a2..db9ea339 100644 --- a/src/tickets/views.py +++ b/src/tickets/views.py @@ -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) diff --git a/src/utils/pdf.py b/src/utils/pdf.py index 1d943d94..b038ee12 100644 --- a/src/utils/pdf.py +++ b/src/utils/pdf.py @@ -64,7 +64,6 @@ def generate_pdf_letter(filename, template, formatdict): finalpdf.write(fh) logger.info('Saved pdf to archive: %s' % fullpath) - # return a file object with the data returnfile = io.BytesIO() finalpdf.write(returnfile) return returnfile