From 0e2ba06fb75be36a60aa3a30fd134f535f0becd9 Mon Sep 17 00:00:00 2001 From: steve Date: Sat, 16 Jan 2016 17:43:28 -0500 Subject: [PATCH] Add support for django 1.9 --- .travis.yml | 3 ++ djqscsv/djqscsv.py | 43 ++++++++++++------- .../djqscsv_tests/migrations/0001_initial.py | 36 ++++++++++++++++ test_app/djqscsv_tests/migrations/__init__.py | 0 4 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 test_app/djqscsv_tests/migrations/0001_initial.py create mode 100644 test_app/djqscsv_tests/migrations/__init__.py diff --git a/.travis.yml b/.travis.yml index 828a1ac..3f285f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,15 @@ env: - DJANGO=1.6 - DJANGO=1.7 - DJANGO=1.8 + - DJANGO=1.9 matrix: exclude: - python: "2.6" env: DJANGO=1.7 - python: "2.6" env: DJANGO=1.8 + - python: "2.6" + env: DJANGO=1.9 install: - pip install -q Django==$DJANGO - pip install -r dev_requirements.txt diff --git a/djqscsv/djqscsv.py b/djqscsv/djqscsv.py index c879f4b..034f689 100644 --- a/djqscsv/djqscsv.py +++ b/djqscsv/djqscsv.py @@ -5,13 +5,6 @@ from django.utils.text import slugify from django.http import HttpResponse -from django.conf import settings -if not settings.configured: - # required to import ValuesQuerySet - settings.configure() # pragma: no cover - -from django.db.models.query import ValuesQuerySet - from django.utils import six """ A simple python package for turning django models into csvs """ @@ -74,24 +67,44 @@ def write_csv(queryset, file_obj, **kwargs): # the CSV must always be built from a values queryset # in order to introspect the necessary fields. - if isinstance(queryset, ValuesQuerySet): + # However, repeated calls to values can expose fields that were not + # present in the original qs. If using `values` as a way to + # scope field permissions, this is unacceptable. The solution + # is to make sure values is called *once*. + + # perform an string check to avoid a non-existent class in certain + # versions + if type(queryset).__name__ == 'ValuesQuerySet': values_qs = queryset else: - values_qs = queryset.values() + # could be a non-values qs, or could be django 1.9+ + iterable_class = getattr(queryset, '_iterable_class', object) + if iterable_class.__name__ == 'ValuesIterable': + values_qs = queryset + else: + values_qs = queryset.values() try: - field_names = values_qs.field_names - + field_names = values_qs.query.values_select except AttributeError: - # in django1.5, empty querysets trigger - # this exception, but not django 1.6 - raise CSVException("Empty queryset provided to exporter.") + try: + field_names = values_qs.field_names + except AttributeError: + # in django1.5, empty querysets trigger + # this exception, but not django 1.6 + raise CSVException("Empty queryset provided to exporter.") extra_columns = list(values_qs.query.extra_select) if extra_columns: field_names += extra_columns - aggregate_columns = list(values_qs.query.aggregate_select) + try: + aggregate_columns = list(values_qs.query.annotation_select) + except AttributeError: + # this gets a deprecation warning in django 1.9 but is + # required in django<=1.7 + aggregate_columns = list(values_qs.query.aggregate_select) + if aggregate_columns: field_names += aggregate_columns diff --git a/test_app/djqscsv_tests/migrations/0001_initial.py b/test_app/djqscsv_tests/migrations/0001_initial.py new file mode 100644 index 0000000..22fbff9 --- /dev/null +++ b/test_app/djqscsv_tests/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2016-01-13 15:38 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, verbose_name=b'Name of Activity')), + ], + ), + migrations.CreateModel( + name='Person', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, verbose_name="Person's name")), + ('address', models.CharField(max_length=255)), + ('info', models.TextField(verbose_name=b'Info on Person')), + ('born', models.DateTimeField(default=datetime.datetime(2001, 1, 1, 1, 1))), + ('hobby', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='djqscsv_tests.Activity')), + ], + ), + ] diff --git a/test_app/djqscsv_tests/migrations/__init__.py b/test_app/djqscsv_tests/migrations/__init__.py new file mode 100644 index 0000000..e69de29