Extending the import process

It’s possible through the backend to import objects into pretix, for example orders from a legacy ticketing system. If your plugin defines additional data structures around those objects, it might be useful to make it possible to import them as well.

Import process

Here’s a short description of pretix’ import process to show you where the system will need to interact with your plugin. You can find more detailed descriptions of the attributes and methods further below.

  1. The user uploads a CSV file. The system tries to parse the CSV file and understand its column headers.

  2. A preview of the file is shown to the user and the user is asked to assign the various different input parameters to columns of the file or static values. For example, the user either needs to manually select a product or specify a column that contains a product. For this purpose, a select field is rendered for every possible input column, allowing the user to choose between a default/empty value (defined by your default_value/default_label) attributes, the columns of the uploaded file, or a static value (defined by your static_choices method).

  3. The user submits its assignment and the system uses the resolve method of all columns to get the raw value for all columns.

  4. The system uses the clean method of all columns to verify that all input fields are valid and transformed to the correct data type.

  5. The system prepares internal model objects (Order etc) and uses the assign method of all columns to assign these objects with actual values.

  6. The system saves all of these model objects to the database in a database transaction. Plugins can create additional objects in this stage through their save method.

Column registration

The import API does not make a lot of usage from signals, however, it does use a signal to get a list of all available import columns. Your plugin should listen for this signal and return the subclass of pretix.base.modelimport.ImportColumn that we’ll provide in this plugin:

 1from django.dispatch import receiver
 2
 3from pretix.base.signals import order_import_columns
 4
 5
 6@receiver(order_import_columns, dispatch_uid="custom_columns")
 7def register_column(sender, **kwargs):
 8    return [
 9        EmailColumn(sender),
10    ]

Similar signals exist for other objects:

pretix.base.signals.voucher_import_columns = <pretix.base.signals.EventPluginSignal object>

This signal is sent out if the user performs an import of vouchers from an external source. You can use this to define additional columns that can be read during import. You are expected to return a list of instances of ImportColumn subclasses.

As with all event-plugin signals, the sender keyword argument will contain the event.

The column class API

class pretix.base.modelimport.ImportColumn

The central object of each import extension is the subclass of ImportColumn.

ImportColumn.event

The default constructor sets this property to the event we are currently working for.

ImportColumn.identifier

Unique, internal name of the column.

This is an abstract attribute, you must override this!

ImportColumn.verbose_name

Human-readable description of the column

This is an abstract attribute, you must override this!

ImportColumn.default_value

Internal default value for the assignment of this column. Defaults to empty. Return None to disable this option.

ImportColumn.default_label

Human-readable description of the default assignment of this column, defaults to “Keep empty”.

ImportColumn.initial

Initial value for the form component

ImportColumn.static_choices()

This will be called when rendering the form component and allows you to return a list of values that can be selected by the user statically during import.

Returns:

list of 2-tuples of strings

ImportColumn.resolve(settings, record)

This method will be called to get the raw value for this field, usually by either using a static value or inspecting the CSV file for the assigned header. You usually do not need to implement this on your own, the default should be fine.

ImportColumn.clean(value, previous_values)

Allows you to validate the raw input value for your column. Raise ValidationError if the value is invalid. You do not need to include the column or row name or value in the error message as it will automatically be included.

Parameters:
  • value – Contains the raw value of your column as returned by resolve. This can usually be None, e.g. if the column is empty or does not exist in this row.

  • previous_values – Dictionary containing the validated values of all columns that have already been validated.

ImportColumn.assign(value, obj, **kwargs)

This will be called to perform the actual import. You are supposed to set attributes on the obj or other related objects that get passed in based on the input value. This is called before the actual database transaction, so the input objects do not yet have a primary key. If you want to create related objects, you need to place them into some sort of internal queue and persist them when save is called.

ImportColumn.save(obj)

This will be called to perform the actual import. This is called inside the actual database transaction and the input object obj has already been saved to the database.

Example

For example, the import column responsible for assigning email addresses looks like this:

 1class EmailColumn(ImportColumn):
 2    identifier = 'email'
 3    verbose_name = _('E-mail address')
 4
 5    def clean(self, value, previous_values):
 6        if value:
 7            EmailValidator()(value)
 8        return value
 9
10    def assign(self, value, order, position, invoice_address, **kwargs):
11        order.email = value