.. _export: ====== Export ====== Exporting is implemented in this project by creating an export job. The job is represented by :class:`~export.models.FileExport` model, executed by :func:`~export.tasks.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 :class:`~megforms.toolbar.ExportToolbarItem` to the interface .. code-block:: python :caption: Example code creating an export toolbar item ExportToolbarItem( gettext('Export'), exporter=Exporter, filters=self.filters_key, params={ 'institution_id': 1', }, ) Link to the url ---------------- To manually construct a url to :class:`~export.views.export_views.ExportView`, you need to pass the following as GET parameter: ``exporter`` path A dotted path to the exporter class. This path must start with ``export.exporters.base.``. ``filters`` (optional) If using a :class:`~megforms.filter_form.OverviewFilterForm` to filter data, pass its url hash to this argument Additional parameters If the export class accepts any parameters, they can be passed as additional GET arguments .. code-block:: python :caption: Example code creating a url to trigger export url: str = reverse('export-view') exporter: str = get_dotted_path(exporter) export_url: str = f"{url}?exporter={exporter}&filters={filters}&institution_id={institution_id}" Manually trigger export ----------------------------------------------- Export can be triggered without using :class:`~export.views.export_views.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 :class:`~export.models.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 .. code-block:: python :caption: 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 :class:`~export.exporters.base.BaseExporter` with a ``__call__`` method. Any export parameters will be passed as GET parameters. The :meth:`export.exporters.base.BaseExporter.collect_params` should parse those parameters into a dictionary to make it available for the export job. .. code-block:: python :caption: ``export/exporters/pandas.py`` An example of a custom Exporter class with custom parameter and error handling :emphasize-lines: 29-38 :linenos: from django.contrib.contenttypes.models import ContentType from django.core.files import File from django.core.files.base import ContentFile from django.db.models import Model from pandas import DataFrame from export.exceptions import ExportError from export.exporters.base import BaseExporter class PandasDataframeExporter(BaseExporter): @classmethod def collect_params(cls, request: HttpRequest) -> dict: # Collect 'model' parameter from request return { 'model': request.GET.get('model'), **super().collect_params(request), } @property def model(self) -> Model: model: str = self.export.parameters.get('model') try: ct: ContentType = ContentType.objects.get(modelname=model) return ct.model_class() except Exception as e: raise ExportError(f"'{model}' is not a valid django model") from e def __call__(self) -> File: from django_pandas.io import read_frame df: DataFrame = read_frame( self.model.objects.all(), fieldnames=self.export.parameters.get('fieldnames', ()), ) output: File = ContentFile(content="", name=f"{self.model._meta.model_name}.csv") df.to_csv(output) return output The exporter can later be referenced by its classpath :class:`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 :meth:`export.views.export_views.ExportView.get_export_params` by reading the GET parameters and sanitizing them.