mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Add event meta-data
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from django_countries.serializers import CountryFieldMixin
|
||||
from rest_framework.fields import Field
|
||||
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import Event, TaxRule
|
||||
@@ -6,12 +7,22 @@ from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import SubEventItem, SubEventItemVariation
|
||||
|
||||
|
||||
class MetaDataField(Field):
|
||||
|
||||
def to_representation(self, value):
|
||||
return {
|
||||
v.property.name: v.value for v in value.meta_values.all()
|
||||
}
|
||||
|
||||
|
||||
class EventSerializer(I18nAwareModelSerializer):
|
||||
meta_data = MetaDataField(source='*')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = ('name', 'slug', 'live', 'currency', 'date_from',
|
||||
'date_to', 'date_admission', 'is_public', 'presale_start',
|
||||
'presale_end', 'location', 'has_subevents')
|
||||
'presale_end', 'location', 'has_subevents', 'meta_data')
|
||||
|
||||
|
||||
class SubEventItemSerializer(I18nAwareModelSerializer):
|
||||
@@ -29,12 +40,13 @@ class SubEventItemVariationSerializer(I18nAwareModelSerializer):
|
||||
class SubEventSerializer(I18nAwareModelSerializer):
|
||||
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True)
|
||||
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True)
|
||||
meta_data = MetaDataField(source='*')
|
||||
|
||||
class Meta:
|
||||
model = SubEvent
|
||||
fields = ('id', 'name', 'date_from', 'date_to', 'active', 'date_admission',
|
||||
'presale_start', 'presale_end', 'location',
|
||||
'item_price_overrides', 'variation_price_overrides')
|
||||
'item_price_overrides', 'variation_price_overrides', 'meta_data')
|
||||
|
||||
|
||||
class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
|
||||
|
||||
@@ -15,7 +15,7 @@ class EventViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
lookup_url_kwarg = 'event'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.organizer.events.all()
|
||||
return self.request.organizer.events.prefetch_related('meta_values', 'meta_values__property')
|
||||
|
||||
|
||||
class SubEventFilter(FilterSet):
|
||||
|
||||
60
src/pretix/base/migrations/0075_auto_20170828_0901.py
Normal file
60
src/pretix/base/migrations/0075_auto_20170828_0901.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-28 09:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.base
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0074_auto_20170825_1258'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventMetaProperty',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(db_index=True, help_text='Can not contain spaces or special characters execpt underscores', max_length=50, validators=[django.core.validators.RegexValidator(message='The property name may only contain letters, numbers and underscores.', regex='^[a-zA-Z0-9_]+$')], verbose_name='Name')),
|
||||
('default', models.TextField()),
|
||||
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_properties', to='pretixbase.Organizer')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventMetaValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.TextField()),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_values', to='pretixbase.Event')),
|
||||
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_values', to='pretixbase.EventMetaProperty')),
|
||||
],
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SubEventMetaValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.TextField()),
|
||||
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subevent_values', to='pretixbase.EventMetaProperty')),
|
||||
('subevent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_values', to='pretixbase.SubEvent')),
|
||||
],
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='subeventmetavalue',
|
||||
unique_together=set([('subevent', 'property')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='eventmetavalue',
|
||||
unique_together=set([('event', 'property')]),
|
||||
),
|
||||
]
|
||||
@@ -452,6 +452,12 @@ class Event(EventMixin, LoggedModel):
|
||||
)
|
||||
).order_by('date_from', 'name')
|
||||
|
||||
@property
|
||||
def meta_data(self):
|
||||
data = {p.name: p.default for p in self.organizer.meta_properties.all()}
|
||||
data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()})
|
||||
return data
|
||||
|
||||
|
||||
class SubEvent(EventMixin, LoggedModel):
|
||||
"""
|
||||
@@ -541,6 +547,12 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
for si in SubEventItemVariation.objects.filter(subevent=self, price__isnull=False)
|
||||
}
|
||||
|
||||
@property
|
||||
def meta_data(self):
|
||||
data = self.event.meta_data
|
||||
data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()})
|
||||
return data
|
||||
|
||||
|
||||
def generate_invite_token():
|
||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||
@@ -589,3 +601,74 @@ class RequiredAction(models.Model):
|
||||
if response:
|
||||
return response
|
||||
return self.action_type
|
||||
|
||||
|
||||
class EventMetaProperty(LoggedModel):
|
||||
"""
|
||||
An organizer account can have EventMetaProperty objects attached to define meta information fields
|
||||
for its events. This information can be re-used for example in ticket layouts.
|
||||
|
||||
:param organizer: The organizer this property is defined for.
|
||||
:type organizer: Organizer
|
||||
:param name: Name
|
||||
:type name: Name of the property, used in various places
|
||||
:param default: Default value
|
||||
:type default: str
|
||||
"""
|
||||
organizer = models.ForeignKey(Organizer, related_name="meta_properties", on_delete=models.CASCADE)
|
||||
name = models.CharField(
|
||||
max_length=50, db_index=True,
|
||||
help_text=_(
|
||||
"Can not contain spaces or special characters execpt underscores"
|
||||
),
|
||||
validators=[
|
||||
RegexValidator(
|
||||
regex="^[a-zA-Z0-9_]+$",
|
||||
message=_("The property name may only contain letters, numbers and underscores."),
|
||||
),
|
||||
],
|
||||
verbose_name=_("Name"),
|
||||
)
|
||||
default = models.TextField(blank=True)
|
||||
|
||||
|
||||
class EventMetaValue(LoggedModel):
|
||||
"""
|
||||
A meta-data value assigned to an event.
|
||||
|
||||
:param event: The event this metadata is valid for
|
||||
:type event: Event
|
||||
:param property: The property this value belongs to
|
||||
:type property: EventMetaProperty
|
||||
:param value: The actual value
|
||||
:type value: str
|
||||
"""
|
||||
event = models.ForeignKey('Event', on_delete=models.CASCADE,
|
||||
related_name='meta_values')
|
||||
property = models.ForeignKey('EventMetaProperty', on_delete=models.CASCADE,
|
||||
related_name='event_values')
|
||||
value = models.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('event', 'property')
|
||||
|
||||
|
||||
class SubEventMetaValue(LoggedModel):
|
||||
"""
|
||||
A meta-data value assigned to a sub-event.
|
||||
|
||||
:param event: The event this metadata is valid for
|
||||
:type event: Event
|
||||
:param property: The property this value belongs to
|
||||
:type property: EventMetaProperty
|
||||
:param value: The actual value
|
||||
:type value: str
|
||||
"""
|
||||
subevent = models.ForeignKey('SubEvent', on_delete=models.CASCADE,
|
||||
related_name='meta_values')
|
||||
property = models.ForeignKey('EventMetaProperty', on_delete=models.CASCADE,
|
||||
related_name='subevent_values')
|
||||
value = models.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('subevent', 'property')
|
||||
|
||||
@@ -10,6 +10,7 @@ from pytz import common_timezones, timezone
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.models import Event, Organizer, TaxRule
|
||||
from pretix.base.models.event import EventMetaValue
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.control.forms import ExtFileField, SlugWidget
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
@@ -163,6 +164,22 @@ class EventWizardCopyForm(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class EventMetaValueForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.property = kwargs.pop('property')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['value'].required = False
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.property.default
|
||||
|
||||
class Meta:
|
||||
model = EventMetaValue
|
||||
fields = ['value']
|
||||
widgets = {
|
||||
'value': forms.TextInput
|
||||
}
|
||||
|
||||
|
||||
class EventUpdateForm(I18nModelForm):
|
||||
def clean_slug(self):
|
||||
return self.instance.slug
|
||||
|
||||
@@ -71,6 +71,14 @@ class OrganizerUpdateForm(OrganizerForm):
|
||||
return instance
|
||||
|
||||
|
||||
class EventMetaPropertyForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ['name', 'default']
|
||||
widgets = {
|
||||
'default': forms.TextInput()
|
||||
}
|
||||
|
||||
|
||||
class TeamForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.utils.functional import cached_property
|
||||
from i18nfield.forms import I18nInlineFormSet
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import SubEventItem
|
||||
|
||||
|
||||
@@ -99,3 +99,20 @@ class QuotaFormSet(I18nInlineFormSet):
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
|
||||
class SubEventMetaValueForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.property = kwargs.pop('property')
|
||||
self.default = kwargs.pop('default', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['value'].required = False
|
||||
self.fields['value'].widget.attrs['placeholder'] = self.default or self.property.default
|
||||
|
||||
class Meta:
|
||||
model = SubEventMetaValue
|
||||
fields = ['value']
|
||||
widgets = {
|
||||
'value': forms.TextInput
|
||||
}
|
||||
|
||||
@@ -15,6 +15,26 @@
|
||||
{% bootstrap_field form.date_admission layout="horizontal" %}
|
||||
{% bootstrap_field form.currency layout="horizontal" %}
|
||||
{% bootstrap_field form.is_public layout="horizontal" %}
|
||||
|
||||
{% if meta_forms %}
|
||||
<div class="form-group metadata-group">
|
||||
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||
<div class="col-md-9">
|
||||
{% for form in meta_forms %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.value.id_for_label }}">
|
||||
{{ form.property.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Display settings" %}</legend>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Organizer" %}</h1>
|
||||
@@ -28,6 +29,65 @@
|
||||
{% bootstrap_form_errors sform %}
|
||||
{% bootstrap_field sform.organizer_info_text layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Event metadata (advanced)" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can here define a set of metadata properties (i.e. variables) that you can later set for your
|
||||
events and re-use in places like ticket layouts. This is an useful timesaver if you create lots and
|
||||
lots of events.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
<div class="row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-6">
|
||||
{% bootstrap_field form.default layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 col-lg-1 text-right">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-6">
|
||||
{% bootstrap_field formset.empty_form.default layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2 col-lg-1 text-right">
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add property" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -27,6 +27,25 @@
|
||||
{% bootstrap_field form.location layout="horizontal" %}
|
||||
{% bootstrap_field form.date_admission layout="horizontal" %}
|
||||
{% bootstrap_field form.frontpage_text layout="horizontal" %}
|
||||
{% if meta_forms %}
|
||||
<div class="form-group metadata-group">
|
||||
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||
<div class="col-md-9">
|
||||
{% for form in meta_forms %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.value.id_for_label }}">
|
||||
{{ form.property.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_form form layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
|
||||
@@ -28,13 +28,14 @@ from pretix.base.models import (
|
||||
CachedTicket, Event, Item, ItemVariation, LogEntry, Order, OrderPosition,
|
||||
RequiredAction, TaxRule, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import EventMetaValue
|
||||
from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import build_preview_invoice_pdf
|
||||
from pretix.base.signals import event_live_issues, register_ticket_outputs
|
||||
from pretix.control.forms.event import (
|
||||
CommentForm, DisplaySettingsForm, EventSettingsForm, EventUpdateForm,
|
||||
InvoiceSettingsForm, MailSettingsForm, PaymentSettingsForm, ProviderForm,
|
||||
TaxRuleForm, TicketSettingsForm,
|
||||
CommentForm, DisplaySettingsForm, EventMetaValueForm, EventSettingsForm,
|
||||
EventUpdateForm, InvoiceSettingsForm, MailSettingsForm,
|
||||
PaymentSettingsForm, ProviderForm, TaxRuleForm, TicketSettingsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
@@ -44,7 +45,39 @@ from . import CreateView, UpdateView
|
||||
from ..logdisplay import OVERVIEW_BLACKLIST
|
||||
|
||||
|
||||
class EventUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
class MetaDataEditorMixin:
|
||||
meta_form = EventMetaValueForm
|
||||
meta_model = EventMetaValue
|
||||
|
||||
@cached_property
|
||||
def meta_forms(self):
|
||||
val_instances = {
|
||||
v.property_id: v for v in self.object.meta_values.all()
|
||||
}
|
||||
|
||||
formlist = []
|
||||
|
||||
for p in self.request.organizer.meta_properties.all():
|
||||
formlist.append(self._make_meta_form(p, val_instances))
|
||||
return formlist
|
||||
|
||||
def _make_meta_form(self, p, val_instances):
|
||||
return self.meta_form(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
instance=val_instances.get(p.pk, self.meta_model(property=p, event=self.object)),
|
||||
data=(self.request.POST if self.request.method == "POST" else None)
|
||||
)
|
||||
|
||||
def save_meta(self):
|
||||
for f in self.meta_forms:
|
||||
if f.cleaned_data.get('value'):
|
||||
f.save()
|
||||
elif f.instance and f.instance.pk:
|
||||
f.delete()
|
||||
|
||||
|
||||
class EventUpdate(EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
|
||||
model = Event
|
||||
form_class = EventUpdateForm
|
||||
template_name = 'pretixcontrol/event/settings.html'
|
||||
@@ -68,11 +101,14 @@ class EventUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
def get_context_data(self, *args, **kwargs) -> dict:
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['meta_forms'] = self.meta_forms
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.sform.save()
|
||||
self.save_meta()
|
||||
|
||||
if self.sform.has_changed():
|
||||
self.request.event.log_action('pretix.event.settings', user=self.request.user, data={
|
||||
k: self.request.event.settings.get(k) for k in self.sform.changed_data
|
||||
@@ -92,7 +128,7 @@ class EventUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid():
|
||||
if form.is_valid() and self.sform.is_valid() and all([f.is_valid() for f in self.meta_forms]):
|
||||
# reset timezone
|
||||
zone = timezone(self.sform.cleaned_data['timezone'])
|
||||
event = form.instance
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.core.files import File
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.forms import inlineformset_factory
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -13,10 +14,12 @@ from django.views.generic import (
|
||||
)
|
||||
|
||||
from pretix.base.models import Organizer, Team, TeamInvite, User
|
||||
from pretix.base.models.event import EventMetaProperty
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.control.forms.organizer import (
|
||||
OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm,
|
||||
EventMetaPropertyForm, OrganizerForm, OrganizerSettingsForm,
|
||||
OrganizerUpdateForm, TeamForm,
|
||||
)
|
||||
from pretix.control.permissions import OrganizerPermissionRequiredMixin
|
||||
from pretix.control.signals import nav_organizer
|
||||
@@ -108,10 +111,12 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
def get_context_data(self, *args, **kwargs) -> dict:
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['sform'] = self.sform
|
||||
context['formset'] = self.formset
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.save_formset(self.object)
|
||||
self.sform.save()
|
||||
if self.sform.has_changed():
|
||||
self.request.organizer.log_action(
|
||||
@@ -145,12 +150,40 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
|
||||
})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid() and self.sform.is_valid():
|
||||
if form.is_valid() and self.sform.is_valid() and self.formset.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
formsetclass = inlineformset_factory(
|
||||
Organizer, EventMetaProperty,
|
||||
form=EventMetaPropertyForm, can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
instance=self.object, queryset=self.object.meta_properties.all())
|
||||
|
||||
def save_formset(self, obj):
|
||||
for form in self.formset.initial_forms:
|
||||
if form in self.formset.deleted_forms:
|
||||
if not form.instance.pk:
|
||||
continue
|
||||
form.instance.delete()
|
||||
form.instance.pk = None
|
||||
elif form.has_changed():
|
||||
form.save()
|
||||
|
||||
for form in self.formset.extra_forms:
|
||||
if not form.has_changed():
|
||||
continue
|
||||
if self.formset._should_delete_form(form):
|
||||
continue
|
||||
form.instance.organizer = obj
|
||||
form.save()
|
||||
|
||||
|
||||
class OrganizerCreate(CreateView):
|
||||
model = Organizer
|
||||
|
||||
@@ -9,14 +9,16 @@ from django.utils.functional import cached_property
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import Quota, SubEventItem, SubEventItemVariation
|
||||
from pretix.control.forms.filter import SubEventFilterForm
|
||||
from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
QuotaFormSet, SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
||||
SubEventMetaValueForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views.event import MetaDataEditorMixin
|
||||
|
||||
|
||||
class SubEventList(EventPermissionRequiredMixin, ListView):
|
||||
@@ -85,7 +87,22 @@ class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
})
|
||||
|
||||
|
||||
class SubEventEditorMixin:
|
||||
class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
meta_form = SubEventMetaValueForm
|
||||
meta_model = SubEventMetaValue
|
||||
|
||||
def _make_meta_form(self, p, val_instances):
|
||||
if not hasattr(self, '_default_meta'):
|
||||
self._default_meta = self.request.event.meta_data
|
||||
|
||||
return self.meta_form(
|
||||
prefix='prop-{}'.format(p.pk),
|
||||
property=p,
|
||||
default=self._default_meta.get(p.name, ''),
|
||||
instance=val_instances.get(p.pk, self.meta_model(property=p, subevent=self.object)),
|
||||
data=(self.request.POST if self.request.method == "POST" else None)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def formset(self):
|
||||
extra = 0
|
||||
@@ -159,6 +176,7 @@ class SubEventEditorMixin:
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['formset'] = self.formset
|
||||
ctx['itemvar_forms'] = self.itemvar_forms
|
||||
ctx['meta_forms'] = self.meta_forms
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -210,7 +228,9 @@ class SubEventEditorMixin:
|
||||
return formlist
|
||||
|
||||
def is_valid(self, form):
|
||||
return form.is_valid() and all([f.is_valid() for f in self.itemvar_forms]) and self.formset.is_valid()
|
||||
return form.is_valid() and all([f.is_valid() for f in self.itemvar_forms]) and self.formset.is_valid() and (
|
||||
all([f.is_valid() for f in self.meta_forms])
|
||||
)
|
||||
|
||||
|
||||
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
|
||||
@@ -239,6 +259,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.save_formset(self.object)
|
||||
self.save_meta()
|
||||
|
||||
for f in self.itemvar_forms:
|
||||
f.save()
|
||||
@@ -308,5 +329,6 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
for f in self.itemvar_forms:
|
||||
f.instance.subevent = form.instance
|
||||
f.save()
|
||||
|
||||
self.object = form.instance
|
||||
self.save_meta()
|
||||
return ret
|
||||
|
||||
@@ -137,7 +137,7 @@ var editor = {
|
||||
if (d.content === "other") {
|
||||
o.setText(d.text);
|
||||
} else {
|
||||
o.setText(editor.text_samples[d.content]);
|
||||
o.setText(editor._get_text_sample(d.content));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,13 @@ var editor = {
|
||||
"addons": gettext("Addon 1\nAddon 2"),
|
||||
},
|
||||
|
||||
_get_text_sample: function (key) {
|
||||
if (key.startsWith('meta:')) {
|
||||
return key.substr(5);
|
||||
}
|
||||
return editor.text_samples[key];
|
||||
},
|
||||
|
||||
_load_pdf: function (dump) {
|
||||
// TODO: Loading indicators
|
||||
var url = editor.pdf_url;
|
||||
@@ -362,7 +369,7 @@ var editor = {
|
||||
if ($("#toolbox-content").val() === "other") {
|
||||
o.setText($("#toolbox-content-other").val());
|
||||
} else {
|
||||
o.setText(editor.text_samples[$("#toolbox-content").val()]);
|
||||
o.setText(editor._get_text_sample($("#toolbox-content").val()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -304,6 +304,11 @@
|
||||
<option value="addons">{% trans "List of Add-Ons" %}</option>
|
||||
<option value="organizer">{% trans "Organizer name" %}</option>
|
||||
<option value="organizer_info_text">{% trans "Organizer info text" %}</option>
|
||||
{% for p in request.organizer.meta_properties.all %}
|
||||
<option value="meta:{{ p.name }}">
|
||||
{% trans "Event attribute:" %} {{ p.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
<option value="other">{% trans "Other…" %}</option>
|
||||
</select>
|
||||
<textarea type="text" value="" class="input-block-level form-control"
|
||||
|
||||
@@ -69,6 +69,8 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
ev = op.subevent or order.event
|
||||
if o['content'] == 'other':
|
||||
return o['text'].replace("\n", "<br/>\n")
|
||||
elif o['content'].startswith('meta:'):
|
||||
return ev.meta_data.get(o['content'][5:])
|
||||
elif o['content'] == 'order':
|
||||
return order.code
|
||||
elif o['content'] == 'item':
|
||||
|
||||
@@ -175,3 +175,11 @@ pre.mail-preview {
|
||||
}
|
||||
|
||||
}
|
||||
.metadata-group {
|
||||
.form-group {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.row label {
|
||||
padding-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,19 @@ def organizer():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event(organizer):
|
||||
return Event.objects.create(
|
||||
def meta_prop(organizer):
|
||||
return organizer.meta_properties.create(name="type", default="Concert")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event(organizer, meta_prop):
|
||||
e = Event.objects.create(
|
||||
organizer=organizer, name='Dummy', slug='dummy',
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC),
|
||||
plugins='pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf'
|
||||
)
|
||||
e.meta_values.create(property=meta_prop, value="Conference")
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -48,11 +55,14 @@ def token_client(client, team):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def subevent(event):
|
||||
def subevent(event, meta_prop):
|
||||
event.has_subevents = True
|
||||
event.save()
|
||||
return event.subevents.create(name="Foobar",
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
se = event.subevents.create(name="Foobar",
|
||||
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
|
||||
|
||||
se.meta_values.create(property=meta_prop, value="Workshop")
|
||||
return se
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -13,6 +13,7 @@ TEST_EVENT_RES = {
|
||||
"location": None,
|
||||
"slug": "dummy",
|
||||
"has_subevents": False,
|
||||
"meta_data": {"type": "Conference"}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ TEST_SUBEVENT_RES = {
|
||||
'id': 1,
|
||||
'variation_price_overrides': [],
|
||||
'location': None,
|
||||
'item_price_overrides': []
|
||||
'item_price_overrides': [],
|
||||
'meta_data': {'type': 'Workshop'}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user