Export

Exporting is implemented in this project by creating an export job. The job is represented by FileExport model, executed by export_file() celery job.

Invoking an exporter

Export is invoked by making a request to the export url. Once triggered, the view redirects to a progress view where user can view progress of the export, and a link to download a file once its procured.

Use toolbar item

The easiest way to add export to interface is by adding ExportToolbarItem to the interface

Example code creating an export toolbar item
ExportToolbarItem(
    gettext('Export'),
    exporter=Exporter,
    filters=self.filters_key,
    params={
        'institution_id': 1',
    },
)

Manually trigger export

Export can be triggered without using ExportView. This allows you to add complex parameters to the export object itself. However, the functionality needs to be re-implemented in the calling site:

  1. Create a FileExport instance. Populate parameters with all perameters required by your export class, remember to include parameters required by the base classes as well, such as audit form ids, institution slug, etc.

  2. Trigger celery job

  3. Redirect to status page

Sample code triggering export from another view. Replace XlsObservationExporter with whatever exporter class you’re using
def post(request, *args, **kwargs):
    exporter: FileExport = XlsObservationExporter.create_export(
        user=request.user,
        parameters={
            # TODO: populate parameters for this export, including filters, and any custom arguments
        },
    )
    exporter.save()
    exporter.run_job()
    return redirect(exporter.get_absolute_url())

Implementing new exporters

In order to implement a new type of export, you need to implement a subclass of BaseExporter with a __call__ method.

Any export parameters will be passed as GET parameters. The export.exporters.base.BaseExporter.collect_params() should parse those parameters into a dictionary to make it available for the export job.

export/exporters/pandas.py An example of a custom Exporter class with custom parameter and error handling
 1from django.contrib.contenttypes.models import ContentType
 2from django.core.files import File
 3from django.core.files.base import ContentFile
 4from django.db.models import Model
 5from pandas import DataFrame
 6
 7from export.exceptions import ExportError
 8from export.exporters.base import BaseExporter
 9
10
11class PandasDataframeExporter(BaseExporter):
12    @classmethod
13    def collect_params(cls, request: HttpRequest) -> dict:
14        # Collect 'model' parameter from request
15        return {
16            'model': request.GET.get('model'),
17            **super().collect_params(request),
18        }
19
20    @property
21    def model(self) -> Model:
22        model: str = self.export.parameters.get('model')
23        try:
24            ct: ContentType = ContentType.objects.get(modelname=model)
25            return ct.model_class()
26        except Exception as e:
27            raise ExportError(f"'{model}' is not a valid django model") from e
28
29    def __call__(self) -> File:
30        from django_pandas.io import read_frame
31        df: DataFrame = read_frame(
32            self.model.objects.all(),
33            fieldnames=self.export.parameters.get('fieldnames', ()),
34        )
35
36        output: File = ContentFile(content="", name=f"{self.model._meta.model_name}.csv")
37        df.to_csv(output)
38        return output

The exporter can later be referenced by its classpath export.exporters.pandas.PandasDataframeExporter:

from export.exporters.pandas import PandasDataframeExporter

ExportToolbarItem(
    gettext('Export all log entries as CSV'),
    exporter=PandasDataframeExporter,
    params={
        'model': 'logentry',
    },
)

Important

If your exporter requires any parameters besides the default, make sure they are handled in export.views.export_views.ExportView.get_export_params() by reading the GET parameters and sanitizing them.