Login

Generic CSV Export

Author:
zbyte64
Posted:
June 10, 2008
Language:
Python
Version:
.96
Score:
10 (after 10 ratings)

This will generically add csv exporting to your views in the admin. It will default to exporting the entire table you see (without paging). If the table only has one column, it will export the fields the the model. You can overide this functionality.

I ended up creating my own admin/change_list.html to apply this functionality universally:

{% extends "admin/base_site.html" %} {% load adminmedia admin_list i18n %} {% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %} {% block bodyclass %}change-list{% endblock %} {% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %} {% block coltype %}flex{% endblock %} {% block content %} <div id="content-main"> {% block object-tools %} <ul class="object-tools"> <li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}{%endif%}" class="addlink">Export to CSV</a></li> {% if has_add_permission %} <li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li> {% endif %} </ul> {% endblock %} <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist"> {% block search %}{% search_form cl %}{% endblock %} {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} {% block filters %}{% filters cl %}{% endblock %} {% block result_list %}{% result_list cl %}{% endblock %} {% block pagination %}{% pagination cl %}{% endblock %} </div> </div> {% endblock %}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import csv
from django.http import HttpResponse, HttpResponseForbidden
from django.template.defaultfilters import slugify
from django.db.models.loading import get_model

def export(qs, fields=None):
    model = qs.model
    response = HttpResponse(mimetype='text/csv')
    response['Content-Disposition'] = 'attachment; filename=%s.csv' % slugify(model.__name__)
    writer = csv.writer(response)
    # Write headers to CSV file
    if fields:
        headers = fields
    else:
        headers = []
        for field in model._meta.fields:
            headers.append(field.name)
    writer.writerow(headers)
    # Write data to CSV file
    for obj in qs:
        row = []
        for field in headers:
            if field in headers:
                val = getattr(obj, field)
                if callable(val):
                    val = val()
                row.append(val)
        writer.writerow(row)
    # Return CSV file to browser as download
    return response

def admin_list_export(request, model_name, app_label, queryset=None, fields=None, list_display=True):
    """
    Put the following line in your urls.py BEFORE your admin include
    (r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/', 'util.csv_view.admin_list_export'),
    """
    if not request.user.is_staff:
        return HttpResponseForbidden()
    if not queryset:
        model = get_model(app_label, model_name)
        queryset = model.objects.all()
        filters = dict()
        for key, value in request.GET.items():
            if key not in ('ot', 'o'):
                filters[str(key)] = str(value)
        if len(filters):
            queryset = queryset.filter(**filters)
    if not fields:
        if list_display and len(queryset.model._meta.admin.list_display) > 1:
            fields = queryset.model._meta.admin.list_display
        else:
            fields = None
    return export(queryset, fields)
    """
    Create your own change_list.html for your admin view and put something like this in it:
    {% block object-tools %}
    <ul class="object-tools">
        <li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}{%endif%}" class="addlink">Export to CSV</a></li>
    {% if has_add_permission %}
        <li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li>
    {% endif %}
    </ul>
    {% endblock %}
    """

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

zbyte64 (on June 10, 2008):

Special thanks to snippet 591 which I shamelessly used to write this code

#

illsci (on September 14, 2008):

Can you show an example where you send the view a queryset and a set of fields please? I would like to see how to use this with a model class that has a reverse relation to another model class. For example if you had an inline edited object and wanted to output the total fields and associated records across that relation...

#

zbyte64 (on October 8, 2008):

If you want to export inline objects, csv is probably not a good idea. If you really really wanted to do csv export for such a thing there are a variety of ways of going about it, hence why this code does not support it.

As for queryset/filtering, it simply takes advantage of the admin filtering

#

denilton (on October 13, 2008):

Can you show an example of the view? 'util.csv_view.admin_list_export'

#

zehi (on November 28, 2008):

It works very nice with English characters. Ones I use something different, like Finnish for example, it gives a error: utils/csv_view.py in export, line 28

'ascii' codec can't encode character u'\xe4' in position 1: ordinal not in range(128)

#

Beuc (on January 6, 2009):

Here's a work-around for the unicode limitation of the csv module.

if callable(val):
    val = val()
# work around csv unicode limitation
if type(val) == unicode:
    val = val.encode("utf-8")
row.append(val)

I think this snippets needs an update. 2 things don't work:

  • 'queryset.model._meta.admin' isn't an object, but a string, resulting in an Exception

  • in the admin template ({%if request.GET%}) it seems 'request' isn't part of the context anymore.

#

sto (on January 29, 2009):

I've fixed the first problem (queryset.model._meta.admin) using the admin.site object (it keeps a registry of models and admins); the patch in diff format is:

-    if not fields:
-        if list_display and len(queryset.model._meta.admin.list_display) > 1:
-            fields = queryset.model._meta.admin.list_display
-        else:
-            fields = None
+    if not fields and list_display:
+        from django.contrib import admin
+        ld = admin.site._registry[queryset.model].list_display
+        if ld and len(ld) > 0: fields = ld

#

ssavelan (on February 21, 2009):

Thanks for the snippet, and nice patch, sto, that fixed up my problem!

#

cameronoliver (on February 23, 2009):

Hi. I've come up with a solution to the second problem (request not being part of the context anymore). Delete the code {%if request.GET%}?{{request.GET.urlencode}}{%endif%} and replace it with the following: {% for key, value in cl.params.items %}{% if forloop.first %}?{% else %}&{% endif %}{{ key }}={{ value }}{% endfor %}

Also, the snippet above doesn't account for pagination. The line if key not in ('ot', 'o'): should be replaced with if key not in ('ot', 'o', 'p'):

#

wardb (on March 31, 2009):

I had this working yesterday. Then /django/contrib/admin/templates/admin/change_list.html changed (r10121) to include a select "Actions" feature. Now exporting CVS, produces an error:

Exception Type: AttributeError
Exception Value: '<INSERT MODEL TO BE EXPORTED NAME HERE>' object has no attribute 'action_checkbox'
Exception Location: /<SNIP>/util/csv_view.py in export, line 24

And solution suggestions?

#

tnovelli (on May 27, 2009):

Solution to the last problem -- put this just before "# Write data to CSV file" in export():

# Exclude checkbox for Django 1.1 Admin Actions
if headers[0] == 'action_checkbox':
    del headers[0]

#

Pete (on August 9, 2011):

To fix the actionbox problem I changed as stated but somehow the export stops after the first fields in my model? Is this the wrong position?

if fields:
    headers = fields
    if headers[0] == 'action_checkbox':
       del headers[0]
else:
    headers = []
    for field in model._meta.fields:
        headers.append(field.name)

#

darkpixel (on December 1, 2011):

Something must be b0rked with the markdown formatter. I can't get my 4-space-indented text to show up as code blocks, and it is therefore unreadable on this site.

I added the ability to access related objects: https://gist.github.com/1418860

#

Please login first before commenting.