├── .coveragerc ├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── MANIFEST.in ├── Makefile ├── README.rst ├── django_tables2_reports ├── __init__.py ├── config.py ├── csv_to_xls │ ├── __init__.py │ ├── base.py │ ├── openpyxl_converter.py │ ├── pyexcelerator_converter.py │ └── xlwt_converter.py ├── locale │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── middleware.py ├── models.py ├── static │ └── img │ │ ├── csv_icon.png │ │ └── xls_icon.png ├── tables.py ├── templates │ └── django_tables2_reports │ │ └── table.html ├── tests.py ├── utils.py └── views.py ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── test_project ├── manage.py ├── run_tests.py ├── test_app │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── initial_data.json │ ├── models.py │ ├── templates │ │ └── test_app │ │ │ └── person_list.html │ ├── tests.py │ └── views.py └── test_project │ ├── __init__.py │ ├── settings.py │ ├── settings_no_debug.py │ ├── urls.py │ └── wsgi.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = django_tables2_reports 3 | omit = 4 | test_project/* 5 | django_tables2_reports/tests.py 6 | 7 | [report] 8 | exclude_lines = 9 | pragma: no cover 10 | def __repr__ 11 | raise NotImplementedError 12 | if __name__ == .__main__.: 13 | def parse_args -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | *.py~ 5 | local_settings.py 6 | .idea 7 | build 8 | dist 9 | *.egg-info 10 | .tox 11 | .coverage.* 12 | test-file*xls* 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | 5 | cache: pip 6 | 7 | python: 8 | - 2.7 9 | - 3.3 10 | - 3.4 11 | - 3.5 12 | - 3.6 13 | 14 | env: 15 | - DJANGO=1.3 16 | - DJANGO=1.4 17 | - DJANGO=1.5 18 | - DJANGO=1.6 19 | - DJANGO=1.7 20 | - DJANGO=1.8 21 | - DJANGO=1.9 22 | - DJANGO=1.10 23 | - DJANGO=1.11 24 | 25 | install: 26 | - pip install -q tox coverage 27 | - coverage erase 28 | 29 | script: 30 | - tox -e py${TRAVIS_PYTHON_VERSION//./}-django${DJANGO//./} 31 | 32 | after_success: 33 | - coverage combine 34 | - coveralls 35 | 36 | matrix: 37 | exclude: 38 | - python: 3.3 39 | env: DJANGO=1.3 40 | - python: 3.3 41 | env: DJANGO=1.4 42 | - python: 3.3 43 | env: DJANGO=1.5 44 | - python: 3.3 45 | env: DJANGO=1.6 46 | - python: 3.3 47 | env: DJANGO=1.7 48 | - python: 3.3 49 | env: DJANGO=1.9 50 | - python: 3.3 51 | env: DJANGO=1.10 52 | - python: 3.3 53 | env: DJANGO=1.11 54 | - python: 3.4 55 | env: DJANGO=1.3 56 | - python: 3.4 57 | env: DJANGO=1.4 58 | - python: 3.4 59 | env: DJANGO=1.5 60 | - python: 3.4 61 | env: DJANGO=1.6 62 | - python: 3.4 63 | env: DJANGO=1.7 64 | - python: 3.5 65 | env: DJANGO=1.3 66 | - python: 3.5 67 | env: DJANGO=1.4 68 | - python: 3.5 69 | env: DJANGO=1.5 70 | - python: 3.5 71 | env: DJANGO=1.6 72 | - python: 3.5 73 | env: DJANGO=1.7 74 | - python: 3.6 75 | env: DJANGO=1.3 76 | - python: 3.6 77 | env: DJANGO=1.4 78 | - python: 3.6 79 | env: DJANGO=1.5 80 | - python: 3.6 81 | env: DJANGO=1.6 82 | - python: 3.6 83 | env: DJANGO=1.7 84 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Releases 2 | ======== 3 | 4 | 0.1.0 (2017-06-19) 5 | ------------------ 6 | * maintenance release, 7 | * Django 1.8, 1.9, 1.10, 1.11 support, 8 | * openpyxl > 2.0.0 support, 9 | * recent django-tables2 support, 10 | * new maintainer `Michał Pasternak `_ 11 | 12 | 0.0.10 (2014-10-13) 13 | ------------------- 14 | * Fixes for xlsx Content-Type: 15 | * django-tables2-reports throws 500 Sever Error when report format is not recognized. 404 is more appropriate in this case. 16 | * django-tables2-reports sets Content-Type to application/vnd.ms-excel for xlsx files which causes warnings in Firefox. application/vnd.openxmlformats-officedocument.spreadsheetml.sheet is the correct Content-Type for xlsx 17 | * Support to Django 1.7 (I'm sorry to the delay) 18 | * Adding new feature: exclude_from_report 19 | * And a little details 20 | * Thanks to: 21 | * `Ramana Varanasi `_ 22 | * `Mihas `_ 23 | * `Paulgueltekin `_ 24 | * `David Ray `_ 25 | 26 | 0.0.9 (2013-11-30) 27 | ------------------ 28 | * Compatible with the future version of Django (>=1.7) 29 | * Update the tests 30 | * Refactor the code 31 | * Fix a bug when the title of the sheet is longer than 31 32 | * Thanks to: 33 | * `Pavel Zaytsev `_ 34 | 35 | 36 | 0.0.8 (2013-11-14) 37 | ------------------ 38 | * `Refactor the csv_to_excel module `_. In the next release this package will be a pypi egg. 39 | * Support for `openpyxl `_ 40 | * Integration with travis and coveralls 41 | * Fix an error if you use the theme paleblue 42 | * Fix test with python 3 43 | * Fix some details 44 | * Test project 45 | * Thanks to: 46 | * `Michał Pasternak `_ 47 | * `Mark Jones `_ 48 | 49 | 0.0.7 (2013-08-29) 50 | ------------------ 51 | 52 | * Russian translations 53 | * Thanks to: 54 | * `Armicron `_ 55 | 56 | 57 | 0.0.6 (2013-08-22) 58 | ------------------- 59 | 60 | * Python3 support 61 | * Polish translation 62 | * Thanks to: 63 | * `Michał Pasternak `_ 64 | 65 | 0.0.5 (2013-07-03) 66 | ------------------- 67 | 68 | * Improvements in the README 69 | * Exportable to XLS with `xlwt `_ 70 | * Thanks to: 71 | * `Crashy23 `_ 72 | * `Gamesbook `_ 73 | * And spatially to `Austin Phillips `_ 74 | 75 | 76 | 0.0.4 (2013-05-17) 77 | ------------------- 78 | 79 | * Escape csv data correctly during output 80 | * The fields with commas now are not split into multiple columns 81 | * Thanks to: 82 | * `Austin Phillips `_ 83 | 84 | 0.0.3 (2012-07-19) 85 | ------------------- 86 | 87 | * Fix a little error, when a column has line breaks. Now these are changed to espaces 88 | * Details 89 | 90 | 0.0.2 (2012-07-18) 91 | ------------------- 92 | 93 | * Add a default view (https://docs.djangoproject.com/en/dev/topics/class-based-views/) 94 | * Exportable to XLS 95 | * Update the README 96 | 97 | 0.0.1 (2012-07-17) 98 | ------------------- 99 | 100 | * Initial release 101 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGES.rst 3 | include requirements.txt 4 | recursive-include django_tables2_reports *.py 5 | recursive-include django_tables2_reports/templates * 6 | recursive-include django_tables2_reports/static * 7 | recursive-include django_tables2_reports/locale * 8 | prune test_project/ 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs 2 | 3 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 4 | 5 | 6 | clean-build: ## remove build artifacts 7 | rm -fr build/ 8 | rm -fr dist/ 9 | rm -fr .eggs/ 10 | rm -rf amms_planop2xls/mainwindow_ui.py 11 | find . -name '*.egg-info' -exec rm -fr {} + 12 | find . -name '*.egg' -exec rm -f {} + 13 | 14 | clean-pyc: ## remove Python file artifacts 15 | find . -name '*.pyc' -exec rm -f {} + 16 | find . -name '*.pyo' -exec rm -f {} + 17 | find . -name '*~' -exec rm -f {} + 18 | find . -name '__pycache__' -exec rm -fr {} + 19 | 20 | clean-test: ## remove test and coverage artifacts 21 | rm -fr .tox/ 22 | rm -f .coverage 23 | rm -fr htmlcov/ 24 | rm -rf test-file*xls* 25 | 26 | release: clean ## package and upload a release 27 | python setup.py sdist upload 28 | python setup.py bdist_wheel upload 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. contents:: 2 | 3 | ============================= 4 | This project has been retired 5 | ============================= 6 | 7 | This project has been retired and we leave it here, as-is, for anybody still using it. 8 | 9 | The proper way to export data to various formats now is `django-tables2` package itself. See for yourself: http://django-tables2.readthedocs.io/en/latest/pages/export.html 10 | 11 | Please do not use django-tables2-reports for new projects. 12 | 13 | ====================== 14 | django-tables2-reports 15 | ====================== 16 | 17 | .. image:: https://travis-ci.org/goinnn/django-tables2-reports.svg?branch=master 18 | :target: https://travis-ci.org/goinnn/django-tables2-reports 19 | 20 | .. image:: https://badge.fury.io/py/django-tables2-reports.svg 21 | :target: https://pypi.python.org/pypi/django-tables2-reports 22 | 23 | With django-tables2-reports you can get a report (CSV, XLS) of any `table `_ with **minimal changes** to your project 24 | 25 | Requirements 26 | ============ 27 | 28 | * `Python `_ (supports 2.7, 3.3, 3.4, 3.5, 3.6) 29 | * `Django `_ (supports 1.3, 1.4, 1.5, 1.6, 1.7, 1.8. 1.9, 1.10, 1.11) 30 | * `django-tables2 `_ 31 | * `xlwt `_, `openpyxl `_ or `pyExcelerator `_ (these are optionals, to export to xls; defaults to xlwt if available) 32 | 33 | 34 | Installation 35 | ============ 36 | 37 | * In your settings: 38 | 39 | :: 40 | 41 | INSTALLED_APPS = ( 42 | 43 | 'django_tables2_reports', 44 | ) 45 | 46 | 47 | TEMPLATE_CONTEXT_PROCESSORS = ( 48 | 49 | 'django.core.context_processors.static', 50 | 51 | ) 52 | 53 | 54 | # This is optional 55 | 56 | EXCEL_SUPPORT = 'xlwt' # or 'openpyxl' or 'pyexcelerator' 57 | 58 | Changes in your project 59 | ======================= 60 | 61 | 1.a Now your table should extend of 'TableReport' 62 | 63 | :: 64 | 65 | ############### Before ################### 66 | 67 | import django_tables2 as tables 68 | 69 | 70 | class MyTable(tables.Table): 71 | 72 | ... 73 | 74 | ############### Now ###################### 75 | 76 | from django_tables2_reports.tables import TableReport 77 | 78 | 79 | class MyTable(TableReport): 80 | 81 | ... 82 | 83 | 1.b If you want to exclude some columns from report (e.g. if it is a column of buttons), you should set 'exclude_from_report' - the names of columns (as well as property 'exclude' in `table `_) 84 | 85 | :: 86 | 87 | class MyTable(TableReport): 88 | 89 | class Meta: 90 | exclude_from_report = ('column1', ...) 91 | ... 92 | 93 | 2.a. If you use a traditional views, now you should use other RequestConfig and change a little your view: 94 | 95 | :: 96 | 97 | ############### Before ################### 98 | 99 | from django_tables2 import RequestConfig 100 | 101 | 102 | def my_view(request): 103 | objs = .... 104 | table = MyTable(objs) 105 | RequestConfig(request).configure(table) 106 | return render_to_response('app1/my_view.html', 107 | {'table': table}, 108 | context_instance=RequestContext(request)) 109 | 110 | ############### Now ###################### 111 | 112 | from django_tables2_reports.config import RequestConfigReport as RequestConfig 113 | from django_tables2_reports.utils import create_report_http_response 114 | 115 | def my_view(request): 116 | objs = .... 117 | table = MyTable(objs) 118 | table_to_report = RequestConfig(request).configure(table) 119 | if table_to_report: 120 | return create_report_http_response(table_to_report, request) 121 | return render_to_response('app1/my_view.html', 122 | {'table': table}, 123 | context_instance=RequestContext(request)) 124 | 125 | 126 | If you have a lot of tables in your project, you can activate the middleware, and you do not have to change your views, only the RequestConfig import 127 | 128 | :: 129 | 130 | # In your settings 131 | 132 | MIDDLEWARE_CLASSES = ( 133 | 134 | 'django_tables2_reports.middleware.TableReportMiddleware', 135 | ) 136 | 137 | ############### Now (with middleware) ###################### 138 | 139 | from django_tables2_reports.config import RequestConfigReport as RequestConfig 140 | 141 | def my_view(request): 142 | objs = .... 143 | table = MyTable(objs) 144 | RequestConfig(request).configure(table) 145 | return render_to_response('app1/my_view.html', 146 | {'table': table}, 147 | context_instance=RequestContext(request)) 148 | 149 | 150 | 2.b. If you use a `Class-based views `_: 151 | 152 | :: 153 | 154 | ############### Before ################### 155 | 156 | from django_tables2.views import SingleTableView 157 | 158 | 159 | class PhaseChangeView(SingleTableView): 160 | table_class = MyTable 161 | model = MyModel 162 | 163 | 164 | ############### Now ###################### 165 | 166 | from django_tables2_reports.views import ReportTableView 167 | 168 | 169 | class PhaseChangeView(ReportTableView): 170 | table_class = MyTable 171 | model = MyModel 172 | 173 | 174 | Usage 175 | ===== 176 | 177 | Under the table appear a CSV icon (and XLS icon if you have `xlwt `_, `openpyxl `_ or `pyExcelerator `_ in your python path), if you click in this icon, you get a CSV report (or xls report) with every item of the table (without pagination). The ordering works! 178 | 179 | 180 | Development 181 | =========== 182 | 183 | You can get the last bleeding edge version of django-tables2-reports by doing a clone 184 | of its git repository:: 185 | 186 | git clone https://github.com/goinnn/django-tables2-reports 187 | 188 | 189 | Test project 190 | ============ 191 | 192 | In the source tree, you will find a directory called 'test_project'. It contains 193 | a readily setup project that uses django-tables2-reports. You can run it as usual: 194 | 195 | :: 196 | 197 | cd test_project 198 | export PYTHONPATH=.. 199 | python manage.py syncdb --noinput 200 | python manage.py runserver 201 | -------------------------------------------------------------------------------- /django_tables2_reports/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 1, 3) 2 | 3 | def get_version(): 4 | return ".".join([str(x) for x in VERSION]) 5 | -------------------------------------------------------------------------------- /django_tables2_reports/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | from django_tables2.config import RequestConfig 18 | 19 | from django_tables2_reports.utils import REQUEST_VARIABLE 20 | 21 | 22 | class RequestConfigReport(RequestConfig): 23 | 24 | def __init__(self, request, paginate=True, paginate_report=False): 25 | self.request = request 26 | self.paginate = paginate 27 | self.paginate_report = paginate_report 28 | 29 | def configure(self, table, extra_context=None): 30 | table.is_configured = True 31 | param_report = table.param_report 32 | is_report = self.request.GET.get(param_report) 33 | table_to_report = None 34 | if is_report: 35 | self.paginate = self.paginate_report 36 | table_to_report = table 37 | setattr(self.request, REQUEST_VARIABLE, table_to_report) 38 | self.request.extra_context = extra_context or {} 39 | super(RequestConfigReport, self).configure(table) 40 | return table_to_report 41 | -------------------------------------------------------------------------------- /django_tables2_reports/csv_to_xls/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | #http://stackoverflow.com/questions/3681868/is-there-a-limit-on-an-excel-worksheets-name-length 18 | MAX_LENGTH_TITLE_SHEET = 31 19 | 20 | 21 | def convert(response, excel_support=None, encoding='utf-8', 22 | title_sheet='Sheet 1', content_attr='content', csv_kwargs=None): 23 | if len(title_sheet) > MAX_LENGTH_TITLE_SHEET: 24 | raise ValueError("The maximum length of a title of a sheet is %s" % MAX_LENGTH_TITLE_SHEET) 25 | excel_support = excel_support or get_xls_support() 26 | if excel_support == 'xlwt': 27 | from .xlwt_converter import convert 28 | elif excel_support == 'openpyxl': 29 | from .openpyxl_converter import convert 30 | elif excel_support == 'pyexcelerator': 31 | from .pyexcelerator_converter import convert 32 | else: 33 | raise RuntimeError("No support for xls generation available") 34 | 35 | convert(response, 36 | encoding=encoding, 37 | title_sheet=title_sheet, 38 | content_attr=content_attr, 39 | csv_kwargs=csv_kwargs) 40 | 41 | 42 | def get_xls_support(): 43 | try: 44 | import xlwt 45 | return 'xlwt' 46 | except ImportError: 47 | pass 48 | try: 49 | import openpyxl 50 | return 'openpyxl' 51 | except ImportError: 52 | pass 53 | try: 54 | import pyExcelerator 55 | return 'pyexcelerator' 56 | except ImportError: 57 | pass 58 | return None 59 | -------------------------------------------------------------------------------- /django_tables2_reports/csv_to_xls/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import sys 18 | 19 | PY3 = sys.version_info[0] == 3 20 | 21 | if PY3: 22 | from io import StringIO 23 | else: 24 | try: 25 | from cStringIO import StringIO 26 | except ImportError: 27 | from StringIO import StringIO 28 | 29 | 30 | def get_content(file_or_response, encoding='utf-8', content_attr='content'): 31 | content = getattr(file_or_response, content_attr) 32 | if PY3 and isinstance(content, bytes): 33 | content = content.decode(encoding).replace('\x00', '') 34 | return StringIO(content) 35 | -------------------------------------------------------------------------------- /django_tables2_reports/csv_to_xls/openpyxl_converter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import csv 18 | import collections 19 | import sys 20 | 21 | import openpyxl 22 | 23 | from openpyxl import Workbook 24 | try: 25 | from openpyxl.cell import get_column_letter 26 | except ImportError: 27 | from openpyxl.utils import get_column_letter 28 | 29 | from .base import get_content 30 | 31 | PY3 = sys.version_info[0] == 3 32 | 33 | OPENPYXL_VERSION = tuple([int(x) for x in openpyxl.__version__.split(".")]) # (openpyxl.__major__, openpyxl.__minor__, openpyxl.__release__) 34 | INITIAL = 0 35 | 36 | if OPENPYXL_VERSION >= (2,0,0): 37 | INITIAL = 1 38 | 39 | def convert(response, encoding='utf-8', title_sheet='Sheet 1', content_attr='content', csv_kwargs=None): 40 | csv_kwargs = csv_kwargs or {} 41 | try: 42 | wb = Workbook(encoding=encoding) 43 | except TypeError: 44 | wb = Workbook() 45 | ws = wb.get_active_sheet() 46 | ws.title = title_sheet 47 | cell_widths = collections.defaultdict(lambda: 0) 48 | content = get_content(response, encoding=encoding, content_attr=content_attr) 49 | reader = csv.reader(content, **csv_kwargs) 50 | 51 | for lno, line in enumerate(reader, INITIAL): 52 | write_row(ws, lno, line, cell_widths, encoding=encoding) 53 | 54 | # Roughly autosize output column widths based on maximum column size 55 | # and add bold style for the header 56 | for i, cell_width in cell_widths.items(): 57 | cell = ws.cell(column=i, row=INITIAL) 58 | if OPENPYXL_VERSION>=(2,0,0): 59 | bold = cell.font.copy(bold=True) 60 | cell.font = bold 61 | else: 62 | cell.style.font.bold = True 63 | ws.column_dimensions[get_column_letter(i + 1)].width = cell_width 64 | 65 | setattr(response, content_attr, '') 66 | wb.save(response) 67 | 68 | 69 | def write_row(ws, lno, cell_text, cell_widths, encoding='utf-8'): 70 | for cno, cell_text in enumerate(cell_text, INITIAL): 71 | if not PY3: 72 | cell_text = cell_text.decode(encoding) 73 | ws.cell(column=cno, row=lno).value = cell_text 74 | cell_widths[cno] = max( 75 | cell_widths[cno], 76 | len(cell_text)) 77 | -------------------------------------------------------------------------------- /django_tables2_reports/csv_to_xls/pyexcelerator_converter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | # This only works with python 2.x 18 | 19 | import csv 20 | import pyExcelerator 21 | 22 | 23 | from .base import get_content 24 | 25 | 26 | def convert(response, encoding='utf-8', title_sheet='Sheet 1', content_attr='content', csv_kwargs=None): 27 | csv_kwargs = csv_kwargs or {} 28 | workbook = pyExcelerator.Workbook() 29 | worksheet = workbook.add_sheet(title_sheet) 30 | lno = 0 31 | content = get_content(response) 32 | reader = csv.reader(content, **csv_kwargs) 33 | for line in reader: 34 | write_row(worksheet, lno, line, encoding=encoding) 35 | lno = lno + 1 36 | setattr(response, 'content_attr', workbook.get_biff_data()) 37 | 38 | 39 | def write_row(worksheet, lno, columns, encoding='utf-8'): 40 | """ Write a non-header row into the worksheet """ 41 | cno = 0 42 | for column in columns: 43 | worksheet.write(lno, cno, column.decode(encoding)) 44 | cno = cno + 1 45 | -------------------------------------------------------------------------------- /django_tables2_reports/csv_to_xls/xlwt_converter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import csv 18 | import collections 19 | import sys 20 | import xlwt 21 | 22 | from .base import get_content 23 | 24 | PY3 = sys.version_info[0] == 3 25 | 26 | 27 | def convert(response, encoding='utf-8', title_sheet='Sheet 1', content_attr='content', csv_kwargs=None): 28 | """Replace HttpResponse csv content with excel formatted data using xlwt 29 | library. 30 | """ 31 | csv_kwargs = csv_kwargs or {} 32 | # Styles used in the spreadsheet. Headings are bold. 33 | header_font = xlwt.Font() 34 | header_font.bold = True 35 | 36 | header_style = xlwt.XFStyle() 37 | header_style.font = header_font 38 | 39 | wb = xlwt.Workbook(encoding=encoding) 40 | ws = wb.add_sheet(title_sheet) 41 | 42 | # Cell width information kept for every column, indexed by column number. 43 | cell_widths = collections.defaultdict(lambda: 0) 44 | content = get_content(response) 45 | reader = csv.reader(content, **csv_kwargs) 46 | for lno, line in enumerate(reader): 47 | if lno == 0: 48 | style = header_style 49 | else: 50 | style = None 51 | write_row(ws, lno, line, cell_widths, style=style, encoding=encoding) 52 | # Roughly autosize output column widths based on maximum column size. 53 | for col, width in cell_widths.items(): 54 | ws.col(col).width = width 55 | setattr(response, content_attr, '') 56 | wb.save(response) 57 | 58 | 59 | def write_row(ws, lno, cell_text, cell_widths, style=None, encoding='utf-8'): 60 | """Write row of utf-8 encoded data to worksheet, keeping track of maximum 61 | column width for each cell. 62 | """ 63 | import xlwt 64 | if style is None: 65 | style = xlwt.Style.default_style 66 | for cno, utf8_text in enumerate(cell_text): 67 | cell_text = utf8_text 68 | if not PY3: 69 | cell_text = cell_text.decode(encoding) 70 | ws.write(lno, cno, cell_text, style) 71 | cell_widths[cno] = max(cell_widths[cno], 72 | get_xls_col_width(cell_text, style)) 73 | 74 | 75 | # A reasonable approximation for column width is based off zero character in 76 | # default font. Without knowing exact font details it's impossible to 77 | # determine exact auto width. 78 | # http://stackoverflow.com/questions/3154270/python-xlwt-adjusting-column-widths?lq=1 79 | def get_xls_col_width(text, style): 80 | return int((1 + len(text)) * 256) 81 | -------------------------------------------------------------------------------- /django_tables2_reports/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_tables2_reports/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2013-08-22 06:49-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: tables.py:84 21 | msgid "CSV Report" 22 | msgstr "" 23 | 24 | #: tables.py:86 25 | msgid "XLS Report" 26 | msgstr "" 27 | -------------------------------------------------------------------------------- /django_tables2_reports/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_tables2_reports/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2013-08-22 06:49-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: Pablo Martín \n" 14 | "Language-Team: ES \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 20 | 21 | #: tables.py:84 22 | msgid "CSV Report" 23 | msgstr "Informe CSV" 24 | 25 | #: tables.py:86 26 | msgid "XLS Report" 27 | msgstr "Informe XLS" 28 | -------------------------------------------------------------------------------- /django_tables2_reports/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_tables2_reports/locale/pl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: 0.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-08-22 06:49-0500\n" 11 | "PO-Revision-Date: 2013-08-22 10:00+0100\n" 12 | "Last-Translator: Michał Pasternak \n" 13 | "Language-Team: PL \n" 14 | "Language: polish\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " 19 | "|| n%100>=20) ? 1 : 2);\n" 20 | "X-Generator: Poedit 1.5.5\n" 21 | 22 | #: tables.py:84 23 | msgid "CSV Report" 24 | msgstr "Raport CSV" 25 | 26 | #: tables.py:86 27 | msgid "XLS Report" 28 | msgstr "Raport XLS" 29 | -------------------------------------------------------------------------------- /django_tables2_reports/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_tables2_reports/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django_tables2_report package. 2 | # Sergey Vishnikin , 2013. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: \n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2013-08-29 00:12+0400\n" 8 | "PO-Revision-Date: 2013-08-29 00:20+0400\n" 9 | "Last-Translator: Sergey Vishnikin \n" 10 | "Language-Team: \n" 11 | "Language: ru_RU\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " 16 | "|| n%100>=20) ? 1 : 2);\n" 17 | 18 | #: tables.py:84 19 | msgid "CSV Report" 20 | msgstr "Отчёт CSV" 21 | 22 | #: tables.py:86 23 | msgid "XLS Report" 24 | msgstr "Отчёт XLS" 25 | -------------------------------------------------------------------------------- /django_tables2_reports/middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | from django_tables2_reports.utils import create_report_http_response, REQUEST_VARIABLE, REPORT_CONTENT_TYPES 18 | 19 | 20 | class TableReportMiddleware(object): 21 | 22 | def process_response(self, request, response): 23 | table_to_report = getattr(request, REQUEST_VARIABLE, None) 24 | current_content_type = response.get('Content-Type', None) 25 | if table_to_report and current_content_type not in REPORT_CONTENT_TYPES: 26 | return create_report_http_response(table_to_report, request) 27 | return response 28 | -------------------------------------------------------------------------------- /django_tables2_reports/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/models.py -------------------------------------------------------------------------------- /django_tables2_reports/static/img/csv_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/static/img/csv_icon.png -------------------------------------------------------------------------------- /django_tables2_reports/static/img/xls_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/django_tables2_reports/static/img/xls_icon.png -------------------------------------------------------------------------------- /django_tables2_reports/tables.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import csv 18 | import codecs 19 | import sys 20 | from functools import wraps 21 | 22 | PY3 = sys.version_info[0] == 3 23 | 24 | if PY3: 25 | string = str 26 | unicode = str 27 | else: 28 | string = basestring 29 | 30 | try: 31 | from cStringIO import StringIO 32 | except ImportError: 33 | from io import StringIO 34 | 35 | import django_tables2 as tables 36 | 37 | from django.conf import settings 38 | from django.utils.translation import ugettext as _ 39 | from django.http import HttpResponse, Http404 40 | from django.utils.html import strip_tags 41 | 42 | from django_tables2_reports import csv_to_xls 43 | from django_tables2_reports.utils import (DEFAULT_PARAM_PREFIX, 44 | get_excel_support, 45 | generate_prefixto_report) 46 | 47 | 48 | # Unicode CSV writer, copied direct from Python docs: 49 | # http://docs.python.org/2/library/csv.html 50 | 51 | class UnicodeWriter: 52 | """ 53 | A CSV writer which will write rows to CSV file "f", 54 | which is encoded in the given encoding. 55 | """ 56 | 57 | def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): 58 | # Redirect output to a queue 59 | self.queue = StringIO() 60 | self.writer = csv.writer(self.queue, dialect=dialect, **kwds) 61 | self.stream = f 62 | self.encoder = codecs.getincrementalencoder(encoding)() 63 | self.encoding = encoding 64 | 65 | def writerow(self, row): 66 | if PY3: 67 | self.writer.writerow([s for s in row]) 68 | # Fetch UTF-8 output from the queue ... 69 | data = self.queue.getvalue() 70 | else: 71 | self.writer.writerow([s.encode(self.encoding) for s in row]) 72 | # Fetch UTF-8 output from the queue ... 73 | data = self.queue.getvalue() 74 | data = data.decode(self.encoding) 75 | # ... and reencode it into the target encoding 76 | data = self.encoder.encode(data) 77 | # write to the target stream 78 | self.stream.write(data) 79 | # empty queue 80 | self.queue.truncate(0) 81 | 82 | 83 | class TableReport(tables.Table): 84 | 85 | exclude_from_report = () # the names of columns that should be excluded from report 86 | 87 | def __init__(self, *args, **kwargs): 88 | if not 'template' in kwargs: 89 | kwargs['template'] = 'django_tables2_reports/table.html' 90 | prefix_param_report = kwargs.pop('prefix_param_report', DEFAULT_PARAM_PREFIX) 91 | super(TableReport, self).__init__(*args, **kwargs) 92 | self.param_report = generate_prefixto_report(self, prefix_param_report) 93 | self.formats = [(_('CSV Report'), 'csv')] 94 | if get_excel_support(): 95 | self.formats.append((_('XLS Report'), 'xls')) 96 | if hasattr(self, 'Meta'): 97 | self.exclude_from_report = getattr(self.Meta, 'exclude_from_report', ()) 98 | 99 | def _with_exclude_from_report(method): 100 | """ Put to 'exclude' columns from 'exclude_from_report', and revert this after method's call """ 101 | @wraps(method) 102 | def with_exclude(self, *args, **kwargs): 103 | origin_exclude = self.exclude 104 | self.exclude = self.exclude_from_report 105 | try: 106 | return method(self, *args, **kwargs) 107 | finally: 108 | self.exclude = origin_exclude 109 | return with_exclude 110 | 111 | def as_report(self, request, report_format='csv'): 112 | if report_format == 'csv': 113 | return self.as_csv(request) 114 | elif report_format == 'xls': 115 | return self.as_xls(request) 116 | else: 117 | raise Http404("This format %s is not accepted" % report_format) 118 | 119 | @_with_exclude_from_report 120 | def as_csv(self, request): 121 | response = HttpResponse() 122 | csv_writer = UnicodeWriter(response, encoding=settings.DEFAULT_CHARSET) 123 | csv_header = [column.header for column in self.columns] 124 | csv_writer.writerow(csv_header) 125 | for row in self.rows: 126 | csv_row = [] 127 | for column, cell in row.items(): 128 | if isinstance(cell, string): 129 | # if cell is not a string strip_tags(cell) get an 130 | # error in django 1.6 131 | cell = strip_tags(cell) 132 | else: 133 | cell = unicode(cell) 134 | csv_row.append(cell) 135 | csv_writer.writerow(csv_row) 136 | return response 137 | 138 | @_with_exclude_from_report 139 | def as_xls(self, request): 140 | return self.as_csv(request) 141 | 142 | def treatement_to_response(self, response, report_format='csv'): 143 | if report_format == 'xls': 144 | csv_to_xls.convert(response, get_excel_support(), 145 | encoding=settings.DEFAULT_CHARSET, 146 | title_sheet=self.param_report[:csv_to_xls.MAX_LENGTH_TITLE_SHEET]) 147 | return response 148 | -------------------------------------------------------------------------------- /django_tables2_reports/templates/django_tables2_reports/table.html: -------------------------------------------------------------------------------- 1 | {% extends "django_tables2/table.html" %} 2 | 3 | {% load i18n django_tables2 %} 4 | 5 | {% block pagination %} 6 | {{ block.super }} 7 | {% block table.report %} 8 | {% if table.is_configured %} 9 | {% for label, format in table.formats %} 10 | 11 | 12 | 13 | {% endfor %} 14 | {% endif %} 15 | {% endblock table.report %} 16 | {% endblock %} -------------------------------------------------------------------------------- /django_tables2_reports/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import sys 18 | 19 | from django.conf import settings 20 | from django.http import HttpRequest 21 | from django.test import TestCase 22 | try: 23 | from django.utils.unittest import skipIf 24 | except ImportError: 25 | from django.test.utils import skipIf 26 | import django_tables2 27 | import django_tables2_reports.tables 28 | import django_tables2_reports.views 29 | 30 | 31 | PY3 = sys.version_info[0] == 3 32 | if PY3: 33 | unichr = chr 34 | 35 | 36 | class TableReportForTesting(django_tables2_reports.tables.TableReport): 37 | name = django_tables2.Column() 38 | item_num = django_tables2.Column() 39 | 40 | 41 | class ReportTableViewForTesting(django_tables2_reports.views.ReportTableView): 42 | table_class = TableReportForTesting 43 | 44 | 45 | class TestCsvGeneration(TestCase): 46 | """Test csv generation on sample table data.""" 47 | 48 | def test_csv_simple_input(self): 49 | """Test ability to generate csv with simple input data.""" 50 | 51 | # Mix of integer and string data. Ensure that commas and 52 | # quotes are escaped properly. 53 | data = [ 54 | { 55 | 'name': 'Normal string', 56 | 'item_num': 1, 57 | }, 58 | { 59 | 'name': 'String, with, commas', 60 | 'item_num': 2, 61 | }, 62 | { 63 | 'name': 'String with " quote', 64 | 'item_num': 3, 65 | }, 66 | ] 67 | 68 | table = TableReportForTesting(data) 69 | response = table.as_csv(HttpRequest()) 70 | self.assertEqual(response.status_code, 200) 71 | # Expect cells containing commas to be escaped with quotes. 72 | content = response.content 73 | if PY3: 74 | content = content.decode(settings.DEFAULT_CHARSET).replace('\x00', '') 75 | self.assertEqual( 76 | content, 77 | 'Name,Item Num\r\n' 78 | 'Normal string,1\r\n' 79 | '"String, with, commas",2\r\n' 80 | '"String with "" quote",3\r\n') 81 | 82 | def test_csv_with_unicode(self): 83 | """Test that unicode cell values are converted correctly to csv.""" 84 | 85 | data = [ 86 | { 87 | 'name': 'Normal string', 88 | 'item_num': 1, 89 | }, 90 | { 91 | 'name': u'String with ' + unichr(0x16c) + ' char', 92 | 'item_num': 2, 93 | }, 94 | ] 95 | 96 | table = TableReportForTesting(data) 97 | response = table.as_csv(HttpRequest()) 98 | self.assertEqual(response.status_code, 200) 99 | # Expect csv content to be utf-8 encoded. 100 | content = response.content 101 | result = ('Name,Item Num\r\n' 102 | 'Normal string,1\r\n' 103 | 'String with ' + unichr(0x16c) + ' char,2\r\n') 104 | if PY3: 105 | content = content.decode(settings.DEFAULT_CHARSET).replace('\x00', '') 106 | else: 107 | result = result.encode(settings.DEFAULT_CHARSET) 108 | self.assertEqual(content, result) 109 | 110 | def test_csv_no_pagination(self): 111 | """Ensure that table pagination doesn't affect output.""" 112 | 113 | data = [ 114 | { 115 | 'name': 'page 1', 116 | 'item_num': 1, 117 | }, 118 | { 119 | 'name': 'page 2', 120 | 'item_num': 2, 121 | }, 122 | ] 123 | 124 | table = TableReportForTesting(data) 125 | table.paginate(per_page=1) 126 | 127 | response = table.as_csv(HttpRequest()) 128 | self.assertEqual(response.status_code, 200) 129 | # Ensure that even if table paginated, output is all row 130 | # data. 131 | content = response.content 132 | if PY3: 133 | content = content.decode(settings.DEFAULT_CHARSET).replace('\x00', '') 134 | self.assertEqual( 135 | content, 136 | ('Name,Item Num\r\n' 137 | 'page 1,1\r\n' 138 | 'page 2,2\r\n') 139 | ) 140 | 141 | def test_exclude_from_report(self): 142 | """Ensure that exclude-some-columns-from-report works.""" 143 | data = [ 144 | { 145 | 'name': 'page 1', 146 | 'item_num': 1, 147 | }, 148 | { 149 | 'name': 'page 2', 150 | 'item_num': 2, 151 | }, 152 | ] 153 | 154 | class TableWithExclude(TableReportForTesting): 155 | class Meta: 156 | exclude_from_report = ('item_num',) 157 | 158 | table = TableWithExclude(data) 159 | table.exclude = ('name', ) 160 | self.assertEqual(table.exclude_from_report, ('item_num',)) 161 | 162 | response = table.as_csv(HttpRequest()) 163 | self.assertEqual(response.status_code, 200) 164 | content = response.content 165 | if PY3: 166 | content = content.decode(settings.DEFAULT_CHARSET).replace('\x00', '') 167 | 168 | self.assertEqual(table.exclude, ('name',)) # Attribute 'exclude_from_report' shouldn't overwrite 'exclude' 169 | self.assertEqual( 170 | content, 171 | ('Name\r\n' 172 | 'page 1\r\n' 173 | 'page 2\r\n') 174 | ) 175 | 176 | 177 | @skipIf( 178 | not django_tables2_reports.utils.get_excel_support(), 179 | "No Excel support, please install xlwt, pyExcelerator or openpyxl") 180 | class TestExcelGeneration(TestCase): 181 | def setUp(self): 182 | # Mix of integer and string data. Ensure that commas and 183 | # quotes are escaped properly. 184 | self.data = [ 185 | { 186 | 'name': 'Normal string', 187 | 'item_num': 1, 188 | }, 189 | { 190 | 'name': 'String, with, commas', 191 | 'item_num': 2, 192 | }, 193 | { 194 | 'name': 'String with " quote', 195 | 'item_num': 3, 196 | }, 197 | { 198 | 'name': u'String with ' + unichr(0x16c) + ' char', 199 | 'item_num': 4, 200 | } 201 | ] 202 | self.table = TableReportForTesting(self.data) 203 | 204 | def test_excel_simple_input(self, extension='xls'): 205 | """Test ability to generate excel output with simple input data.""" 206 | excel_support = getattr(settings, 'EXCEL_SUPPORT', django_tables2_reports.utils.get_excel_support()) 207 | response = self.table.treatement_to_response( 208 | self.table.as_csv(HttpRequest()), 209 | report_format='xls') 210 | self.assertEqual(response.status_code, 200) 211 | open('test-file-%s.%s' % (excel_support, extension), 212 | 'wb').write(response.content) 213 | 214 | def test_pyexcelerator(self): 215 | if PY3: 216 | return 217 | settings.EXCEL_SUPPORT = "pyexcelerator" 218 | self.test_excel_simple_input() 219 | 220 | def test_xlwt(self): 221 | settings.EXCEL_SUPPORT = "xlwt" 222 | self.test_excel_simple_input() 223 | 224 | def test_openpyxls(self): 225 | settings.EXCEL_SUPPORT = "openpyxl" 226 | self.test_excel_simple_input(extension='xlsx') 227 | -------------------------------------------------------------------------------- /django_tables2_reports/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | from django.http import HttpResponse 18 | from django_tables2_reports import csv_to_xls 19 | 20 | DEFAULT_PARAM_PREFIX = 'report' 21 | REQUEST_VARIABLE = 'table_to_report' 22 | REPORT_CONTENT_TYPES = { 23 | 'csv': 'text/csv', 24 | 'xls': 'application/vnd.ms-excel', 25 | 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 26 | } 27 | REPORT_MYMETYPE = REPORT_CONTENT_TYPES['xls'] # backwards compatible 28 | 29 | 30 | def create_report_http_response(table, request): 31 | report_format = request.GET.get(table.param_report) 32 | report = table.as_report(request, report_format=report_format) 33 | extension = get_extension_report(report_format) 34 | filename = '%s.%s' % (table.param_report, extension) 35 | response = HttpResponse(report, content_type=REPORT_CONTENT_TYPES[extension]) 36 | response['Content-Disposition'] = 'attachment; filename=%s' % filename 37 | response = table.treatement_to_response(response, report_format=report_format) 38 | return response 39 | 40 | 41 | def get_excel_support(): 42 | # If you don't specify a xls library, this function will autodetect the library to use for xls writing. Default to xlwt. 43 | from django.conf import settings 44 | return getattr(settings, "EXCEL_SUPPORT", None) or csv_to_xls.get_xls_support() 45 | 46 | 47 | def get_extension_report(report_format): 48 | if report_format == 'xls' and get_excel_support() == "openpyxl": 49 | return 'xlsx' 50 | return report_format 51 | 52 | 53 | def generate_prefixto_report(table, prefix_param_report=None): 54 | param_report = prefix_param_report or DEFAULT_PARAM_PREFIX 55 | table_class = table.__class__ 56 | prefix = table.prefix 57 | param_report = "%s-%s" % (param_report, table_class.__name__.lower()) 58 | if prefix: 59 | param_report = "%s-%s" % (prefix, param_report) 60 | return param_report 61 | -------------------------------------------------------------------------------- /django_tables2_reports/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import django 18 | 19 | from django_tables2.views import SingleTableView 20 | 21 | from django_tables2_reports.config import RequestConfigReport 22 | from django_tables2_reports.utils import create_report_http_response 23 | 24 | 25 | class ReportTableView(SingleTableView): 26 | 27 | def get_table(self, **kwargs): 28 | """ 29 | Return a table object to use. The table has automatic support for 30 | sorting and pagination. 31 | """ 32 | options = {} 33 | table_class = self.get_table_class() 34 | table = table_class(self.get_table_data(), **kwargs) 35 | args = () 36 | if django.VERSION >= (1,8,0): 37 | args = (table, ) 38 | paginate = self.get_table_pagination(*args) 39 | if paginate is not None: 40 | options['paginate'] = paginate 41 | self.table_to_report = RequestConfigReport(self.request, **options).configure(table) 42 | return table 43 | 44 | def render_to_response(self, context, **response_kwargs): 45 | if self.table_to_report: 46 | return create_report_http_response(self.table_to_report, self.request) 47 | return super(ReportTableView, self).render_to_response(context, **response_kwargs) 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | django-tables2 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion 2 | Pillow 3 | pyExcelerator 4 | xlwt 5 | openpyxl 6 | coveralls 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.3 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:django_tables2_reports/__init__.py] 11 | parse = \((?P\d+), (?P\d+), (?P\d+)\) 12 | serialize = ({major}, {minor}, {patch}) 13 | 14 | [wheel] 15 | universal = 1 16 | 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import os 18 | from setuptools import setup, find_packages 19 | 20 | 21 | def read(*rnames): 22 | return open(os.path.join(os.path.dirname(__file__), *rnames)).read() 23 | 24 | setup( 25 | name="django-tables2-reports", 26 | version='0.1.3', 27 | author="Pablo Martin", 28 | author_email="goinnn@gmail.com", 29 | description="With django-tables2-reports you can get a report (CSV, XLS) of any django-tables2 with minimal changes to your project", 30 | long_description=(read('README.rst') + '\n\n' + read('CHANGES.rst')), 31 | classifiers=[ 32 | 'Development Status :: 4 - Beta', 33 | 'Framework :: Django', 34 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 35 | 'Programming Language :: Python :: 2', 36 | 'Programming Language :: Python :: 3', 37 | ], 38 | license="LGPL 3", 39 | keywords="django,tables,django-tables2,reports,CSV,XLS", 40 | url='https://github.com/goinnn/django-tables2-reports', 41 | packages=find_packages(), 42 | install_requires=[x.strip() for x in open("requirements.txt").readlines()], 43 | include_package_data=True, 44 | zip_safe=False, 45 | ) 46 | -------------------------------------------------------------------------------- /test_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | from django.conf import ENVIRONMENT_VARIABLE 6 | 7 | if __name__ == "__main__": 8 | os.environ.setdefault(ENVIRONMENT_VARIABLE, "test_project.settings") 9 | from django.core.management import execute_from_command_line 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /test_project/run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2013 by Pablo Martín 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with this programe. If not, see . 18 | 19 | import os 20 | import sys 21 | 22 | import django 23 | 24 | from django.conf import ENVIRONMENT_VARIABLE 25 | from django.core import management 26 | 27 | 28 | if len(sys.argv) == 1: 29 | os.environ[ENVIRONMENT_VARIABLE] = 'test_project.settings' 30 | else: 31 | os.environ[ENVIRONMENT_VARIABLE] = sys.argv[1] 32 | 33 | if django.VERSION[0] == 1 and django.VERSION[1] >= 7: 34 | from django.core.wsgi import get_wsgi_application 35 | application = get_wsgi_application() 36 | 37 | management.call_command('test', 'django_tables2_reports', 'test_app') 38 | -------------------------------------------------------------------------------- /test_project/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/test_project/test_app/__init__.py -------------------------------------------------------------------------------- /test_project/test_app/admin.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from django.contrib import admin 3 | from test_app.models import Person 4 | 5 | admin.site.register(Person) -------------------------------------------------------------------------------- /test_project/test_app/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "test_app.person", 5 | "fields": { 6 | "last": "Kovalsky", 7 | "first": "John", 8 | "dob": "2013-10-18", 9 | "active": true 10 | 11 | } 12 | }, 13 | { 14 | "pk": 2, 15 | "model": "test_app.person", 16 | "fields": { 17 | "last": "Thomson", 18 | "first": "John", 19 | "dob": "1982-11-14", 20 | "active": false 21 | 22 | } 23 | }, 24 | { 25 | "pk": 3, 26 | "model": "test_app.person", 27 | "fields": { 28 | "last": "Johnson", 29 | "first": "Tom", 30 | "dob": "1985-07-01", 31 | "active": false 32 | 33 | } 34 | }, 35 | { 36 | "pk": 4, 37 | "model": "test_app.person", 38 | "fields": { 39 | "last": "Garcia", 40 | "first": "Manolo", 41 | "dob": "2013-10-18", 42 | "active": true 43 | 44 | } 45 | } 46 | ] -------------------------------------------------------------------------------- /test_project/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Person(models.Model): 5 | first = models.CharField(max_length=200) 6 | last = models.CharField(max_length=200) 7 | dob = models.DateField() 8 | active = models.BooleanField(default=True) 9 | -------------------------------------------------------------------------------- /test_project/test_app/templates/test_app/person_list.html: -------------------------------------------------------------------------------- 1 | {% load render_table from django_tables2 %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Person table

10 | {% render_table table %} 11 | 12 | -------------------------------------------------------------------------------- /test_project/test_app/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2012-2013 by Pablo Martín 3 | # 4 | # This software is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This software is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this software. If not, see . 16 | 17 | import sys 18 | 19 | PY3 = sys.version_info[0] == 3 20 | 21 | from django.conf import settings 22 | from django.core.urlresolvers import reverse 23 | from django.test import TestCase 24 | 25 | from test_app.models import Person 26 | 27 | 28 | class TestRenderDT2R(TestCase): 29 | 30 | def _test_check_render(self, url): 31 | response = self.client.get(url) 32 | self.assertEqual(response.status_code, 200) 33 | return response 34 | 35 | def test_check_render_class_view(self): 36 | url = reverse('index') 37 | response = self._test_check_render(url) 38 | return response 39 | 40 | def test_check_render_function_view(self): 41 | url = reverse('index_function_view') 42 | response = self._test_check_render(url) 43 | return response 44 | 45 | def test_equal_render_class_view_and_function_view(self): 46 | response_clv = self.test_check_render_class_view() 47 | response_fv = self.test_check_render_function_view() 48 | self.assertEqual(response_clv.status_code, response_fv.status_code) 49 | self.assertEqual(response_clv.content, response_fv.content) 50 | 51 | def _test_check_report_csv(self, url, report_format='csv', report_param='report-testtable'): 52 | url = url + '?%s=%s' % (report_param, report_format) 53 | response = self.client.get(url) 54 | self.assertEqual(response.status_code, 200) 55 | if report_format == 'csv': 56 | content = response.content 57 | if PY3: 58 | content = content.decode(settings.DEFAULT_CHARSET) 59 | num_lines = len([line for line in content.split('\n') if line]) 60 | self.assertEqual(num_lines - 1, Person.objects.all().count()) 61 | return response 62 | 63 | def test_check_report_csv_class_view(self, report_format='csv'): 64 | url = reverse('index') 65 | response = self._test_check_report_csv(url, report_format=report_format) 66 | return response 67 | 68 | def test_check_report_csv_function_view(self, report_format='csv'): 69 | url = reverse('index_function_view') 70 | response = self._test_check_report_csv(url, 71 | report_format=report_format) 72 | return response 73 | 74 | def test_equal_report_class_view_and_function_view(self, report_format='csv'): 75 | response_clv = self.test_check_report_csv_class_view(report_format=report_format) 76 | response_fv = self.test_check_report_csv_function_view(report_format=report_format) 77 | self.assertEqual(response_clv.status_code, response_fv.status_code) 78 | self.assertEqual(response_clv.content, response_fv.content) 79 | 80 | def test_check_report_xls_class_view(self): 81 | return self.test_check_report_csv_class_view('xls') 82 | 83 | def test_check_report_xls_function_view(self): 84 | return self.test_check_report_csv_function_view('xls') 85 | 86 | def test_equal_report_class_view_and_function_view_xls(self): 87 | response_clv = self.test_check_report_xls_class_view() 88 | response_fv = self.test_check_report_xls_function_view() 89 | self.assertEqual(response_clv.status_code, response_fv.status_code) 90 | self.assertEqual(response_clv.content, response_fv.content) 91 | 92 | def test_check_report_csv_function_view_middleware(self): 93 | report_format = 'csv' 94 | response = self.test_check_report_csv_function_view(report_format=report_format) 95 | url = reverse('index_function_view_middleware') 96 | try: 97 | self._test_check_report_csv(url, report_format=report_format, report_param='my-prefix-report-testtable') 98 | raise AssertionError("The call to _test_check_report_csv method should get an AssertionError exception") 99 | except AssertionError: 100 | pass 101 | original_middlewares = settings.MIDDLEWARE_CLASSES 102 | settings.MIDDLEWARE_CLASSES += ('django_tables2_reports.middleware.TableReportMiddleware',) 103 | self.client.handler.load_middleware() 104 | self._test_check_render(url) 105 | response_with_middleware = self._test_check_report_csv(url, report_format=report_format, report_param='my-prefix-report-testtable') 106 | self.assertEqual(response.content, response_with_middleware.content) 107 | settings.MIDDLEWARE_CLASSES = original_middlewares 108 | self.assertEqual('django_tables2_reports.middleware.TableReportMiddleware' in settings.MIDDLEWARE_CLASSES, 109 | False) 110 | self.client.handler.load_middleware() 111 | -------------------------------------------------------------------------------- /test_project/test_app/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | import django 3 | 4 | from django.template import RequestContext 5 | 6 | from django_tables2_reports.config import RequestConfigReport as RequestConfig 7 | from django_tables2_reports.tables import TableReport 8 | from django_tables2_reports.utils import create_report_http_response 9 | from django_tables2_reports.views import ReportTableView 10 | 11 | from test_app.models import Person 12 | 13 | 14 | class TestTable(TableReport): 15 | 16 | class Meta: 17 | model = Person 18 | attrs = {"class": "paleblue"} 19 | 20 | 21 | class TestView(ReportTableView): 22 | table_class = TestTable 23 | model = Person 24 | 25 | def compat_render_to_response(template_name, context, context_instance, request): 26 | if django.VERSION < (1,10,0): 27 | from django.shortcuts import render_to_response 28 | return render_to_response(template_name, context, context_instance=context_instance) 29 | from django.shortcuts import render 30 | return render(request, template_name, context) 31 | 32 | def index_function_view(request): 33 | objs = Person.objects.all() 34 | table = TestTable(objs) 35 | table_to_report = RequestConfig(request).configure(table) 36 | if table_to_report: 37 | return create_report_http_response(table_to_report, request) 38 | return compat_render_to_response('test_app/person_list.html', 39 | {'table': table}, 40 | context_instance=RequestContext(request), 41 | request=request) 42 | 43 | 44 | def index_function_view_middleware(request): 45 | objs = Person.objects.all() 46 | table = TestTable(objs, prefix='my-prefix') 47 | RequestConfig(request).configure(table) 48 | return compat_render_to_response('test_app/person_list.html', 49 | {'table': table}, 50 | context_instance=RequestContext(request), 51 | request=request) 52 | -------------------------------------------------------------------------------- /test_project/test_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goinnn/django-tables2-reports/0fcdb4becd8e25559819e877e77078c0cf17b6cd/test_project/test_project/__init__.py -------------------------------------------------------------------------------- /test_project/test_project/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for test_project project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': 'db.sqlite', # Or path to database file if using sqlite3. 16 | # The following settings are not used with sqlite3: 17 | 'USER': '', 18 | 'PASSWORD': '', 19 | 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 20 | 'PORT': '', # Set to empty string for default. 21 | } 22 | } 23 | 24 | # Hosts/domain names that are valid for this site; required if DEBUG is False 25 | # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts 26 | ALLOWED_HOSTS = [] 27 | 28 | # Local time zone for this installation. Choices can be found here: 29 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 30 | # although not all choices may be available on all operating systems. 31 | # In a Windows environment this must be set to your system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale. 46 | USE_L10N = True 47 | 48 | # If you set this to False, Django will not use timezone-aware datetimes. 49 | USE_TZ = True 50 | 51 | # Absolute filesystem path to the directory that will hold user-uploaded files. 52 | # Example: "/var/www/example.com/media/" 53 | MEDIA_ROOT = '' 54 | 55 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 56 | # trailing slash. 57 | # Examples: "http://example.com/media/", "http://media.example.com/" 58 | MEDIA_URL = '' 59 | 60 | # Absolute path to the directory static files should be collected to. 61 | # Don't put anything in this directory yourself; store your static files 62 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 63 | # Example: "/var/www/example.com/static/" 64 | STATIC_ROOT = '' 65 | 66 | # URL prefix for static files. 67 | # Example: "http://example.com/static/", "http://static.example.com/" 68 | STATIC_URL = '/static/' 69 | 70 | # Additional locations of static files 71 | STATICFILES_DIRS = ( 72 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 73 | # Always use forward slashes, even on Windows. 74 | # Don't forget to use absolute paths, not relative paths. 75 | ) 76 | 77 | # List of finder classes that know how to find static files in 78 | # various locations. 79 | STATICFILES_FINDERS = ( 80 | 'django.contrib.staticfiles.finders.FileSystemFinder', 81 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 82 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 83 | ) 84 | 85 | # Make this unique, and don't share it with anybody. 86 | SECRET_KEY = 'lwfo1o9r^+x8xwec=6$a&m(dmg$1t%8)g6hr%&b4%)%_ualb8s' 87 | 88 | # List of callables that know how to import templates from various sources. 89 | TEMPLATE_LOADERS = ( 90 | 'django.template.loaders.filesystem.Loader', 91 | 'django.template.loaders.app_directories.Loader', 92 | # 'django.template.loaders.eggs.Loader', 93 | ) 94 | 95 | MIDDLEWARE_CLASSES = ( 96 | 'django.middleware.common.CommonMiddleware', 97 | 'django.contrib.sessions.middleware.SessionMiddleware', 98 | 'django.middleware.csrf.CsrfViewMiddleware', 99 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 100 | 'django.contrib.messages.middleware.MessageMiddleware', 101 | 102 | # django-tables2-reports: don't add this. Some tests rely upon 103 | # this not living here: 104 | # 'django_tables2_reports.middleware.TableReportMiddleware', 105 | 106 | # Uncomment the next line for simple clickjacking protection: 107 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 108 | ) 109 | 110 | ROOT_URLCONF = 'test_project.urls' 111 | 112 | # Python dotted path to the WSGI application used by Django's runserver. 113 | WSGI_APPLICATION = 'test_project.wsgi.application' 114 | 115 | TEMPLATE_DIRS = ( 116 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 117 | # Always use forward slashes, even on Windows. 118 | # Don't forget to use absolute paths, not relative paths. 119 | ) 120 | 121 | TEMPLATE_CONTEXT_PROCESSORS = ( 122 | 'django.contrib.auth.context_processors.auth', 123 | 'django.core.context_processors.request', 124 | 'django.core.context_processors.static' 125 | 126 | ) 127 | 128 | INSTALLED_APPS = ( 129 | 'django.contrib.auth', 130 | 'django.contrib.contenttypes', 131 | 'django.contrib.sessions', 132 | 'django.contrib.sites', 133 | 'django.contrib.messages', 134 | 'django.contrib.staticfiles', 135 | # Uncomment the next line to enable the admin: 136 | 'django.contrib.admin', 137 | # Uncomment the next line to enable admin documentation: 138 | # 'django.contrib.admindocs', 139 | 140 | 'django_tables2', 141 | 'django_tables2_reports', 142 | 'test_app' 143 | ) 144 | 145 | # EXCEL_SUPPORT = 'openpyxl' 146 | 147 | # A sample logging configuration. The only tangible logging 148 | # performed by this configuration is to send an email to 149 | # the site admins on every HTTP 500 error when DEBUG=False. 150 | # See http://docs.djangoproject.com/en/dev/topics/logging for 151 | # more details on how to customize your logging configuration. 152 | LOGGING = { 153 | 'version': 1, 154 | 'disable_existing_loggers': False, 155 | 'handlers': { 156 | 'mail_admins': { 157 | 'level': 'ERROR', 158 | 'class': 'django.utils.log.AdminEmailHandler' 159 | } 160 | }, 161 | 'loggers': { 162 | 'django.request': { 163 | 'handlers': ['mail_admins'], 164 | 'level': 'ERROR', 165 | 'propagate': True, 166 | }, 167 | } 168 | } 169 | 170 | import django 171 | 172 | if django.VERSION >= (1,4,0): 173 | TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.tz',) 174 | LOGGING['filters'] = { 175 | 'require_debug_false': { 176 | '()': 'django.utils.log.RequireDebugFalse', 177 | }, 178 | } 179 | LOGGING['handlers']['mail_admins']['filters'] = ['require_debug_false'] 180 | 181 | if django.VERSION >= (1,10,0): 182 | TEMPLATES = [ 183 | { 184 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 185 | 'APP_DIRS': True, 186 | 'DIRS': [], 187 | 'OPTIONS': { 188 | 'context_processors': [ 189 | 'django.template.context_processors.request', 190 | 'django.template.context_processors.debug', 191 | 'django.contrib.auth.context_processors.auth', 192 | 'django.contrib.messages.context_processors.messages', 193 | ], 194 | }, 195 | }, 196 | ] 197 | -------------------------------------------------------------------------------- /test_project/test_project/settings_no_debug.py: -------------------------------------------------------------------------------- 1 | from test_project.settings import * 2 | 3 | DEBUG = False 4 | TEMPLATE_DEBUG = DEBUG 5 | -------------------------------------------------------------------------------- /test_project/test_project/urls.py: -------------------------------------------------------------------------------- 1 | import django 2 | 3 | from django.conf import settings 4 | 5 | # Uncomment the next two lines to enable the admin: 6 | from django.conf.urls.static import static 7 | from django.contrib import admin 8 | from test_app.views import TestView 9 | 10 | admin.autodiscover() 11 | 12 | if django.VERSION < (1,4,0): 13 | from django.conf.urls.defaults import include, patterns, url 14 | elif django.VERSION >= (1,4,0): 15 | from django.conf.urls import include, url 16 | if django.VERSION < (1,9,0): 17 | from django.conf.urls import patterns 18 | 19 | if django.VERSION < (1,9,0): 20 | urlpatterns = patterns( 21 | '', 22 | # Examples 23 | url(r'^$', TestView.as_view(), name='index'), 24 | url(r'^index_function_view/$', 'test_app.views.index_function_view', name='index_function_view'), 25 | url(r'^index_function_view_middleware/$', 'test_app.views.index_function_view_middleware', name='index_function_view_middleware'), 26 | url(r'^admin/', include(admin.site.urls)), 27 | ) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 28 | else: 29 | # Django 1.10 30 | from test_app.views import index_function_view, index_function_view_middleware 31 | urlpatterns = [ 32 | url(r'^$', TestView.as_view(), name='index'), 33 | url(r'^index_function_view/$', index_function_view, name='index_function_view'), 34 | url(r'^index_function_view_middleware/$', index_function_view_middleware, name='index_function_view_middleware'), 35 | url(r'^admin/', include(admin.site.urls)), 36 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 37 | -------------------------------------------------------------------------------- /test_project/test_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for test_project project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 19 | # if running multiple sites in the same mod_wsgi process. To fix this, use 20 | # mod_wsgi daemon mode with each site in its own daemon process, or use 21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings" 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 23 | 24 | # This application object is used by any WSGI server configured to use this 25 | # file. This includes Django's development server, if the WSGI_APPLICATION 26 | # setting points here. 27 | from django.core.wsgi import get_wsgi_application 28 | application = get_wsgi_application() 29 | 30 | # Apply WSGI middleware here. 31 | # from helloworld.wsgi import HelloWorldApplication 32 | # application = HelloWorldApplication(application) 33 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,33,34,35,36}-django{13,14,15,16,17,18,19,110,111} 3 | 4 | [testenv] 5 | usedevelop = True 6 | deps = 7 | django13: django>=1.3,<1.4 8 | django14: django>=1.4,<1.5 9 | django15: django>=1.5,<1.6 10 | django16: django>=1.6,<1.7 11 | django17: django>=1.7, <1.8 12 | django18: django>=1.8, <1.9 13 | django19: django>=1.9, <1.10 14 | django110: django>=1.10, <1.11 15 | django111: django>=1.11, <1.12 16 | django{13,14,15,16,17}: django-tables2==0.14.0 17 | django{18,19,110,111}: django-tables2 18 | Pillow 19 | pyExcelerator 20 | xlwt 21 | openpyxl 22 | coveralls 23 | 24 | commands = 25 | python {envbindir}/coverage run -p test_project/run_tests.py 26 | python {envbindir}/coverage run -p test_project/run_tests.py test_project.settings_no_debug 27 | install_command = 28 | pip install {opts} {packages} 29 | 30 | [testenv:py33-django13] 31 | platform = nope 32 | 33 | [testenv:py33-django14] 34 | platform = nope 35 | 36 | [testenv:py33-django15] 37 | platform = nope 38 | 39 | [testenv:py33-django16] 40 | platform = nope 41 | 42 | [testenv:py33-django17] 43 | platform = nope 44 | 45 | [testenv:py34-django13] 46 | platform = nope 47 | 48 | [testenv:py34-django14] 49 | platform = nope 50 | 51 | [testenv:py34-django15] 52 | platform = nope 53 | 54 | [testenv:py34-django16] 55 | platform = nope 56 | 57 | [testenv:py34-django17] 58 | platform = nope 59 | 60 | [testenv:py35-django13] 61 | platform = nope 62 | 63 | [testenv:py35-django14] 64 | platform = nope 65 | 66 | [testenv:py35-django15] 67 | platform = nope 68 | 69 | [testenv:py35-django16] 70 | platform = nope 71 | 72 | [testenv:py35-django17] 73 | platform = nope 74 | 75 | [testenv:py36-django13] 76 | platform = nope 77 | 78 | [testenv:py36-django14] 79 | platform = nope 80 | 81 | [testenv:py36-django15] 82 | platform = nope 83 | 84 | [testenv:py36-django16] 85 | platform = nope 86 | 87 | [testenv:py36-django17] 88 | platform = nope 89 | --------------------------------------------------------------------------------