Codice sorgente per jmb.jadmin.utils

# coding: utf-8
"""
Export Data
===========
    .. autoclass:: ExportData

"""
from __future__ import unicode_literals

import warnings

import django
from django.utils import six
from django.http import HttpResponse
from django.contrib import admin
from django.utils.encoding import force_text, smart_bytes, force_str
from django.db import models
try:
    from django.contrib.admin.utils import lookup_field, display_for_field
except ImportError:  # dja <= 1.6
    from django.contrib.admin.util import lookup_field, display_for_field
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as ugt


[documenti]class ExportData(object): """ Utility to export data from a model. It can export data from model fields, model methods, model properties, model admin methods. For a more precise export of data inside models, it's possible to extend this class. The default get_data() will parse also methods inside of the export class, enabling the creation of export-specif fields. It can export the data in different file output: csv, xls, xlsx. To do so, it uses different packages: #. unicodecsv for .CSV #. xlwt for .XLS #. openpyxl for .XLSX. JAdmin is NOT dependent from any of these packages. To enable the export in a specific format, it's necessary to install one or more of these packages. In case unicodecsv is not installed, the function will try to export data using the default csv python package, which cannot, however, grant data integrity in case of six.text_type strings. In the other two cases, the export won't work, returning an ImportError. Whenever any of the packages is installed, the corresponding action will get enabled into the admin extra_action_form. """ def __init__(self, modeladmin=None, queryset=None, request=None, model=None, fields_list=None): """ :arg modeladmin: the modeladmin. :arg request: the request, not used :arg queryset: the queryset or a list of :arg model: the model (used to export directly from model). :arg fields_list: the list of fields to be exported """ self.modeladmin = modeladmin self.queryset = queryset self.request = request self.model = model or modeladmin.model self.fields_list = fields_list def get_field_list(self): """return the applicable field list A field can be a callable, an attribute of modeladmin, an attribute of the model or a property of the model """ if self.fields_list: return self.fields_list if self.modeladmin: if hasattr(self.modeladmin, 'list_display_csv'): field_list = self.modeladmin.list_display_csv else: field_list = self.modeladmin.get_list_display(self.request) elif self.model and hasattr(self.model, 'list_display_csv'): field_list = self.model.list_display_csv else: field_list = self.get_exportable_fields_from_model() if 'action_checkbox' in field_list: field_list.remove('action_checkbox') return field_list def get_exportable_fields_from_model(self): """return the list of fields and properties from model """ fields = [field.attname for field in self.model._meta.fields] ## nel caso si voglia attivare l'import automatico delle properties del modello # fields += [name for name in dir(self.model) if (isinstance(getattr(self.model, name), property))] return fields def get_headers(self, field_list): """ return a translated list. If field is a callable, test if short_description exists """ def descr(item): if self.modeladmin and hasattr(self.modeladmin, item): try: return str(getattr(self.modeladmin, item).short_description) except AttributeError: return item if self.model and hasattr(self.model, item): try: return str(getattr(self.model, item).short_description) except AttributeError: return item return item return [descr(name) for name in field_list] @property def plural_name(self): """ return the plural name of the model """ return ugt(self.model._meta.verbose_name_plural) def get_data(self): """ return the rows of the document to be exported """ # Attempt to split into a simple part that uses modeladmin that is difficult to # serialize and another that can be serialized so as to use it when passing over # to celery field_list = self.get_field_list() yield self.get_headers(field_list) for obj in self.queryset: row = [] for field_name in field_list: if field_name == 'action_checkbox': continue field, attr, value = lookup_field(field_name, obj, self.modeladmin) if not field: # If modeladmin is defined and it has field_name attr we get value from there if self.modeladmin and hasattr(self.modeladmin, field_name): result_repr = getattr(self.modeladmin, field_name)(obj) # If modeladmin is defined but it hasn't field_name attr we get value from obj elif self.modeladmin and hasattr(obj, field_name): result_repr = getattr(obj, field_name) if isinstance( getattr(self.model, field_name), property ) else getattr(obj, field_name)() # If modeladmin is not defined we get the value from obj elif not self.modeladmin and hasattr(obj, field_name): result_repr = getattr(obj, field_name) if isinstance( getattr(self.model, field_name), property ) else getattr(obj, field_name)() # Finally, tries to interpret it as a function of the ExportData class (or # an inherited class) elif hasattr(self, field_name): result_repr = getattr(obj, field_name) # If there is no match at all, we need to raise a ConfigurationError else: ConfigurationError( ugt( "field name %(field_name)s does not appear to be valid" ) % {'field_name': field_name} ) elif value is None: if isinstance(field, models.NullBooleanField): result_repr = ugt("None") else: result_repr = '' else: if isinstance(field, (models.NullBooleanField, models.BooleanField)): result_repr = value and _('Yes') or _('No') else: if django.VERSION[:2] >= (1, 9): result_repr = display_for_field(value, field, '') else: result_repr = display_for_field(value, field) # Tolgo eventuali 'a capo' row.append(result_repr) yield row def export(self, filename=None, output_type='csv'): """Export data according to specific output_type :arg filename: the output filename, missing the filename a buffer is returned created with six.StringIO :arg output_type: csv (default), xls or xlsx. Note that each output type depends on external pachakes that you must add to your env """ if output_type == 'csv': return export_csv(self.get_data(), filename) elif output_type == 'xls': return export_xls(self.get_data(), filename, sheet_name=self.model.__name__.lower()) elif output_type == 'xlsx': return export_xlsx(self.get_data(), filename)
def export_csv(data, filename, delimiter=";", quotechar='"', quoting=None): """Write data to a csv file :arg data: a list of lists to be saved :arg filename: the name of the file to be created :arg delimiter: delimiter (default ';') :arg quotechar: quotechar (default '"') :arg quoting: default csv.QUTE_ALL """ try: import unicodecsv as csv except ImportError: warnings.warn( "To properly manage six.text_type string, " "it's recommended to install unicodecsv package", UnicodeWarning ) import csv if filename: f_obj = open(filename, 'wb') else: f_obj = six.BytesIO() if six.PY2: delimiter = smart_bytes(delimiter) quotechar = smart_bytes(quotechar) else: delimiter = force_text(delimiter) quotechar = force_text(quotechar) writer = csv.writer( f_obj, delimiter=delimiter, quotechar=quotechar, quoting=quoting or csv.QUOTE_ALL ) writer.writerows(data) if not filename: return f_obj def export_xls(data, filename=None, sheet_name='sheet'): """Write data to an xls file or return a buffer :arg data: a list of lists to be saved :arg filename: the name of the file to be created, if missing a buffer is returned :arg save: It False a buffer is created and return instead of a file :arg quotechar: quotechar (default '"') :arg quoting: default csv.QUTE_ALL """ try: from xlwt import Workbook except ImportError: raise ImportError( "To export data to a .xls file it's necessary to install xlwt package" ) workbook = Workbook(encoding='utf-8') page_index = 0 for row_index, row_content in enumerate(data): if row_index == page_index * 65530: sheet = workbook.add_sheet( "{0}-{1}".format(sheet_name, page_index) ) page_index += 1 page_row_index = 0 for column_index, cell_value in enumerate(row_content): try: sheet.write(page_row_index, column_index, cell_value) except Exception: # Purtroppo xlwt solleva un'eccezione generica cell_value = force_text(cell_value, strings_only=True) sheet.write(page_row_index, column_index, cell_value) page_row_index += 1 if filename: workbook.save(filename) else: buffer = six.StringIO() workbook.save(buffer) return buffer def export_xlsx(data, filename): """Write data to an xlsx file or return a buffer :arg data: a list of lists to be saved :arg filename: the name of the file to be created, if missing a buffer is returned """ try: from openpyxl.workbook import Workbook except ImportError: raise ImportError( "To export data to a .xlsx file it's necessary to install openpyxl package" ) wb = Workbook(write_only=True) ws = wb.create_sheet() for row_content in data: ws.append(row_content) if filename: wb.save(filename) else: buffer = six.StringIO() wb.save(buffer) return buffer def attach_response(data, filename, output_type='csv'): """Return an HttpResponse containing data and content_type according to output_type :arg data: data (list of lists) or a file-like object :param filename: Name of the retured file. Default: file.xls :return: HttpResponse object """ assert output_type in ('csv', 'xls', 'xlsx'), "Output type {} not supported".format( output_type) content_types = { 'csv': 'text/csv', 'xls': 'application/vnd.ms-excel', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', } if not filename: filename = 'file.{}'.format(output_type) response = HttpResponse(content_type=content_types[output_type]) response['Content-Disposition'] = 'attachment; filename= %s' % filename response.write(data.getvalue()) return response