Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3 support, Django 1.8 ~ 1.10 #12

Closed
wants to merge 9 commits into from
28 changes: 28 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[run]
branch = True
source =
admin_export
omit =
*test*.py
tests/*
*/migrations/*

[report]
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover

# Don't complain about missing debug-only code:
def __repr__
if self\.debug

# Don't complain about default Django's code
from django.shortcuts import render
from django.contrib import admin

# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError

[html]
directory = htmlcov
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage*
.cache
nosetests.xml
coverage.xml
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Hello - this project is looking for a new maintainer. Please open an issue if you'd like to help maintain.
Hello - this project is looking for a new maintainer. Please open an issue if you'd like to help maintain.

Most pressing issue is to support modern python/django See [#6](https://github.com/burke-software/django-admin-export/issues/6)

Expand Down Expand Up @@ -32,9 +32,16 @@ Running tests
-------------

1. Acquire a checkout of the repository
2. ``pip install -e . -r test_requirements.txt``
2. ``pip install -e . -r requirements_test.txt``
3. ``py.test tests``

Or to run tests with all supported environments:

1. Acquire a checkout of the repository
2. Install tox with ``pip install tox``
3. ``tox``


Security
--------

Expand Down
5 changes: 3 additions & 2 deletions admin_export/templates/admin_export/export.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

{% block extrahead %}
{{ block.super }}
<script src="{% static "admin/js/jquery.min.js" %}"></script>
<script src="{% static "admin/js/vendor/jquery/jquery.min.js" %}"></script>
<script src="{% static "admin/js/jquery.init.js" %}"></script>
<script type="text/javascript">
(function ($) {
window.show_fields = function (event, model_ct, field, path, path_verbose) {
Expand All @@ -22,7 +23,7 @@
$('.check_field').prop('checked', checked);
});
});
}(jQuery));
}(django.jQuery));

</script>
{% endblock %}
Expand Down
8 changes: 4 additions & 4 deletions admin_export/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.conf.urls import url, patterns
from django.conf.urls import url
from django.contrib.admin.views.decorators import staff_member_required
from .views import AdminExport

view = staff_member_required(AdminExport.as_view())
urlpatterns = patterns('',
urlpatterns = [
url(r'^export/$', view, name="export"),
(r'^export_to_xls/$', view), # compatibility for users who upgrade without touching URLs
)
url(r'^export_to_xls/$', view), # compatibility for users who upgrade without touching URLs
]
11 changes: 6 additions & 5 deletions admin_export/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponse
from django.template import loader, Context
from django.template import Context, Engine
from django.utils.text import force_text
from django.views.generic import TemplateView
import csv
from report_utils.mixins import GetFieldsMixin, DataExportMixin
Expand Down Expand Up @@ -33,14 +34,14 @@
class ExtDataExportMixin(DataExportMixin):

def list_to_html_response(self, data, title='', header=None):
html = loader.get_template_from_string(HTML_TEMPLATE).render(Context(locals()))
html = Engine().from_string(HTML_TEMPLATE).render(Context(locals()))
return HttpResponse(html)

def list_to_csv_response(self, data, title='', header=None):
resp = HttpResponse(content_type="text/csv; charset=UTF-8")
cw = csv.writer(resp)
for row in chain([header] if header else [], data):
cw.writerow([unicode(s).encode(resp._charset) for s in row])
cw.writerow([force_text(s).encode(resp.charset) for s in row])
return resp


Expand Down Expand Up @@ -99,7 +100,7 @@ def post(self, request, **kwargs):
return self.list_to_xlsx_response(data_list, header=fields)

def get(self, request, *args, **kwargs):
if request.REQUEST.get("related"): # Dispatch to the other view
if request.GET.get("related", request.POST.get("related")): # Dispatch to the other view
return AdminExportRelated.as_view()(request=self.request)
return super(AdminExport, self).get(request, *args, **kwargs)

Expand All @@ -117,5 +118,5 @@ def get(self, request, **kwargs):
context['model_ct'] = model_ct.id
context['field_name'] = field_name
context['table'] = True
context = dict(context.items() + field_data.items())
context.update(field_data)
return self.render_to_response(context)
3 changes: 3 additions & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest==3.0.2
pytest-django==3.0.0
pytest-cov
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
'Intended Audience :: System Administrators',
"License :: OSI Approved :: BSD License",
],
install_requires=['django-report-utils'],
install_requires=['django-report-utils', 'openpyxl'],
)
2 changes: 0 additions & 2 deletions test_requirements.txt

This file was deleted.

9 changes: 9 additions & 0 deletions tests/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# coding: utf-8
from __future__ import unicode_literals

from django.contrib import admin

from .models import ModelUnderTest


admin.site.register(ModelUnderTest)
7 changes: 6 additions & 1 deletion tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
from django.db import models


class TestModel(models.Model):
class ModelUnderTest(models.Model):
value = models.IntegerField(unique=True)


class ModelWithRelated(models.Model):
mut = models.ForeignKey(ModelUnderTest)
value = models.IntegerField(unique=True)
20 changes: 20 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)

ROOT_URLCONF = 'tests.urls'

STATIC_URL = '/static/'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
106 changes: 94 additions & 12 deletions tests/test_admin_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
from admin_export.views import AdminExport
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.utils.http import urlencode
import pytest
from tests.models import TestModel
from .models import ModelUnderTest, ModelWithRelated


class TestModelAdmin(admin.ModelAdmin):
class ModelAdminTest(admin.ModelAdmin):
def get_queryset(self, request):
return super(TestModelAdmin, self).get_queryset(request).filter(value__lt=request.magic)
return super(ModelAdminTest, self).get_queryset(request).filter(value__lt=request.magic)


def queryset_valid(request, queryset):
Expand All @@ -19,27 +21,107 @@ def queryset_valid(request, queryset):
@pytest.mark.django_db
def test_queryset_from_admin(rf, admin_user):
for x in range(100):
TestModel.objects.get_or_create(value=x)
assert TestModel.objects.count() >= 100
ModelUnderTest.objects.get_or_create(value=x)
assert ModelUnderTest.objects.count() >= 100

request = rf.get("/")
request.user = admin_user
request.magic = random.randint(10, 90)
request.GET = {
"ct": ContentType.objects.get_for_model(TestModel).pk,
"ids": ",".join(str(id) for id in TestModel.objects.all().values_list("pk", flat=True))
"ct": ContentType.objects.get_for_model(ModelUnderTest).pk,
"ids": ",".join(str(id) for id in ModelUnderTest.objects.all().values_list("pk", flat=True))
}

old_registry = admin.site._registry
admin.site._registry = {}
admin.site.register(TestModel, TestModelAdmin)
assert queryset_valid(request, admin.site._registry[TestModel].get_queryset(request))
assert not queryset_valid(request, TestModel.objects.all())
admin.site.register(ModelUnderTest, ModelAdminTest)
assert queryset_valid(request, admin.site._registry[ModelUnderTest].get_queryset(request))
assert not queryset_valid(request, ModelUnderTest.objects.all())

admin_export_view = AdminExport()
admin_export_view.request = request
admin_export_view.args = ()
admin_export_view.kwargs = {}
assert admin_export_view.get_model_class() == TestModel
assert queryset_valid(request, admin_export_view.get_queryset(TestModel))
assert admin_export_view.get_model_class() == ModelUnderTest
assert queryset_valid(request, admin_export_view.get_queryset(ModelUnderTest))
admin.site._registry = old_registry


@pytest.mark.django_db
@pytest.mark.parametrize('output_name', ['html', 'csv'])
def test_AdminExport_list_to_method_response_should_return_200(admin_user, output_name):
for x in range(3):
ModelUnderTest.objects.get_or_create(value=x)

admin = AdminExport()
data = admin.report_to_list(ModelUnderTest.objects.all(), ['value'], admin_user)

method = getattr(admin, 'list_to_{}_response'.format(output_name))
res = method(data)
assert res.status_code == 200


@pytest.mark.django_db
@pytest.mark.parametrize('output_format', ['html', 'csv', 'xls'])
def test_AdminExport_post_should_return_200(admin_client, output_format):
for x in range(3):
ModelUnderTest.objects.get_or_create(value=x)

params = {
'ct': ContentType.objects.get_for_model(ModelUnderTest).pk,
'ids': ','.join(repr(pk) for pk in ModelUnderTest.objects.values_list('pk', flat=True))
}
data = {
"value": "on",
"__format": output_format,
}
url = "{}?{}".format(reverse('admin_export:export'), urlencode(params))
response = admin_client.post(url, data=data)
assert response.status_code == 200


@pytest.mark.django_db
def test_AdminExport_get_should_return_200(admin_client):
for x in range(3):
ModelUnderTest.objects.get_or_create(value=x)

params = {
'ct': ContentType.objects.get_for_model(ModelUnderTest).pk,
'ids': ','.join(repr(pk) for pk in ModelUnderTest.objects.values_list('pk', flat=True))
}
url = "{}?{}".format(reverse('admin_export:export'), urlencode(params))
response = admin_client.get(url)
assert response.status_code == 200


@pytest.mark.django_db
def test_AdminExport_with_related_get_should_return_200(admin_client):
mut, created = ModelUnderTest.objects.get_or_create(value=1)
for x in range(3):
ModelWithRelated.objects.get_or_create(value=x, mut=mut)

params = {
'related': True,
'model_ct': ContentType.objects.get_for_model(ModelWithRelated).pk,
'field': 'mut',
'path': 'mut.value',
}
url = "{}?{}".format(reverse('admin_export:export'), urlencode(params))
response = admin_client.get(url)
assert response.status_code == 200


@pytest.mark.django_db
def test_AdminExport_with_unregistered_model_should_raise_ValueError(admin_client):
mut, created = ModelUnderTest.objects.get_or_create(value=1)
for x in range(3):
ModelWithRelated.objects.get_or_create(value=x, mut=mut)

params = {
'ct': ContentType.objects.get_for_model(ModelWithRelated).pk,
'ids': ','.join(repr(pk) for pk in ModelWithRelated.objects.values_list('pk', flat=True))
}
url = "{}?{}".format(reverse('admin_export:export'), urlencode(params))

with pytest.raises(ValueError):
admin_client.get(url)
10 changes: 10 additions & 0 deletions tests/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# coding: utf-8
from __future__ import unicode_literals

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^admin_export/', include("admin_export.urls", namespace="admin_export")),
]
21 changes: 21 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[tox]
envlist =
{py27,py34,py35}-django1{8,9,10}

[testenv]
setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/admin_export
commands = py.test tests --cov=admin_export --cov-report=term
deps =
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11
-r{toxinidir}/requirements_test.txt

[testenv:py27-django110]
commands =
py.test tests --cov=admin_export --cov-report=html

[testenv:clean]
commands=
coverage erase