diff --git a/src/pretix/base/migrations/0135_auto_20190910_2020.py b/src/pretix/base/migrations/0135_auto_20190910_2020.py index 8db9543e2a..ec4803180e 100644 --- a/src/pretix/base/migrations/0135_auto_20190910_2020.py +++ b/src/pretix/base/migrations/0135_auto_20190910_2020.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('issuance', models.DateTimeField(auto_now_add=True)), ('secret', models.CharField(db_index=True, default=pretix.base.models.giftcards.gen_giftcard_secret, max_length=190, unique=True)), ('currency', models.CharField(max_length=10)), - ('issued_in', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='issued_gift_cards', to='pretixbase.OrderPosition')), + ('issued_in', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='issued_gift_cards', to='pretixbase.OrderPosition', null=True, blank=True)), ('issuer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='issued_gift_cards', to='pretixbase.Organizer')), ], ), diff --git a/src/pretix/base/migrations/0136_auto_20190918_1537.py b/src/pretix/base/migrations/0136_auto_20190918_1537.py new file mode 100644 index 0000000000..fe56026023 --- /dev/null +++ b/src/pretix/base/migrations/0136_auto_20190918_1537.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.1 on 2019-09-18 15:37 + +import django.db.models.deletion +from django.db import migrations, models + +import pretix.base.models.fields + + +def fwd(app, schema_editor): + Team = app.get_model('pretixbase', 'Team') + Team.objects.filter(can_change_organizer_settings=True).update(can_manage_gift_cards=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0135_auto_20190910_2020'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='can_manage_gift_cards', + field=models.BooleanField(default=False), + ), + migrations.RunPython( + fwd, migrations.RunPython.noop + ), + ] diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index b47cd32b54..55ccff0ecd 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -730,7 +730,7 @@ class Event(EventMixin, LoggedModel): def has_payment_provider(self): result = False for provider in self.get_payment_providers().values(): - if provider.is_enabled and provider.identifier not in ('free', 'boxoffice', 'offsetting'): + if provider.is_enabled and provider.identifier not in ('free', 'boxoffice', 'offsetting', 'giftcard'): result = True break return result diff --git a/src/pretix/base/models/giftcards.py b/src/pretix/base/models/giftcards.py index ac68d6cdbf..83d6f36c8d 100644 --- a/src/pretix/base/models/giftcards.py +++ b/src/pretix/base/models/giftcards.py @@ -4,6 +4,9 @@ from django.conf import settings from django.db import models from django.db.models import Sum from django.utils.crypto import get_random_string +from django.utils.translation import ugettext_lazy as _ + +from pretix.base.models import LoggedModel def gen_giftcard_secret(): @@ -27,7 +30,7 @@ class GiftCardAcceptance(models.Model): ) -class GiftCard(models.Model): +class GiftCard(LoggedModel): issuer = models.ForeignKey( 'Organizer', related_name='issued_gift_cards', @@ -37,6 +40,7 @@ class GiftCard(models.Model): 'OrderPosition', related_name='issued_gift_cards', on_delete=models.PROTECT, + null=True, blank=True ) issuance = models.DateTimeField( auto_now_add=True, @@ -46,8 +50,13 @@ class GiftCard(models.Model): default=gen_giftcard_secret, unique=True, db_index=True, + verbose_name=_('Gift card code'), ) - currency = models.CharField(max_length=10) + CURRENCY_CHOICES = [(c.alpha_3, c.alpha_3 + " - " + c.name) for c in settings.CURRENCIES] + currency = models.CharField(max_length=10, choices=CURRENCY_CHOICES) + + def __str__(self): + return self.secret @property def value(self): @@ -88,3 +97,6 @@ class GiftCardTransaction(models.Model): blank=True, on_delete=models.PROTECT ) + + class Meta: + ordering = ("datetime",) diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index 5ca7ca8be3..7d9dc090a3 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -156,6 +156,10 @@ class Team(LoggedModel): help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy ' 'reports, so be careful who you add to this team!') ) + can_manage_gift_cards = models.BooleanField( + default=False, + verbose_name=_("Can manage gift cards") + ) can_change_event_settings = models.BooleanField( default=False, diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index a1c87926a5..3f103f23cc 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -502,6 +502,29 @@ class OrganizerFilterForm(FilterForm): return qs +class GiftCardFilterForm(FilterForm): + query = forms.CharField( + label=_('Search query'), + widget=forms.TextInput(attrs={ + 'placeholder': _('Search query'), + 'autofocus': 'autofocus' + }), + required=False + ) + + def __init__(self, *args, **kwargs): + kwargs.pop('request') + super().__init__(*args, **kwargs) + + def filter_qs(self, qs): + fdata = self.cleaned_data + + if fdata.get('query'): + query = fdata.get('query') + qs = qs.filter(secret__icontains=query) + return qs + + class EventFilterForm(FilterForm): orders = { 'slug': 'slug', diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 8f7c965160..9164fc3a33 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -4,6 +4,7 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import RegexValidator +from django.db.models import Q from django.utils.safestring import mark_safe from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django_scopes.forms import SafeModelMultipleChoiceField @@ -12,7 +13,7 @@ from i18nfield.forms import I18nFormField, I18nTextarea from pretix.api.models import WebHook from pretix.api.webhooks import get_all_webhook_events from pretix.base.forms import I18nModelForm, SettingsForm -from pretix.base.models import Device, Organizer, Team +from pretix.base.models import Device, GiftCard, Organizer, Team from pretix.control.forms import ( ExtFileField, FontSelect, MultipleLanguagesWidget, ) @@ -145,6 +146,7 @@ class TeamForm(forms.ModelForm): model = Team fields = ['name', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams', 'can_change_organizer_settings', + 'can_manage_gift_cards', 'can_change_event_settings', 'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers', 'can_change_vouchers'] @@ -328,3 +330,29 @@ class WebHookForm(forms.ModelForm): field_classes = { 'limit_events': SafeModelMultipleChoiceField } + + +class GiftCardCreateForm(forms.ModelForm): + value = forms.DecimalField( + label=_('Gift card value') + ) + + def __init__(self, *args, **kwargs): + self.organizer = kwargs.pop('organizer') + super().__init__(*args, **kwargs) + + def clean_secret(self): + s = self.cleaned_data['secret'] + if GiftCard.objects.filter( + secret=s + ).filter( + Q(issuer=self.organizer) | Q(issuer__gift_card_collector_acceptance__collector=self.organizer) + ).exists(): + raise ValidationError( + _('A gift card with the same secret already exists in your or an affiliated organizer account.') + ) + return s + + class Meta: + model = GiftCard + fields = ['secret', 'currency'] diff --git a/src/pretix/control/navigation.py b/src/pretix/control/navigation.py index 8428741693..a7e4d13df4 100644 --- a/src/pretix/control/navigation.py +++ b/src/pretix/control/navigation.py @@ -437,6 +437,16 @@ def get_organizer_navigation(request): 'active': 'organizer.device' in url.url_name, 'icon': 'tablet', }) + if 'can_manage_gift_cards' in request.orgapermset: + nav.append({ + 'label': _('Gift cards'), + 'url': reverse('control:organizer.giftcards', kwargs={ + 'organizer': request.organizer.slug + }), + 'active': 'organizer.giftcard' in url.url_name, + 'icon': 'credit-card', + }) + if 'can_change_organizer_settings' in request.orgapermset: nav.append({ 'label': _('Webhooks'), 'url': reverse('control:organizer.webhooks', kwargs={ diff --git a/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html b/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html new file mode 100644 index 0000000000..2f2cb8b3ba --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html @@ -0,0 +1,80 @@ +{% extends "pretixcontrol/organizers/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% load money %} +{% block inner %} +
| {% trans "Date" %} | +{% trans "Order" %} | +{% trans "Value" %} | +
|---|---|---|
| {{ t.datetime|date:"SHORT_DATETIME_FORMAT" }} | ++ {% if t.order %} + + {{ t.order.full_code }} + {% else %} + {% trans "Manual transaction" %} + {% endif %} + | ++ {{ t.value|money:card.currency }} + | +
| + | + | + + | + +
+ {% blocktrans trimmed %} + You haven't issued any gift cards yet. You can either set up a product in an event shop to sell gift cards, + or you can manually issue gift cards. + {% endblocktrans %} +
+ + {% trans "Manually issue a gift card" %} ++ {% trans "Manually issue a gift card" %} +
+| {% trans "Gift card code" %} | +{% trans "Creation date" %} | +{% trans "Current value" %} | ++ |
|---|---|---|---|
| + + {{ g.secret }} + + | +{{ g.issuance|date:"SHORT_DATETIME_FORMAT" }} | ++ {{ g.cached_value|money:g.currency }} + | ++ + + + | +