Add CSV Export to Wagtail's Modeladmin
source link: https://tkainrad.dev/posts/export-wagtail-modeladmin-tables-to-csv/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Introduction
Wagtail is a modern open source CMS written in Python and based on Django. It is easy to integrate with existing Django projects. Apart from traditional CMS features, it provides a nice UI for managing any Django database model via the modeladmin module.
The modeladmin IndexView lists entries for a specific model in tabular form. It is easy to define which columns should be included. Starting from here, there are buttons for editing, creating and deleting entries.
One feature is missing though: Data Export
As the data is already presented in a table, CSV is an obvious export format.
We will add an additional button to the modeladmin IndexView
This idea is not entirely original. This blog post and this StackOverflow question discuss the same thing and my code is heavily influenced by them. However, the solutions given at these sources are not quite ready to copy and paste, as they require some customization of the CSV exporting code for each model you want to export.
The code given in this blog post can be used with any Django model. Per default, all columns are exported, but this can easily be customized on a per-model basis.
Implementation
I will cover the different implementation parts in detail. If you just want to copy-paste and get on with your life, that’s fine too. Just make sure you copy all the given code snippets. It is fine to put everything into wagtail_hooks.py
, except the HTML template.
ButtonHelper
The first thing you need whenever you want to add custom functionality to Wagtail’s modeladmin is usually a ButtonHelper
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ExportButtonHelper(ButtonHelper):
export_button_classnames = ['icon', 'icon-download']
def export_button(self, classnames_add=None, classnames_exclude=None):
if classnames_add is None:
classnames_add = []
if classnames_exclude is None:
classnames_exclude = []
classnames = self.export_button_classnames + classnames_add
cn = self.finalise_classname(classnames, classnames_exclude)
text = _('Export {} to CSV'.format(self.verbose_name_plural.title()))
return {
'url': self.url_helper.get_action_url('export',
query_params=self.request.GET),
'label': text,
'classname': cn,
'title': text,
}
Most of this code is just to get the CSS classes for the button right. The CSS classes icon
and icon-download
will ensure a simple button with a download icon and some text.
AdminURLHelper
Next, we need an AdminURLHelper that helps with generation, naming, and referencing of our new export
URL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ExportAdminURLHelper(AdminURLHelper):
non_object_specific_actions = ('create', 'choose_parent', 'index',
'export')
def get_action_url(self, action, *args, **kwargs):
query_params = kwargs.pop('query_params', None)
url_name = self.get_action_url_name(action)
if action in self.non_object_specific_actions:
url = reverse(url_name)
else:
url = reverse(url_name, args=args, kwargs=kwargs)
if query_params:
url += '?{params}'.format(params=query_params.urlencode())
return url
def get_action_url_pattern(self, action):
if action in self.non_object_specific_actions:
return self._get_action_url_pattern(action)
return self._get_object_specific_action_url_pattern(action)
Once again, this looks a little more complicated than it is. We just need to add the export
action to the non_object_specific_actions
, because Wagtail treats actions as object-specific per default and will attempt to add the an object’s PK to the URL. Additionally, the URL helper appends the modeladmin filters to the action so that only the filtered data is exported.
ExportView
Finally, we need an ExportView
that implements the CSV export. For this, we will use some help from django-queryset-csv
.
Install via
pip install django-queryset-csv
Using this, our view is very simple:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ExportView(IndexView):
model_admin = None
def export_csv(self):
if (self.model_admin is None) or not hasattr(self.model_admin,
'csv_export_fields'):
data = self.queryset.all().values()
else:
data = self.queryset.all().values(
*self.model_admin.csv_export_fields)
return render_to_csv_response(data)
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
return self.export_csv()
It is worth to note the model_admin
field. We will use this for specifying a custom list of exported fields. Lines 5 and 6 make sure that whenever model_admin
is set and the csv_export_fields
attribute is given, the custom field list is used instead of the default behavior that just exports all fields.
Mixin
Making use of Python’s Multiple-Inheritance system, we can create a Mixin that we will later use to override button_helper_class
, url_helper_class
, export_view_class
and get_admin_urls_for_registration
all at once:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ExportModelAdminMixin(object):
button_helper_class = ExportButtonHelper
url_helper_class = ExportAdminURLHelper
export_view_class = ExportView
def get_admin_urls_for_registration(self):
urls = super().get_admin_urls_for_registration()
urls += (url(self.url_helper.get_action_url_pattern('export'),
self.export_view,
name=self.url_helper.get_action_url_name('export')), )
return urls
def export_view(self, request):
kwargs = {'model_admin': self}
view_class = self.export_view_class
return view_class.as_view(**kwargs)(request)
HTML Template
Now, we need to create an HTML template that includes our new button plus the original modeladmin buttons:
1
2
3
4
5
6
{% extends "modeladmin/index.html" %}
{% block header_extra %}
{% include 'modeladmin/includes/button.html' with button=view.button_helper.export_button %}
{{ block.super }}{% comment %}Display the original buttons {% endcomment %}
{% endblock %}
Enabling CSV Export for Models
To make a modeladmin table exportable, just add the mixin to your ModelAdmin definitions in wagtail_hooks.py
and set the index_template_name
:
class FooModelAdmin(ExportModelAdminMixin, ModelAdmin):
index_template_name = "wagtailadmin/export_csv.html"
If you want to customize the CSV columns, you can use our new optionl csv_export_fields
attribute. It even allows to export attributes of related tables using regular Django ORM syntax (__
):
class FooModelAdmin(ExportModelAdminMixin, ModelAdmin):
index_template_name = "wagtailadmin/export_admin.html"
csv_export_fields = [
'bar', 'foobar', 'other_model__attribute'
]
Conclusion
In 2020, Wagtail is almost certainly the best option to add CMS functionality to a Django project. This post illustrates its extensibility. In a couple of minutes, you can enable CSV export for all your Django models.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK