diff options
author | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-09-18 01:21:02 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-09-18 01:21:02 +0200 |
commit | 919f1da7d1afac6c39bc12f86b979272b0dd5483 (patch) | |
tree | 9239f3ee7af9289fa3b417d6494948c7bd210c3b | |
parent | 432e3ee5db3745502e47091199d9677f00d69339 (diff) | |
download | Chimère-919f1da7d1afac6c39bc12f86b979272b0dd5483.tar.bz2 Chimère-919f1da7d1afac6c39bc12f86b979272b0dd5483.zip |
Work on CSV export
-rw-r--r-- | chimere/admin.py | 15 | ||||
-rw-r--r-- | chimere/models.py | 12 | ||||
-rw-r--r-- | chimere/utils.py | 156 | ||||
-rw-r--r-- | example_project/settings.py | 1 |
4 files changed, 178 insertions, 6 deletions
diff --git a/chimere/admin.py b/chimere/admin.py index 3e2bb16..d215d84 100644 --- a/chimere/admin.py +++ b/chimere/admin.py @@ -39,7 +39,8 @@ from chimere.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\ from chimere.models import Category, Icon, SubCategory, Marker, \ PropertyModel, News, Route, Area, ColorTheme, Color, RouteFile,\ MultimediaType, MultimediaFile, PictureFile, Importer, Layer, AreaLayers -from chimere.utils import unicode_normalize, ShapefileManager, KMLManager +from chimere.utils import unicode_normalize, ShapefileManager, KMLManager,\ + CSVManager from chimere.widgets import TextareaWidget def get_areas_for_user(user): @@ -86,6 +87,16 @@ def export_to_shapefile(modeladmin, request, queryset): return response export_to_shapefile.short_description = _(u"Export to Shapefile") +def export_to_csv(modeladmin, request, queryset): + u""" + Export data to CSV + """ + filename, result = CSVManager.export(queryset) + response = HttpResponse(result, mimetype='text/csv') + response['Content-Disposition'] = 'attachment; filename=%s' % filename + return response +export_to_csv.short_description = _(u"Export to CSV") + class PictureInline(admin.TabularInline): model = PictureFile extra = 1 @@ -101,7 +112,7 @@ class MarkerAdmin(admin.ModelAdmin): search_fields = ("name",) list_display = ('name', 'status') list_filter = ('status', 'categories') - actions = [validate, export_to_kml, export_to_shapefile] + actions = [validate, export_to_kml, export_to_shapefile, export_to_csv] exclude = ['submiter_session_key', 'import_key', 'import_version', 'available_date'] readonly_fields = ['submiter_email', 'submiter_comment', 'import_source', diff --git a/chimere/models.py b/chimere/models.py index 19c99eb..0db9899 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -291,6 +291,10 @@ class GeographicItem(models.Model): self.import_key = new_keys self.save() + @classmethod + def properties(cls): + return [pm for pm in PropertyModel.objects.filter(available=True)] + class Marker(GeographicItem): '''Marker for a POI ''' @@ -312,8 +316,7 @@ class Marker(GeographicItem): super(Marker, self).__init__(*args, **kwargs) # add read attributes for properties for property in self.getProperties(): - attr_name = defaultfilters.slugify(property.propertymodel.name) - attr_name = re.sub(r'-','_', attr_name) + attr_name = property.propertymodel.getAttrName() if not hasattr(self, attr_name): setattr(self, attr_name, property.python_value) @@ -1049,6 +1052,11 @@ class PropertyModel(models.Model): ordering = ('order',) verbose_name = _("Property model") + def getAttrName(self): + attr_name = defaultfilters.slugify(self.name) + attr_name = re.sub(r'-','_', attr_name) + return attr_name + def getNamedId(self): '''Get the name used as named id (easily sortable) ''' diff --git a/chimere/utils.py b/chimere/utils.py index 60b2dac..a24e22f 100644 --- a/chimere/utils.py +++ b/chimere/utils.py @@ -21,6 +21,7 @@ Utilitaries """ +import csv import datetime import os import re @@ -429,10 +430,161 @@ class ShapefileManager(ImportManager): buff.close() return filename, zip_stream -RE_HOOK = re.compile('\[([^\]]*)\]') +class ImportManager: + u""" + Generic class for specific importers + """ + default_source = None + def __init__(self, importer_instance): + self.importer_instance = importer_instance + self.default_name = " - ".join([cat.name + for cat in self.importer_instance.categories.order_by('name').all()]) + + def get(self): + pass + + def put(self): + pass + + def create_or_update_item(self, cls, values, import_key, version=None): + updated, created, item = False, False, None + if import_key: + dct_import = { + 'import_key__icontains':'%s:%s;' % ( + self.importer_instance.importer_type, + import_key), + 'import_source':self.importer_instance.source} + try: + item = cls.objects.get(**dct_import) + if version and item.import_version == int(version): + # no update since the last import + return item, None, None + for k in values: + setattr(item, k, values[k]) + try: + item.save() + except TypeError: + # error on data source + return None, False, False + updated = True + except ObjectDoesNotExist: + pass + if not item: + values.update({ + 'import_source':self.importer_instance.source}) + values['status'] = 'I' + try: + item = cls.objects.create(**values) + except TypeError: + # error on data source + return None, False, False + created = True + if import_key: + item.set_key(self.importer_instance.importer_type, + import_key) + item.categories.clear() + for cat in self.importer_instance.categories.all(): + item.categories.add(cat) + return item, updated, created + @classmethod + def get_files_inside_zip(cls, zippedfile, suffixes, dest_dir=None): + try: + flz = zipfile.ZipFile(zippedfile) + except zipfile.BadZipfile: + return [], _(u"Bad zip file") + namelist = flz.namelist() + filenames = [] + for suffix in suffixes: + current_file_name = None + for name in namelist: + if name.endswith(suffix) \ + or name.endswith(suffix.lower()) \ + or name.endswith(suffix.upper()): + current_file_name = name + filenames.append(current_file_name) + files = [] + for filename in filenames: + if filename: + if dest_dir: + files.append(filename) + flz.extract(filename, dest_dir) + else: + files.append(flz.open(filename)) + else: + files.append(None) + return files + + def get_source_file(self, source, suffixes, dest_dir=None, + extra_url=None): + if not hasattr(source, 'read'): + if not source: + source = self.importer_instance.source \ + if self.importer_instance.source else self.default_source + try: + url = source + if extra_url: + url += extra_url + remotehandle = urllib2.urlopen(url) + source = StringIO.StringIO(remotehandle.read()) + remotehandle.close() + except ValueError: + # assume it is a local file + try: + source = open(source) + except IOError, msg: + return (None, msg) + except urllib2.URLError as error: + return (None, error.message) + if self.importer_instance.zipped: + try: + files = self.get_files_inside_zip(source, suffixes, dest_dir) + except zipfile.BadZipfile: + return (None, _(u"Bad zip file")) + if not files or None in files: + return (None, + _(u"Missing file(s) inside the zip file")) + source = files[0] if len(suffixes) == 1 else files + return (source, None) + +class CSVManager(ImportManager): + u""" + CSV importer + """ + _COLS = [("Id", 'pk'), (_(u"Name"), 'name'), + (_(u"Categories"), lambda obj:", ".join( + [c.name for c in obj.categories.all()])), + (_(u"State"), 'status')] + COLS = {'marker':_COLS+[(_(u"Description"), 'description'), + (_(u"Localisation"), lambda obj: obj.point.wkt)], + 'route':_COLS+[(_(u"Localisation"), lambda obj: obj.route.wkt)]} + + @classmethod + def export(cls, queryset): + dct = {'description':unicode(datetime.date.today()), 'data':[]} + cls_name = queryset.model.__name__.lower() + cols = cls.COLS[cls_name][:] + for pm in queryset.model.properties(): + cols.append((pm.name, pm.getAttrName())) + header = [lbl for lbl, attr in cols] + dct['data'].append(header) + for item in queryset.all(): + data = [] + for lbl, attr in cols: + if callable(attr): + data.append(attr(item)) + print lbl, attr(item) + else: + data.append(getattr(item, attr)) + dct['data'].append(data) + filename = unicode_normalize(settings.PROJECT_NAME + dct['description']\ + + '.csv') + result = render_to_response('chimere/export_%s.csv' % cls_name, dct) + return filename, result + +RE_HOOK = re.compile('\[([^\]]*)\]') -# manage deleted item from OSM +# TODO: manage deleted item from OSM class OSMManager(ImportManager): u""" diff --git a/example_project/settings.py b/example_project/settings.py index dec2827..6aaa577 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -76,6 +76,7 @@ CHIMERE_SHAPEFILE_ENCODING = 'ISO-8859-1' CHIMERE_THUMBS_SCALE_HEIGHT=250 CHIMERE_THUMBS_SCALE_WIDTH=None +CHIMERE_CSV_ENCODING = 'ISO-8859-1' ADMINS = ( # ('Your Name', 'your_email@domain.com'), |