Organizer/MultiEvent-Exports (#1684)

Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
Martin Gross
2020-06-16 11:06:40 +02:00
committed by GitHub
parent e895c13b28
commit 0b20d3f6f8
14 changed files with 384 additions and 78 deletions

View File

@@ -411,6 +411,14 @@ def get_organizer_navigation(request):
'active': url.url_name == 'organizer',
'icon': 'calendar',
},
{
'label': _('Export'),
'url': reverse('control:organizer.export', kwargs={
'organizer': request.organizer.slug,
}),
'active': 'organizer.export' in url.url_name,
'icon': 'download',
},
]
if 'can_change_organizer_settings' in request.orgapermset:
nav.append({

View File

@@ -0,0 +1,37 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %}
{% block content %}
<h1>
{% trans "Data export" %}
{% if "identifier" in request.GET %}
<a href="?" class="btn btn-default">{% trans "Show all" %}</a>
{% endif %}
</h1>
{% for e in exporters %}
<details class="panel panel-default" {% if "identifier" in request.GET %}open{% endif %}>
<summary class="panel-heading">
<h3 class="panel-title">
{{ e.verbose_name }}
<i class="fa fa-angle-down collapse-indicator"></i>
</h3>
</summary>
<div id="{{ e.identifier }}">
<div class="panel-body">
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
{% csrf_token %}
<input type="hidden" name="exporter" value="{{ e.identifier }}" />
{% bootstrap_form e.form layout='control' %}
<button class="btn btn-primary pull-right flip" type="submit">
<span class="icon icon-upload"></span> {% trans "Start export" %}
</button>
</form>
</div>
</div>
</details>
{% endfor %}
{% endblock %}

View File

@@ -106,6 +106,8 @@ urlpatterns = [
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
name='organizer.team.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
url(r'^events/$', main.EventList.as_view(), name='events'),
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),

View File

@@ -1,4 +1,5 @@
import json
from datetime import timedelta
from decimal import Decimal
from django import forms
@@ -14,25 +15,32 @@ from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import (
CreateView, DeleteView, DetailView, FormView, ListView, UpdateView,
CreateView, DeleteView, DetailView, FormView, ListView, TemplateView,
UpdateView,
)
from pretix.api.models import WebHook
from pretix.base.auth import get_auth_backends
from pretix.base.models import (
Device, GiftCard, OrderPayment, Organizer, Team, TeamInvite, User,
CachedFile, Device, GiftCard, OrderPayment, Organizer, Team, TeamInvite,
User,
)
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
from pretix.base.models.giftcards import gen_giftcard_secret
from pretix.base.models.organizer import TeamAPIToken
from pretix.base.payment import PaymentException
from pretix.base.services.export import multiexport
from pretix.base.services.mail import SendMailException, mail
from pretix.base.signals import register_multievent_data_exporters
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.filter import (
EventFilterForm, GiftCardFilterForm, OrganizerFilterForm,
)
from pretix.control.forms.orders import ExporterForm
from pretix.control.forms.organizer import (
DeviceForm, EventMetaPropertyForm, GiftCardCreateForm, GiftCardUpdateForm,
OrganizerDeleteForm, OrganizerForm, OrganizerSettingsForm,
@@ -1127,3 +1135,97 @@ class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
'giftcard': self.object.pk
}
))
class ExportMixin:
@cached_property
def exporters(self):
exporters = []
events = self.request.user.get_events_with_permission('can_view_orders')
responses = register_multievent_data_exporters.send(self.request.organizer)
for ex in sorted([response(events) for r, response in responses], key=lambda ex: str(ex.verbose_name)):
if self.request.GET.get("identifier") and ex.identifier != self.request.GET.get("identifier"):
continue
# Use form parse cycle to generate useful defaults
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields
test_form.is_valid()
initial = {
k: v for k, v in test_form.cleaned_data.items() if ex.identifier + "-" + k in self.request.GET
}
ex.form = ExporterForm(
data=(self.request.POST if self.request.method == 'POST' else None),
prefix=ex.identifier,
initial=initial
)
ex.form.fields = ex.export_form_fields
ex.form.fields.update([
('events',
forms.ModelMultipleChoiceField(
queryset=events,
initial=events,
widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'}
),
label=_('Events'),
required=True
)),
])
exporters.append(ex)
return exporters
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, View):
known_errortypes = ['ExportError']
task = multiexport
def get_success_message(self, value):
return None
def get_success_url(self, value):
return reverse('cachedfile.download', kwargs={'id': str(value)})
def get_error_url(self):
return reverse('control:organizer.export', kwargs={
'organizer': self.request.organizer.slug
})
@cached_property
def exporter(self):
for ex in self.exporters:
if ex.identifier == self.request.POST.get("exporter"):
return ex
def post(self, request, *args, **kwargs):
if not self.exporter:
messages.error(self.request, _('The selected exporter was not found.'))
return redirect('control:organizer.export', kwargs={
'organizer': self.request.organizer.slug
})
if not self.exporter.form.is_valid():
messages.error(self.request, _('There was a problem processing your input. See below for error details.'))
return self.get(request, *args, **kwargs)
cf = CachedFile()
cf.date = now()
cf.expires = now() + timedelta(days=3)
cf.save()
return self.do(
organizer=self.request.organizer.id,
user=self.request.user.id,
fileid=str(cf.id),
provider=self.exporter.identifier,
form_data=self.exporter.form.cleaned_data
)
class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
template_name = 'pretixcontrol/organizers/export.html'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters
return ctx