Writing a data shredder

If your plugin adds the ability to store personal data within pretix, you should also implement a “data shredder” to anonymize or pseudonymize the data later.

Shredder registration

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

 1from django.dispatch import receiver
 3from pretix.base.signals import register_data_shredders
 6@receiver(register_data_shredders, dispatch_uid="custom_data_shredders")
 7def register_shredder(sender, **kwargs):
 8    return [
 9        PluginDataShredder,
10    ]

The shredder class

class pretix.base.shredder.BaseDataShredder

The central object of each data shredder is the subclass of BaseDataShredder.


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


A short and unique identifier for this shredder. This should only contain lowercase letters and in most cases will be the same as your package name.

This is an abstract attribute, you must override this!


A human-readable name for what this shredder removes. This should be short but self-explanatory. Good examples include ‘E-Mail addresses’ or ‘Invoices’.

This is an abstract attribute, you must override this!


A more detailed description of what this shredder does. Can contain HTML.

This is an abstract attribute, you must override this!

BaseDataShredder.generate_files() List[Tuple[str, str, str]]

This method is called to export the data that is about to be shred and return a list of tuples consisting of a filename, a file type and file content.

You can also implement this as a generator and yield those tuples instead of returning a list of them.


This method is called to actually remove the data from the system. You should remove any database objects here.

You can call progress_callback with an integer value between 0 and 100 to communicate back your progress.

You should never delete LogEntry objects, but you might modify them to remove personal data. In this case, set the LogEntry.shredded attribute to True to show that this is no longer original log data.


For example, the core data shredder responsible for removing invoice address information including their history looks like this:

 1class InvoiceAddressShredder(BaseDataShredder):
 2    verbose_name = _('Invoice addresses')
 3    identifier = 'invoice_addresses'
 4    description = _('This will remove all invoice addresses from orders, '
 5                    'as well as logged changes to them.')
 7    def generate_files(self) -> List[Tuple[str, str, str]]:
 8        yield 'invoice-addresses.json', 'application/json', json.dumps({
 9            ia.order.code: InvoiceAddressSerializer(ia).data
10            for ia in InvoiceAddress.objects.filter(order__event=self.event)
11        }, indent=4)
13    @transaction.atomic
14    def shred_data(self):
15        InvoiceAddress.objects.filter(order__event=self.event).delete()
17        for le in self.event.logentry_set.filter(action_type="pretix.event.order.modified"):
18            d = le.parsed_data
19            if 'invoice_data' in d and not isinstance(d['invoice_data'], bool):
20                for field in d['invoice_data']:
21                    if d['invoice_data'][field]:
22                        d['invoice_data'][field] = '█'
23                le.data = json.dumps(d)
24                le.shredded = True
25                le.save(update_fields=['data', 'shredded'])