A lot of ticketing stuff.
This commit is contained in:
parent
e258db6546
commit
869f5a8fe9
|
@ -53,6 +53,7 @@ TEMPLATES = [
|
|||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'camps.context_processors.current_camp',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<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>
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url 'profiles:detail' %}">Profile</a></li>
|
||||
|
|
|
@ -35,6 +35,10 @@ urlpatterns = [
|
|||
r'^profile/',
|
||||
include('profiles.urls', namespace='profiles')
|
||||
),
|
||||
url(
|
||||
r'^tickets/',
|
||||
include('tickets.urls', namespace='tickets')
|
||||
),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
|
|
5
camps/context_processors.py
Normal file
5
camps/context_processors.py
Normal 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
10
camps/managers.py
Normal 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
|
20
camps/migrations/0004_camp_ticket_sale_open.py
Normal file
20
camps/migrations/0004_camp_ticket_sale_open.py
Normal 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?'),
|
||||
),
|
||||
]
|
|
@ -4,6 +4,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from bornhack.utils import CreatedUpdatedModel, UUIDModel
|
||||
|
||||
from .managers import CampQuerySet
|
||||
|
||||
|
||||
class Camp(CreatedUpdatedModel, UUIDModel):
|
||||
class Meta:
|
||||
|
@ -28,6 +30,14 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
|||
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):
|
||||
return _('{} {}').format(
|
||||
self.name,
|
||||
|
@ -36,12 +46,10 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
|||
|
||||
def create_days(self):
|
||||
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(
|
||||
date=self.start + datetime.timedelta(days=day_offset)
|
||||
)
|
||||
if created:
|
||||
print('{} created'.format(day))
|
||||
|
||||
def save(self, **kwargs):
|
||||
super().save(**kwargs)
|
||||
|
|
|
@ -8,14 +8,12 @@ class TicketAdmin(admin.ModelAdmin):
|
|||
list_display = [
|
||||
'user',
|
||||
'ticket_type',
|
||||
'camp',
|
||||
'paid',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'paid',
|
||||
'ticket_type',
|
||||
'camp',
|
||||
]
|
||||
|
||||
|
||||
|
|
16
tickets/forms.py
Normal file
16
tickets/forms.py
Normal 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
13
tickets/managers.py
Normal 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)
|
||||
)
|
36
tickets/migrations/0002_auto_20160506_1602.py
Normal file
36
tickets/migrations/0002_auto_20160506_1602.py
Normal 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),
|
||||
),
|
||||
]
|
31
tickets/migrations/0003_auto_20160506_2016.py
Normal file
31
tickets/migrations/0003_auto_20160506_2016.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -1,25 +1,23 @@
|
|||
from django.db import models
|
||||
from django.contrib.postgres.fields import DateTimeRangeField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from bornhack.utils import CreatedUpdatedModel, UUIDModel
|
||||
|
||||
from .managers import TicketTypeQuerySet
|
||||
|
||||
|
||||
class Ticket(CreatedUpdatedModel, UUIDModel):
|
||||
class Meta:
|
||||
verbose_name = _('Ticket')
|
||||
verbose_name_plural = _('Tickets')
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
verbose_name=_('Camp'),
|
||||
help_text=_('The camp this ticket is for.'),
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
'auth.User',
|
||||
verbose_name=_('User'),
|
||||
help_text=_('The user this ticket belongs to.'),
|
||||
related_name='tickets',
|
||||
)
|
||||
|
||||
paid = models.BooleanField(
|
||||
|
@ -31,7 +29,12 @@ class Ticket(CreatedUpdatedModel, UUIDModel):
|
|||
ticket_type = models.ForeignKey(
|
||||
'tickets.TicketType',
|
||||
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:
|
||||
verbose_name = _('Ticket Type')
|
||||
verbose_name_plural = _('Ticket Types')
|
||||
ordering = ['available_in']
|
||||
|
||||
name = models.CharField(max_length=150)
|
||||
|
||||
|
@ -53,8 +57,20 @@ class TicketType(CreatedUpdatedModel, UUIDModel):
|
|||
)
|
||||
|
||||
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):
|
||||
return self.name
|
||||
return '{} ({} DKK)'.format(
|
||||
self.name,
|
||||
self.price,
|
||||
)
|
||||
|
||||
def is_available(self):
|
||||
now = timezone.now()
|
||||
return now in self.available_in
|
||||
|
|
13
tickets/templates/tickets/buy.html
Normal file
13
tickets/templates/tickets/buy.html
Normal 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 %}
|
7
tickets/templates/tickets/detail.html
Normal file
7
tickets/templates/tickets/detail.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ ticket }}
|
||||
|
||||
{% endblock %}
|
73
tickets/templates/tickets/index.html
Normal file
73
tickets/templates/tickets/index.html
Normal 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
13
tickets/urls.py
Normal 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'),
|
||||
]
|
|
@ -1,3 +1,59 @@
|
|||
from django.http import HttpResponseRedirect, Http404
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue