├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── admin_export
├── __init__.py
├── admin.py
├── models.py
├── templates
│ └── admin_export
│ │ ├── export.html
│ │ └── fields.html
├── urls.py
└── views.py
├── pytest.ini
├── requirements.txt
├── setup.py
├── test_requirements.txt
└── tests
├── __init__.py
├── models.py
├── settings.py
└── test_admin_export.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .cache
41 | nosetests.xml
42 | coverage.xml
43 |
44 | # Translations
45 | *.mo
46 | *.pot
47 |
48 | # Django stuff:
49 | *.log
50 |
51 | # Sphinx documentation
52 | docs/_build/
53 |
54 | # PyBuilder
55 | target/
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2014 Burke Software and Consulting LLC
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | 3. The name of the author may not be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include admin_export/templates *
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project is no longer active. Check out a fork at https://github.com/fgmacedo/django-export-action
2 |
3 |
4 | django-admin-export
5 | ===================
6 |
7 | Generic export to XLSX/HTML/CSV action for the Django admin interface.
8 |
9 | Meant for fast and simple exports and lets you choose the data to export.
10 |
11 | Features
12 | --------
13 | - Drop in application
14 | - Traverse model relations recursively
15 |
16 | django-admin-export is built with [django-report-utils](https://github.com/burke-software/django-report-utils).
17 | For a a full query builder try using [django-report-builder](https://github.com/burke-software/django-report-builder).
18 |
19 | Install
20 | -------
21 | 1. ``pip install django-admin-export``
22 | 2. Add ``admin_export`` to INSTALLED_APPS
23 | 3. Add ``url(r'^admin_export/', include("admin_export.urls", namespace="admin_export")),`` to your project's urls.py
24 |
25 | Usage
26 | -----
27 | Go to any admin page, select fields, then select the export to xls action. Then check off any fields you want to export.
28 |
29 | Running tests
30 | -------------
31 |
32 | 1. Acquire a checkout of the repository
33 | 2. ``pip install -e . -r test_requirements.txt``
34 | 3. ``py.test tests``
35 |
36 | Security
37 | --------
38 |
39 | This project assumes staff users are trusted. There may be ways for users to manipulate this project to get more data access than they should have.
40 |
--------------------------------------------------------------------------------
/admin_export/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burke-software/django-admin-export/65f31edd1149d859888c27cabd60222c227729cf/admin_export/__init__.py
--------------------------------------------------------------------------------
/admin_export/admin.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | from django.contrib import admin
3 | from django.contrib.contenttypes.models import ContentType
4 | from django.core.urlresolvers import reverse, NoReverseMatch
5 | from django.http import HttpResponseRedirect
6 |
7 |
8 | def export_simple_selected_objects(modeladmin, request, queryset):
9 | selected = list(queryset.values_list('id', flat=True))
10 | ct = ContentType.objects.get_for_model(queryset.model)
11 |
12 | try:
13 | url = reverse("admin_export:export")
14 | except NoReverseMatch: # Old configuration, maybe? Fall back to old URL scheme.
15 | url = "/admin_export/export_to_xls/"
16 |
17 | if len(selected) > 1000:
18 | session_key = "admin_export_%s" % uuid.uuid4()
19 | request.session[session_key] = selected
20 | return HttpResponseRedirect("%s?ct=%s&session_key=%s" % (url, ct.pk, session_key))
21 | else:
22 | return HttpResponseRedirect("%s?ct=%s&ids=%s" % (url, ct.pk, ",".join(str(pk) for pk in selected)))
23 |
24 | export_simple_selected_objects.short_description = "Export selected items..."
25 |
26 | admin.site.add_action(export_simple_selected_objects)
27 |
--------------------------------------------------------------------------------
/admin_export/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burke-software/django-admin-export/65f31edd1149d859888c27cabd60222c227729cf/admin_export/models.py
--------------------------------------------------------------------------------
/admin_export/templates/admin_export/export.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/base_site.html' %}
2 | {% load i18n admin_urls admin_static %}
3 |
4 | {% block extrahead %}
5 | {{ block.super }}
6 |
7 |
28 | {% endblock %}
29 |
30 | {% block breadcrumbs %}
31 |
39 | {% endblock %}
40 |
41 |
42 | {% block content %}
43 | Export {{ opts.verbose_name_plural }} ({{ queryset.count }})
44 |
45 | {% for object in queryset|slice:":10" %}
46 | {{ object }}
47 | {% if not forloop.last %},{% endif %}
48 | {% endfor %}
49 | {% if queryset.count > 10 %}...{% endif %}
50 |
51 |
52 |
80 |
81 | {% endblock %}
82 |
--------------------------------------------------------------------------------
/admin_export/templates/admin_export/fields.html:
--------------------------------------------------------------------------------
1 | {% if table %}{% endif %}
48 |
--------------------------------------------------------------------------------
/admin_export/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url, patterns
2 | from django.contrib.admin.views.decorators import staff_member_required
3 | from .views import AdminExport
4 |
5 | view = staff_member_required(AdminExport.as_view())
6 | urlpatterns = patterns('',
7 | url(r'^export/$', view, name="export"),
8 | (r'^export_to_xls/$', view), # compatibility for users who upgrade without touching URLs
9 | )
10 |
--------------------------------------------------------------------------------
/admin_export/views.py:
--------------------------------------------------------------------------------
1 | from itertools import chain
2 | from django.contrib import admin
3 | from django.contrib.contenttypes.models import ContentType
4 | from django.http.response import HttpResponse
5 | from django.template import loader, Context
6 | from django.views.generic import TemplateView
7 | import csv
8 | from report_utils.mixins import GetFieldsMixin, DataExportMixin
9 | from report_utils.model_introspection import get_relation_fields_from_model
10 |
11 | HTML_TEMPLATE = r"""
12 |
13 |
14 |
15 |
16 | {{ title }}
17 |
18 |
19 | {{ title }}
20 |
21 | {% if header %}{% for h in header %}{{ h }} | {% endfor %}
{% endif %}
22 |
23 | {% for datum in data %}
24 | {% for cell in datum %}{{ cell|linebreaksbr }} | {% endfor %}
25 | {% endfor %}
26 |
27 |
28 |
29 |
30 | """
31 |
32 |
33 | class ExtDataExportMixin(DataExportMixin):
34 |
35 | def list_to_html_response(self, data, title='', header=None):
36 | html = loader.get_template_from_string(HTML_TEMPLATE).render(Context(locals()))
37 | return HttpResponse(html)
38 |
39 | def list_to_csv_response(self, data, title='', header=None):
40 | resp = HttpResponse(content_type="text/csv; charset=UTF-8")
41 | cw = csv.writer(resp)
42 | for row in chain([header] if header else [], data):
43 | cw.writerow([unicode(s).encode(resp._charset) for s in row])
44 | return resp
45 |
46 |
47 | class AdminExport(GetFieldsMixin, ExtDataExportMixin, TemplateView):
48 |
49 | """ Get fields from a particular model """
50 | template_name = 'admin_export/export.html'
51 |
52 | def get_queryset(self, model_class):
53 | if self.request.GET.get("session_key"):
54 | ids = self.request.session[self.request.GET["session_key"]]
55 | else:
56 | ids = self.request.GET['ids'].split(',')
57 | try:
58 | model_admin = admin.site._registry[model_class]
59 | except KeyError:
60 | raise ValueError("Model %r not registered with admin" % model_class)
61 | queryset = model_admin.get_queryset(self.request).filter(pk__in=ids)
62 | return queryset
63 |
64 | def get_model_class(self):
65 | model_class = ContentType.objects.get(id=self.request.GET['ct']).model_class()
66 | return model_class
67 |
68 | def get_context_data(self, **kwargs):
69 | context = super(AdminExport, self).get_context_data(**kwargs)
70 | field_name = self.request.GET.get('field', '')
71 | model_class = self.get_model_class()
72 | queryset = self.get_queryset(model_class)
73 | path = self.request.GET.get('path', '')
74 | path_verbose = self.request.GET.get('path_verbose', '')
75 | context['opts'] = model_class._meta
76 | context['queryset'] = queryset
77 | context['model_ct'] = self.request.GET['ct']
78 | context['related_fields'] = get_relation_fields_from_model(model_class)
79 | context.update(self.get_fields(model_class, field_name, path, path_verbose))
80 | return context
81 |
82 | def post(self, request, **kwargs):
83 | context = self.get_context_data(**kwargs)
84 | fields = []
85 | for field_name, value in request.POST.items():
86 | if value == "on":
87 | fields.append(field_name)
88 | data_list, message = self.report_to_list(
89 | context['queryset'],
90 | fields,
91 | self.request.user,
92 | )
93 | format = request.POST.get("__format")
94 | if format == "html":
95 | return self.list_to_html_response(data_list, header=fields)
96 | elif format == "csv":
97 | return self.list_to_csv_response(data_list, header=fields)
98 | else:
99 | return self.list_to_xlsx_response(data_list, header=fields)
100 |
101 | def get(self, request, *args, **kwargs):
102 | if request.REQUEST.get("related"): # Dispatch to the other view
103 | return AdminExportRelated.as_view()(request=self.request)
104 | return super(AdminExport, self).get(request, *args, **kwargs)
105 |
106 |
107 | class AdminExportRelated(GetFieldsMixin, TemplateView):
108 | template_name = 'admin_export/fields.html'
109 |
110 | def get(self, request, **kwargs):
111 | context = self.get_context_data(**kwargs)
112 | model_class = ContentType.objects.get(id=self.request.GET['model_ct']).model_class()
113 | field_name = request.GET['field']
114 | path = request.GET['path']
115 | field_data = self.get_fields(model_class, field_name, path, '')
116 | context['related_fields'], model_ct, context['path'] = self.get_related_fields(model_class, field_name, path)
117 | context['model_ct'] = model_ct.id
118 | context['field_name'] = field_name
119 | context['table'] = True
120 | context = dict(context.items() + field_data.items())
121 | return self.render_to_response(context)
122 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | DJANGO_SETTINGS_MODULE=tests.settings
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django
2 | django-report-utils>=0.2
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name = "django-admin-export",
5 | version = "2.0",
6 | author = "David Burke",
7 | author_email = "david@burkesoftware.com",
8 | description = ("Generic export action for Django admin interface"),
9 | license = "BSD",
10 | keywords = "django admin",
11 | url = "https://github.com/burke-software/django-admin-export",
12 | packages=find_packages(exclude=("tests",)),
13 | include_package_data=True,
14 | classifiers=[
15 | "Development Status :: 4 - Beta",
16 | 'Environment :: Web Environment',
17 | 'Framework :: Django',
18 | 'Programming Language :: Python',
19 | 'Intended Audience :: Developers',
20 | 'Intended Audience :: System Administrators',
21 | "License :: OSI Approved :: BSD License",
22 | ],
23 | install_requires=['django-report-utils'],
24 | )
25 |
--------------------------------------------------------------------------------
/test_requirements.txt:
--------------------------------------------------------------------------------
1 | pytest==2.6.4
2 | pytest-django==2.8.0
3 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # -- encoding: UTF-8 --
2 |
--------------------------------------------------------------------------------
/tests/models.py:
--------------------------------------------------------------------------------
1 | # -- encoding: UTF-8 --
2 | from django.db import models
3 |
4 |
5 | class TestModel(models.Model):
6 | value = models.IntegerField(unique=True)
7 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | # -- encoding: UTF-8 --
2 |
3 | SECRET_KEY = "hi"
4 |
5 | DATABASES = {
6 | "default": dict(ENGINE='django.db.backends.sqlite3', NAME=':memory:')
7 | }
8 |
9 | INSTALLED_APPS = (
10 | 'django.contrib.admin',
11 | 'django.contrib.auth',
12 | 'django.contrib.contenttypes',
13 | 'django.contrib.sessions',
14 | 'django.contrib.messages',
15 | 'django.contrib.staticfiles',
16 | 'admin_export',
17 | 'tests',
18 | )
19 |
20 | MIDDLEWARE_CLASSES = (
21 | 'django.contrib.sessions.middleware.SessionMiddleware',
22 | 'django.middleware.common.CommonMiddleware',
23 | 'django.middleware.csrf.CsrfViewMiddleware',
24 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
25 | 'django.contrib.messages.middleware.MessageMiddleware',
26 | )
27 |
--------------------------------------------------------------------------------
/tests/test_admin_export.py:
--------------------------------------------------------------------------------
1 | # -- encoding: UTF-8 --
2 | import random
3 | from admin_export.views import AdminExport
4 | from django.contrib import admin
5 | from django.contrib.contenttypes.models import ContentType
6 | import pytest
7 | from tests.models import TestModel
8 |
9 |
10 | class TestModelAdmin(admin.ModelAdmin):
11 | def get_queryset(self, request):
12 | return super(TestModelAdmin, self).get_queryset(request).filter(value__lt=request.magic)
13 |
14 |
15 | def queryset_valid(request, queryset):
16 | return all(x.value < request.magic for x in queryset)
17 |
18 |
19 | @pytest.mark.django_db
20 | def test_queryset_from_admin(rf, admin_user):
21 | for x in range(100):
22 | TestModel.objects.get_or_create(value=x)
23 | assert TestModel.objects.count() >= 100
24 |
25 | request = rf.get("/")
26 | request.user = admin_user
27 | request.magic = random.randint(10, 90)
28 | request.GET = {
29 | "ct": ContentType.objects.get_for_model(TestModel).pk,
30 | "ids": ",".join(str(id) for id in TestModel.objects.all().values_list("pk", flat=True))
31 | }
32 |
33 | old_registry = admin.site._registry
34 | admin.site._registry = {}
35 | admin.site.register(TestModel, TestModelAdmin)
36 | assert queryset_valid(request, admin.site._registry[TestModel].get_queryset(request))
37 | assert not queryset_valid(request, TestModel.objects.all())
38 |
39 | admin_export_view = AdminExport()
40 | admin_export_view.request = request
41 | admin_export_view.args = ()
42 | admin_export_view.kwargs = {}
43 | assert admin_export_view.get_model_class() == TestModel
44 | assert queryset_valid(request, admin_export_view.get_queryset(TestModel))
45 | admin.site._registry = old_registry
46 |
--------------------------------------------------------------------------------