forked from CGM_Public/pretix_original
UI for creating and chacnging vouchers
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-02-09 09:59
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0002_auto_20160209_0940'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventpermission',
|
||||||
|
name='can_change_vouchers',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Can change vouchers'),
|
||||||
|
),
|
||||||
|
]
|
||||||
31
src/pretix/base/migrations/0004_auto_20160209_1023.py
Normal file
31
src/pretix/base/migrations/0004_auto_20160209_1023.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9 on 2016-02-09 10:23
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0003_eventpermission_can_change_vouchers'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='voucher',
|
||||||
|
name='item',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='voucher',
|
||||||
|
name='item',
|
||||||
|
field=models.ForeignKey(default=None, help_text="This product is added to the user's cart if the voucher is redeemed.", on_delete=django.db.models.deletion.CASCADE, related_name='vouchers', to='pretixbase.Item', verbose_name='Product'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='voucher',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, help_text='If empty, the product will cost its normal price.', max_digits=10, null=True, verbose_name='Set product price to'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -237,6 +237,10 @@ class EventPermission(models.Model):
|
|||||||
default=True,
|
default=True,
|
||||||
verbose_name=_("Can change orders")
|
verbose_name=_("Can change orders")
|
||||||
)
|
)
|
||||||
|
can_change_vouchers = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
verbose_name=_("Can change vouchers")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Event permission")
|
verbose_name = _("Event permission")
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .base import LoggedModel
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .items import Item
|
from .items import Item
|
||||||
from .orders import CartPosition, OrderPosition
|
from .orders import CartPosition, OrderPosition
|
||||||
|
|
||||||
|
|
||||||
class Voucher(models.Model):
|
def generate_code():
|
||||||
|
charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
|
||||||
|
while True:
|
||||||
|
code = "".join([random.choice(charset) for i in range(16)])
|
||||||
|
if not Voucher.objects.filter(code=code).exists():
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
class Voucher(LoggedModel):
|
||||||
event = models.ForeignKey(
|
event = models.ForeignKey(
|
||||||
Event,
|
Event,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -15,7 +26,7 @@ class Voucher(models.Model):
|
|||||||
)
|
)
|
||||||
code = models.CharField(
|
code = models.CharField(
|
||||||
verbose_name=_("Voucher code"),
|
verbose_name=_("Voucher code"),
|
||||||
max_length=255
|
max_length=255, default=generate_code
|
||||||
)
|
)
|
||||||
valid_until = models.DateTimeField(
|
valid_until = models.DateTimeField(
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
@@ -38,9 +49,10 @@ class Voucher(models.Model):
|
|||||||
)
|
)
|
||||||
price = models.DecimalField(
|
price = models.DecimalField(
|
||||||
verbose_name=_("Set product price to"),
|
verbose_name=_("Set product price to"),
|
||||||
decimal_places=2, max_digits=10, null=True, blank=True
|
decimal_places=2, max_digits=10, null=True, blank=True,
|
||||||
|
help_text=_('If empty, the product will cost its normal price.')
|
||||||
)
|
)
|
||||||
item = models.ManyToManyField(
|
item = models.ForeignKey(
|
||||||
Item, related_name='vouchers',
|
Item, related_name='vouchers',
|
||||||
verbose_name=_("Product"),
|
verbose_name=_("Product"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
@@ -53,16 +65,19 @@ class Voucher(models.Model):
|
|||||||
verbose_name_plural = _("Vouchers")
|
verbose_name_plural = _("Vouchers")
|
||||||
unique_together = (("event", "code"),)
|
unique_together = (("event", "code"),)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.code
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.code = self.code.upper()
|
self.code = self.code.upper()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def is_ordered(self) -> int:
|
def is_ordered(self) -> int:
|
||||||
return OrderPosition.objects.current.filter(
|
return OrderPosition.objects.filter(
|
||||||
voucher=self.voucher
|
voucher=self
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
def is_in_cart(self) -> int:
|
def is_in_cart(self) -> int:
|
||||||
return CartPosition.objects.current.filter(
|
return CartPosition.objects.filter(
|
||||||
voucher=self.voucher
|
voucher=self
|
||||||
).count()
|
).count()
|
||||||
|
|||||||
14
src/pretix/control/forms/vouchers.py
Normal file
14
src/pretix/control/forms/vouchers.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from pretix.base.forms import I18nModelForm
|
||||||
|
from pretix.base.models import Voucher
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherForm(I18nModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Voucher
|
||||||
|
localized_fields = '__all__'
|
||||||
|
fields = [
|
||||||
|
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'item'
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_validation_exclusions(self):
|
||||||
|
return []
|
||||||
@@ -123,6 +123,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if request.eventperm.can_change_vouchers %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'control:event.vouchers' organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
{% if url_name == "event.vouchers" %}class="active"{% endif %}>
|
||||||
|
<i class="fa fa-tags fa-fw"></i>
|
||||||
|
{% trans "Vouchers" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% for nav in nav_event %}
|
{% for nav in nav_event %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ nav.url }}" {% if nav.active %}class="active"{% endif %}>
|
<a href="{{ nav.url }}" {% if nav.active %}class="active"{% endif %}>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Delete voucher" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Delete voucher" %}</h1>
|
||||||
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if not allowed %}
|
||||||
|
<p>{% trans "You can not delete this voucher after it has been redeemed" %}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>{% blocktrans %}Are you sure you want to delete the voucher
|
||||||
|
<strong>{{ voucher }}</strong>?{% endblocktrans %}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<a href="{% url "control:event.vouchers" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
{% if allowed %}
|
||||||
|
<button type="submit" class="btn btn-danger btn-save">
|
||||||
|
{% trans "Delete" %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Voucher" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Voucher" %}</h1>
|
||||||
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Voucher details" %}</legend>
|
||||||
|
{% bootstrap_field form.code layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.valid_until layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.block_quota layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.price layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.item layout="horizontal" %}
|
||||||
|
</fieldset>
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Vouchers" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Vouchers" %}</h1>
|
||||||
|
<p>
|
||||||
|
<a href="{% url "control:event.vouchers.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new voucher" %}</a>
|
||||||
|
</p>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-quotas">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Voucher code" %}</th>
|
||||||
|
<th>{% trans "Is redeemed" %}</th>
|
||||||
|
<th>{% trans "Expiry" %}</th>
|
||||||
|
<th>{% trans "Product" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for v in vouchers %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong><a href="
|
||||||
|
{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}">{{ v.code }}</a></strong>
|
||||||
|
</td>
|
||||||
|
<td>{% if v.is_ordered %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
|
||||||
|
<td>{{ v.valid_until|date }}</td>
|
||||||
|
<td>{{ v.item }}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<a href="{% url "control:event.voucher.delete" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
from pretix.control.views import (
|
from pretix.control.views import (
|
||||||
auth, event, item, main, orders, organizer, user,
|
auth, event, item, main, orders, organizer, user, vouchers,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -54,6 +54,11 @@ urlpatterns = [
|
|||||||
url(r'^quotas/(?P<quota>\d+)/delete$', item.QuotaDelete.as_view(),
|
url(r'^quotas/(?P<quota>\d+)/delete$', item.QuotaDelete.as_view(),
|
||||||
name='event.items.quotas.delete'),
|
name='event.items.quotas.delete'),
|
||||||
url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
|
url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
|
||||||
|
url(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
|
||||||
|
url(r'^vouchers/(?P<voucher>\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
|
||||||
|
url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
|
||||||
|
name='event.voucher.delete'),
|
||||||
|
url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
|
||||||
url(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
|
url(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
|
||||||
name='event.order.transition'),
|
name='event.order.transition'),
|
||||||
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
||||||
|
|||||||
121
src/pretix/control/views/vouchers.py
Normal file
121
src/pretix/control/views/vouchers.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from django.contrib import messages
|
||||||
|
from django.core.urlresolvers import resolve, reverse
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||||
|
|
||||||
|
from pretix.base.models import Voucher
|
||||||
|
from pretix.control.forms.vouchers import VoucherForm
|
||||||
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherList(EventPermissionRequiredMixin, ListView):
|
||||||
|
model = Voucher
|
||||||
|
context_object_name = 'vouchers'
|
||||||
|
paginate_by = 30
|
||||||
|
template_name = 'pretixcontrol/vouchers/index.html'
|
||||||
|
permission = 'can_change_vouchers'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.vouchers.all().select_related('item')
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
|
||||||
|
model = Voucher
|
||||||
|
template_name = 'pretixcontrol/vouchers/delete.html'
|
||||||
|
permission = 'can_change_vouchers'
|
||||||
|
context_object_name = 'voucher'
|
||||||
|
|
||||||
|
def get_object(self, queryset=None) -> Voucher:
|
||||||
|
try:
|
||||||
|
return self.request.event.vouchers.get(
|
||||||
|
id=self.kwargs['voucher']
|
||||||
|
)
|
||||||
|
except Voucher.DoesNotExist:
|
||||||
|
raise Http404(_("The requested voucher does not exist."))
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs) -> dict:
|
||||||
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context['allowed'] = not self.get_object().is_ordered()
|
||||||
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
success_url = self.get_success_url()
|
||||||
|
|
||||||
|
if self.object.is_ordered():
|
||||||
|
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
|
||||||
|
else:
|
||||||
|
self.object.log_action('pretix.voucher.deleted', user=self.request.user)
|
||||||
|
self.object.delete()
|
||||||
|
messages.success(request, _('The selected voucher has been deleted.'))
|
||||||
|
return HttpResponseRedirect(success_url)
|
||||||
|
|
||||||
|
def get_success_url(self) -> str:
|
||||||
|
return reverse('control:event.vouchers', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||||
|
model = Voucher
|
||||||
|
form_class = VoucherForm
|
||||||
|
template_name = 'pretixcontrol/vouchers/detail.html'
|
||||||
|
permission = 'can_change_vouchers'
|
||||||
|
context_object_name = 'voucher'
|
||||||
|
|
||||||
|
def get_object(self, queryset=None) -> VoucherForm:
|
||||||
|
url = resolve(self.request.path_info)
|
||||||
|
try:
|
||||||
|
return self.request.event.vouchers.get(
|
||||||
|
id=url.kwargs['voucher']
|
||||||
|
)
|
||||||
|
except Voucher.DoesNotExist:
|
||||||
|
raise Http404(_("The requested voucher does not exist."))
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
|
if form.has_changed():
|
||||||
|
self.object.log_action(
|
||||||
|
'pretix.voucher.changed', user=self.request.user, data={
|
||||||
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self) -> str:
|
||||||
|
return reverse('control:event.vouchers', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherCreate(EventPermissionRequiredMixin, CreateView):
|
||||||
|
model = Voucher
|
||||||
|
form_class = VoucherForm
|
||||||
|
template_name = 'pretixcontrol/vouchers/detail.html'
|
||||||
|
permission = 'can_change_vouchers'
|
||||||
|
context_object_name = 'voucher'
|
||||||
|
|
||||||
|
def get_success_url(self) -> str:
|
||||||
|
return reverse('control:event.vouchers', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['instance'] = Voucher(event=self.request.event)
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.event = self.request.event
|
||||||
|
messages.success(self.request, _('The new voucher has been created.'))
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||||
|
return ret
|
||||||
Reference in New Issue
Block a user