Added a basic framework for data exporters

This commit is contained in:
Raphael Michel
2015-08-14 22:44:07 +02:00
parent e49a159ff4
commit c928864477
10 changed files with 221 additions and 1 deletions
+59
View File
@@ -0,0 +1,59 @@
.. highlight:: python
:linenothreshold: 5
Writing an exporter plugin
==========================
An is a method to export the product and order data in pretix for later use in another
context.
In this document, we will walk through the creation of an exporter output plugin. This
is very similar to creating a payment provider.
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
Exporter registration
---------------------
The exporter API does not make a lot of usage from signals, however, it does use a signal to get a list of
all available exporters. Your plugin should listen for this signal and return the subclass of
``pretix.base.exporter.BaseExporter``
that we'll soon create::
from django.dispatch import receiver
from pretix.base.signals import register_data_exporter
@receiver(register_data_exporter)
def register_data_exporter(sender, **kwargs):
from .exporter import MyExporter
return MyExporter
The exporter class
------------------
.. class:: pretix.base.exporter.BaseExporter
The central object of each exporter is the subclass of ``BaseExporter`` we already mentioned above.
In this section, we will discuss it's interface in detail.
.. py:attribute:: BaseExporter.event
The default constructor sets this property to the event we are currently
working for.
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: verbose_name
This is an abstract attribute, you **must** override this!
.. autoattribute:: export_form_fields
.. automethod:: render
This is an abstract method, you **must** override this!
+1
View File
@@ -10,4 +10,5 @@ Contents:
restriction
payment
ticketoutput
exporter
general
+4
View File
@@ -5,4 +5,8 @@ class PretixBaseConfig(AppConfig):
name = 'pretix.base'
label = 'pretixbase'
def ready(self):
from . import exporter # NOQA
from . import payment # NOQA
default_app_config = 'pretix.base.PretixBaseConfig'
+70
View File
@@ -0,0 +1,70 @@
from django import forms
from django.dispatch import receiver
from django.http import HttpRequest, HttpResponse, JsonResponse
from pretix.base.signals import register_data_exporters
class BaseExporter:
"""
This is the base class for all data exporters
"""
def __init__(self, event):
self.event = event
def __str__(self):
return self.identifier
@property
def verbose_name(self) -> str:
"""
A human-readable name for this exporter. This should be short but
self-explaining. Good examples include 'JSON' or 'Microsoft Excel'.
"""
raise NotImplementedError() # NOQA
@property
def identifier(self) -> str:
"""
A short and unique identifier for this exporter.
This should only contain lowercase letters and in most
cases will be the same as your packagename.
"""
raise NotImplementedError() # NOQA
@property
def export_form_fields(self) -> dict:
"""
When the event's administrator administrator visits the export page, this method
is called to return the configuration fields available.
It should therefore return a dictionary where the keys should be field names and
the values should be corresponding Django form fields.
We suggest that you return an ``OrderedDict`` object instead of a dictionary.
Your implementation could look like this::
@property
def export_form_fields(self):
return OrderedDict(
[
('tab_width',
forms.IntegerField(
label=_('Tab width'),
default=4
))
]
)
"""
return {}
def render(self, request: HttpRequest) -> HttpResponse:
"""
Render the exported file and return a request that either contains the file
or redirects to it.
:type request: HttpRequest
:param request: The HTTP request of the user requesting the export
"""
raise NotImplementedError() # NOQA
+1
View File
@@ -10,6 +10,7 @@ class PluginType(Enum):
RESTRICTION = 1
PAYMENT = 2
ADMINFEATURE = 3
EXPORT = 4
def get_all_plugins() -> "List[class]":
+8
View File
@@ -69,3 +69,11 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
register_ticket_outputs = EventPluginSignal(
providing_args=[]
)
"""
This signal is sent out to get all known data exporters. Receivers should return a
subclass of pretix.base.exporter.BaseExporter
"""
register_data_exporters = EventPluginSignal(
providing_args=[]
)
@@ -114,6 +114,12 @@
{% trans "Overview" %}
</a>
</li>
<li>
<a href="{% url 'control:event.orders.export' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if url_name == "event.orders.export" %}class="active"{% endif %}>
{% trans "Export" %}
</a>
</li>
</ul>
</li>
{% endif %}
@@ -0,0 +1,25 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %}
{% block content %}
<h1>{% trans "Data export" %}</h1>
{% for e in exporters %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ e.verbose_name }}</h3>
</div>
<div class="panel-body">
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<input type="hidden" name="exporter" value="{{ e.identifier }}" />
{% bootstrap_form e.form layout='horizontal' %}
<button class="btn btn-primary pull-right" type="submit">
<span class="icon icon-upload"></span> {% trans "Start export" %}
</button>
</form>
</div>
</div>
{% endfor %}
{% endblock %}
+1
View File
@@ -65,6 +65,7 @@ urlpatterns = [
name='event.order.extend'),
url(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
url(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'),
url(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'),
url(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'),
url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
])),
+46 -1
View File
@@ -1,5 +1,6 @@
from itertools import groupby
from django import forms
from django.contrib import messages
from django.db.models import Q, Count, Sum
from django.http import HttpResponse
@@ -11,7 +12,9 @@ from django.views.generic import DetailView, ListView, TemplateView, View
from pretix.base.models import Item, ItemCategory, Order, OrderPosition, Quota
from pretix.base.services.orders import mark_order_paid
from pretix.base.signals import register_payment_providers
from pretix.base.signals import (
register_data_exporters, register_payment_providers,
)
from pretix.control.forms.orders import ExtendForm
from pretix.control.permissions import EventPermissionRequiredMixin
@@ -325,3 +328,45 @@ class OrderGo(EventPermissionRequiredMixin, View):
except Order.DoesNotExist:
messages.error(request, _('There is no order with the given order code.'))
return redirect('control:event.orders', event=request.event.slug, organizer=request.event.organizer.slug)
class ExportView(EventPermissionRequiredMixin, TemplateView):
permission = 'can_view_orders'
template_name = 'pretixcontrol/orders/export.html'
@cached_property
def exporters(self):
exporters = []
responses = register_data_exporters.send(self.request.event)
for receiver, response in responses:
ex = response(self.request.event)
ex.form = forms.Form(
data=(self.request.POST if self.request.method == 'POST' else None)
)
ex.form.fields = ex.export_form_fields
exporters.append(ex)
return exporters
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters
return ctx
@cached_property
def exporter(self):
for ex in self.exporters:
if ex.identifier == self.request.POST.get("exporter"):
return ex
def post(self, *args, **kwargs):
if not self.exporter:
messages.error(self.request, _('The selected exporter was not found.'))
return redirect('control:event.orders.export', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.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(*args, **kwargs)
return self.exporter.render(self.request)