A lot of ticketing stuff.

This commit is contained in:
Víðir Valberg Guðmundsson 2016-05-06 22:33:59 +02:00
parent e258db6546
commit 869f5a8fe9
18 changed files with 338 additions and 15 deletions

View file

@ -53,6 +53,7 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'camps.context_processors.current_camp',
], ],
}, },
}, },

View file

@ -37,6 +37,9 @@
<div id="navbar" class="navbar-collapse collapse"> <div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
{% if current_camp.ticket_sale_open %}
<li><a href="{% url 'tickets:index' %}">Tickets</a></li>
{% endif %}
<li><a href="{% url 'good-to-know' %}">Good to know</a></li> <li><a href="{% url 'good-to-know' %}">Good to know</a></li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'profiles:detail' %}">Profile</a></li> <li><a href="{% url 'profiles:detail' %}">Profile</a></li>

View file

@ -35,6 +35,10 @@ urlpatterns = [
r'^profile/', r'^profile/',
include('profiles.urls', namespace='profiles') include('profiles.urls', namespace='profiles')
), ),
url(
r'^tickets/',
include('tickets.urls', namespace='tickets')
),
url(r'^accounts/', include('allauth.urls')), url(r'^accounts/', include('allauth.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),

View file

@ -0,0 +1,5 @@
from .models import Camp
def current_camp(request):
return {'current_camp': Camp.objects.current()}

10
camps/managers.py Normal file
View file

@ -0,0 +1,10 @@
from django.utils import timezone
from django.db.models import QuerySet
class CampQuerySet(QuerySet):
def current(self):
now = timezone.now()
if self.filter(start__year=now.year).exists():
return self.get(start__year=now.year)
return None

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-06 20:16
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('camps', '0003_auto_20160422_2019'),
]
operations = [
migrations.AddField(
model_name='camp',
name='ticket_sale_open',
field=models.BooleanField(default=False, help_text='Whether tickets are for sale or not.', verbose_name='Ticket sale open?'),
),
]

View file

@ -4,6 +4,8 @@ from django.utils.translation import ugettext_lazy as _
from bornhack.utils import CreatedUpdatedModel, UUIDModel from bornhack.utils import CreatedUpdatedModel, UUIDModel
from .managers import CampQuerySet
class Camp(CreatedUpdatedModel, UUIDModel): class Camp(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
@ -28,6 +30,14 @@ class Camp(CreatedUpdatedModel, UUIDModel):
unique=True, unique=True,
) )
ticket_sale_open = models.BooleanField(
verbose_name=_('Ticket sale open?'),
help_text=_('Whether tickets are for sale or not.'),
default=False,
)
objects = CampQuerySet.as_manager()
def __str__(self): def __str__(self):
return _('{} {}').format( return _('{} {}').format(
self.name, self.name,
@ -36,12 +46,10 @@ class Camp(CreatedUpdatedModel, UUIDModel):
def create_days(self): def create_days(self):
delta = self.end - self.start delta = self.end - self.start
for day_offset in range(1, delta.days + 1): for day_offset in range(0, delta.days + 1):
day, created = self.days.get_or_create( day, created = self.days.get_or_create(
date=self.start + datetime.timedelta(days=day_offset) date=self.start + datetime.timedelta(days=day_offset)
) )
if created:
print('{} created'.format(day))
def save(self, **kwargs): def save(self, **kwargs):
super().save(**kwargs) super().save(**kwargs)

View file

@ -8,14 +8,12 @@ class TicketAdmin(admin.ModelAdmin):
list_display = [ list_display = [
'user', 'user',
'ticket_type', 'ticket_type',
'camp',
'paid', 'paid',
] ]
list_filter = [ list_filter = [
'paid', 'paid',
'ticket_type', 'ticket_type',
'camp',
] ]

16
tickets/forms.py Normal file
View file

@ -0,0 +1,16 @@
from django import forms
from .models import Ticket, TicketType
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = [
'ticket_type',
]
ticket_type = forms.ModelChoiceField(
queryset=TicketType.objects.available()
)

13
tickets/managers.py Normal file
View file

@ -0,0 +1,13 @@
from psycopg2.extras import DateTimeTZRange
from django.db.models import QuerySet
from django.utils import timezone
class TicketTypeQuerySet(QuerySet):
def available(self):
now = timezone.now()
return self.filter(
available_in__contains=DateTimeTZRange(now, None)
)

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-06 16:02
from __future__ import unicode_literals
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tickets', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='ticket',
name='camp',
),
migrations.AlterField(
model_name='ticket',
name='ticket_type',
field=models.ForeignKey(help_text='The type of the ticket.', on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType', verbose_name='Ticket type'),
),
migrations.AlterField(
model_name='tickettype',
name='available_in',
field=django.contrib.postgres.fields.ranges.DateTimeRangeField(help_text='Which period is this ticket available for purchase? | (Format: YYYY-MM-DD HH:MM) | Only one of start/end is required'),
),
migrations.AlterField(
model_name='tickettype',
name='name',
field=models.CharField(max_length=150),
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-06 20:16
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tickets', '0002_auto_20160506_1602'),
]
operations = [
migrations.AlterModelOptions(
name='tickettype',
options={'ordering': ['available_in'], 'verbose_name': 'Ticket Type', 'verbose_name_plural': 'Ticket Types'},
),
migrations.AlterField(
model_name='ticket',
name='ticket_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.TicketType', verbose_name='Ticket type'),
),
migrations.AlterField(
model_name='ticket',
name='user',
field=models.ForeignKey(help_text='The user this ticket belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View file

@ -1,25 +1,23 @@
from django.db import models from django.db import models
from django.contrib.postgres.fields import DateTimeRangeField from django.contrib.postgres.fields import DateTimeRangeField
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from bornhack.utils import CreatedUpdatedModel, UUIDModel from bornhack.utils import CreatedUpdatedModel, UUIDModel
from .managers import TicketTypeQuerySet
class Ticket(CreatedUpdatedModel, UUIDModel): class Ticket(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
verbose_name = _('Ticket') verbose_name = _('Ticket')
verbose_name_plural = _('Tickets') verbose_name_plural = _('Tickets')
camp = models.ForeignKey(
'camps.Camp',
verbose_name=_('Camp'),
help_text=_('The camp this ticket is for.'),
)
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', 'auth.User',
verbose_name=_('User'), verbose_name=_('User'),
help_text=_('The user this ticket belongs to.'), help_text=_('The user this ticket belongs to.'),
related_name='tickets',
) )
paid = models.BooleanField( paid = models.BooleanField(
@ -31,7 +29,12 @@ class Ticket(CreatedUpdatedModel, UUIDModel):
ticket_type = models.ForeignKey( ticket_type = models.ForeignKey(
'tickets.TicketType', 'tickets.TicketType',
verbose_name=_('Ticket type'), verbose_name=_('Ticket type'),
help_text=_('The type of the ticket.'), )
def __str__(self):
return '{} ({})'.format(
self.ticket_type.name,
self.ticket_type.camp
) )
@ -39,6 +42,7 @@ class TicketType(CreatedUpdatedModel, UUIDModel):
class Meta: class Meta:
verbose_name = _('Ticket Type') verbose_name = _('Ticket Type')
verbose_name_plural = _('Ticket Types') verbose_name_plural = _('Ticket Types')
ordering = ['available_in']
name = models.CharField(max_length=150) name = models.CharField(max_length=150)
@ -53,8 +57,20 @@ class TicketType(CreatedUpdatedModel, UUIDModel):
) )
available_in = DateTimeRangeField( available_in = DateTimeRangeField(
help_text=_('Which period is this ticket available for purchase?') help_text=_(
'Which period is this ticket available for purchase? | '
'(Format: YYYY-MM-DD HH:MM) | Only one of start/end is required'
)
) )
objects = TicketTypeQuerySet.as_manager()
def __str__(self): def __str__(self):
return self.name return '{} ({} DKK)'.format(
self.name,
self.price,
)
def is_available(self):
now = timezone.now()
return now in self.available_in

View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
{% bootstrap_button "Buy" button_type="submit" button_class="btn-primary" %}
</form>
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
{{ ticket }}
{% endblock %}

View file

@ -0,0 +1,73 @@
{% extends 'base.html' %}
{% block content %}
<h2>Tickets</h2>
<p class="lead">
Here you can see the different ticket types, their prices and availability.
</p>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>
Description
<th>
Price
<th>
Availability
<th>
Buy
<tbody>
{% for ticket_type in ticket_types %}
<tr {% if not ticket_type.is_available %}style="color: lightgrey"{%endif%}>
<td>
{{ ticket_type.name }}
<td>
{{ ticket_type.price }} DKK
<td>
{{ ticket_type.available_in.lower }}
{% if ticket_type.available_in.upper %}
- {{ ticket_type.available_in.upper }}
{% endif %}
<td>
{% if ticket_type.is_available %}
<a href="{% url 'tickets:buy' %}?ticket_type={{ ticket_type.pk }}">Buy</a>
{% else %}
N/A
{% endif %}
{% endfor %}
</table>
{% if user.is_authenticated %}
<hr />
<h3>Your tickets</h3>
<table class="table table-hover">
{% for ticket in user.tickets.all %}
<tr>
<td>
{{ ticket.ticket_type.name }}
<td>
{% if ticket.paid %} Paid {% else %} Not paid {% endif %}
{% empty %}
<tr>
<td colspan=3>
You don't have a ticket! Why don't buy one and join the fun?
{% endfor %}
</table>
<a href="{% url 'tickets:buy' %}" class="btn btn-success">Buy tickets</a>
{% else %}
<a href="{% url 'account_signup' %}?next={% url 'tickets:index' %}">Sign up</a> or
<a href="{% url 'account_login' %}?next={% url 'tickets:index' %}">login</a> to buy tickets.
{% endif %}
{% endblock %}

13
tickets/urls.py Normal file
View file

@ -0,0 +1,13 @@
from django.conf.urls import url
from .views import BuyTicketView, TicketIndexView, TicketDetailView
urlpatterns = [
url(r'buy/$', BuyTicketView.as_view(), name='buy'),
url(
r'detail/(?P<pk>[a-zA-Z0-9\-]+)/$',
TicketDetailView.as_view(),
name='detail'
),
url(r'$', TicketIndexView.as_view(), name='index'),
]

View file

@ -1,3 +1,59 @@
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render from django.shortcuts import render
from django.views.generic import CreateView, TemplateView, DetailView
from django.core.urlresolvers import reverse_lazy
from camps.models import Camp
from .models import Ticket, TicketType
from .forms import TicketForm
class CampTicketSaleCheck(object):
def dispatch(self, *args, **kwargs):
current_camp = Camp.objects.current()
if current_camp and current_camp.ticket_sale_open:
return super().dispatch(*args, **kwargs)
raise Http404()
class TicketIndexView(CampTicketSaleCheck, TemplateView):
template_name = "tickets/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ticket_types'] = TicketType.objects.all()
return context
class TicketDetailView(CampTicketSaleCheck, DetailView):
model = Ticket
template_name = 'tickets/detail.html'
context_object_name = 'ticket'
class BuyTicketView(CampTicketSaleCheck, CreateView):
model = Ticket
template_name = "tickets/buy.html"
form_class = TicketForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
ticket_type = self.request.GET.get('ticket_type', None)
if ticket_type:
kwargs['initial'] = {
'ticket_type': ticket_type
}
return kwargs
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
return HttpResponseRedirect(
reverse_lazy('tickets:detail', kwargs={
'pk': str(instance.pk)
})
)
# Create your views here.