diff options
Diffstat (limited to 'chimere/admin.py')
| -rw-r--r-- | chimere/admin.py | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/chimere/admin.py b/chimere/admin.py new file mode 100644 index 0000000..a30f0df --- /dev/null +++ b/chimere/admin.py @@ -0,0 +1,610 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +""" +Settings for administration pages +""" + +from copy import deepcopy + +from django.conf import settings +from django.contrib import admin, messages +from django.contrib.admin import SimpleListFilter +from django.contrib.admin.util import flatten_fieldsets +from django.contrib.auth.admin import UserAdmin as VanillaUserAdmin +from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.translation import ugettext_lazy as _ +try: + from chimere import tasks +except ImportError: + pass + +from chimere.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\ + NewsAdminForm, CategoryAdminForm, ImporterAdminForm, OSMForm, \ + PageAdminForm, PictureFileAdminForm, MultimediaFileAdminForm, \ + PolygonAdminForm +from chimere import models +from chimere.models import Category, Icon, SubCategory, Marker, \ + PropertyModel, News, Route, Area, ColorTheme, Color, \ + MultimediaFile, PictureFile, Importer, Layer, AreaLayers,\ + PropertyModelChoice, Page, get_areas_for_user, \ + ImporterKeyCategories, SubCategoryUserLimit +from chimere.utils import ShapefileManager, KMLManager, CSVManager + + +def disable(modeladmin, request, queryset): + for item in queryset: + item.status = 'D' + item.save() +disable.short_description = _(u"Disable") + + +def validate(modeladmin, request, queryset): + for item in queryset: + item.status = 'A' + item.save() +validate.short_description = _(u"Validate") + + +def export_to_kml(modeladmin, request, queryset): + u""" + Export data to KML + """ + filename, result = KMLManager.export(queryset) + response = HttpResponse(result, + mimetype='application/vnd.google-earth.kml+xml') + response['Content-Disposition'] = 'attachment; filename=%s' % filename + return response +export_to_kml.short_description = _(u"Export to KML") + + +def export_to_shapefile(modeladmin, request, queryset): + u""" + Export data to Shapefile + """ + filename, zip_stream = ShapefileManager.export(queryset) + # Stick it all in a django HttpResponse + response = HttpResponse() + response['Content-Disposition'] = 'attachment; filename=%s.zip' % filename + response['Content-length'] = str(len(zip_stream)) + response['Content-Type'] = 'application/zip' + response.write(zip_stream) + 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") + + +def managed_modified(modeladmin, request, queryset): + # not very clean... There is must be a better way to do that + redirect_url = request.get_full_path().split('admin_modification')[0] + if queryset.count() != 1 and len(set([i.ref_item or i + for i in queryset.all()])) != 1: + messages.error(request, _(u"Only one item can be managed at a " + u"time.")) + return HttpResponseRedirect(redirect_url) + + item = queryset.all()[0] + if not item.ref_item or item.ref_item == item: + try: + item = modeladmin.model.objects.filter(ref_item=item)\ + .exclude(pk=item.pk).all()[0] + except IndexError: + messages.error(request, _(u"No modified item associated " + u"to the selected item.")) + return HttpResponseRedirect(redirect_url) + item_ref = item.ref_item + if request.POST.get('rapprochement'): + couple = [(item, item_ref)] + if hasattr(item, 'associated_marker'): + couple.append((item.associated_marker.all()[0], + item_ref.associated_marker.all()[0])) + updated = dict(request.POST) + # clean + for k in ('action', 'rapprochement', 'index', '_selected_action'): + if k in updated: + updated.pop(k) + for idx, cpl in enumerate(couple): + it, it_ref = cpl + # don't copy geometry of associated items + if idx: + for k in ('route', 'point'): + if k in updated: + updated.pop(k) + updated_keys = updated.keys() + if it.status == 'I': + updated_keys.append('import_version') + for k in updated_keys: + if k != 'import_version' and not request.POST[k]: + continue + if hasattr(it_ref, k): + c_value = getattr(it_ref, k) + if hasattr(c_value, 'select_related'): + c_value.clear() + for val in getattr(it, k).all(): + c_value.add(val) + else: + setattr(it_ref, k, getattr(it, k)) + it_ref.save() + elif k.startswith('property_'): + try: + pm = PropertyModel.get(pk=int(k[len('property_'):])) + it_ref.setProperty(pm, it.getProperty(pm)) + except (ValueError, ObjectDoesNotExist): + pass + if hasattr(item, 'associated_marker'): + for it in item.associated_marker.all(): + it.delete() + item.delete() + messages.success(request, _(u"Modified item traited.")) + return HttpResponseRedirect(redirect_url) + return render_to_response('admin/chimere/managed_modified.html', + {'item': item, 'item_ref': item_ref}, + context_instance=RequestContext(request)) +managed_modified.short_description = _(u"Managed modified items") + + +class CatLimitInline(admin.TabularInline): + model = SubCategoryUserLimit + extra = 5 + + +class UserAdmin(VanillaUserAdmin): + list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') + inlines = (CatLimitInline,) + +admin.site.unregister(User) +admin.site.register(User, UserAdmin) + + +class PictureInline(admin.TabularInline): + model = PictureFile + extra = 1 + ordering = ('order',) + form = PictureFileAdminForm + readonly_fields = ('height', 'width') + exclude = ('thumbnailfile', 'thumbnailfile_height', 'thumbnailfile_width') + + +class MultimediaInline(admin.TabularInline): + model = MultimediaFile + extra = 1 + ordering = ('order',) + form = MultimediaFileAdminForm + + +class AreaMarkerListFilter(admin.SimpleListFilter): + title = _('area') + parameter_name = 'area' + + def lookups(self, request, model_admin): + return [(area.urn, area.name) for area in models.Area.objects.all()] + + def queryset(self, request, queryset): + try: + area = models.Area.objects.get(urn=self.value()) + except models.Area.DoesNotExist: + return queryset + return queryset.filter(area.getIncludeMarker()) + + +class AreaRouteListFilter(AreaMarkerListFilter): + def queryset(self, request, queryset): + try: + area = models.Area.objects.get(urn=self.value()) + except models.Area.DoesNotExist: + return queryset + return queryset.filter(area.getIncludeRoute()) + + +class AreaPolygonListFilter(AreaMarkerListFilter): + def queryset(self, request, queryset): + try: + area = models.Area.objects.get(urn=self.value()) + except models.Area.DoesNotExist: + return queryset + return queryset.filter(area.getIncludePolygon()) + + +class HasCategoriesListFilter(SimpleListFilter): + title = _('Has categories') + parameter_name = 'has_category' + + def lookups(self, request, model_admin): + return ( + ('true', _('Yes')), + ('false', _('No')), + ) + + def queryset(self, request, queryset): + if self.value() == 'false': + return queryset.filter(categories__isnull=True) + elif self.value() == 'true': + return queryset.exclude(categories__isnull=True) + return queryset + + +class CategoriesListFilter(SimpleListFilter): + title = _('categories') + parameter_name = 'category' + + def lookups(self, request, model_admin): + if request.user.subcategory_limit_to.count(): + q = request.user.subcategory_limit_to + return [(l.subcategory.pk, unicode(l.subcategory)) + for l in q.all()] + q = SubCategory.objects + return [(cat.pk, unicode(cat)) for cat in q.all()] + + def queryset(self, request, queryset): + if self.value(): + return queryset.filter(categories__pk=self.value()) + return queryset + + +def moderator_right(user, qs, geo_type='marker'): + if user.is_superuser: + return qs + areas = get_areas_for_user(user) + if areas: + contained = Q() + for area in areas: + if geo_type == 'marker': + contained = contained | area.getIncludeMarker() + elif geo_type == 'route': + contained = contained | area.getIncludeRoute() + qs = qs.filter(contained) + if user.subcategory_limit_to.count(): + qs = qs.filter(categories__in=SubCategory.objects.filter( + limited_for_user__user=user).all()) + return qs + +MARKER_FIELDSETS = [ + [None, { + 'fields': ['point', 'name', 'status', 'categories', 'description', + 'keywords', 'start_date', 'end_date'] + }], + [_(u"Submitter"), { + 'classes': ('collapse',), + 'fields': ('submiter_name', 'submiter_email', 'submiter_comment') + }], + [_(u"Import"), { + 'classes': ('collapse',), + 'fields': ('not_for_osm', 'modified_since_import', 'import_source', + 'origin', 'license') + }], + [_(u"Associated items"), { + 'classes': ('collapse',), + 'fields': ['ref_item', 'route'] + }] +] + +ROUTE_FIELDSETS = deepcopy(MARKER_FIELDSETS) +ROUTE_FIELDSETS[0][1]['fields'][0] = 'route' +ROUTE_FIELDSETS[0][1]['fields'].pop(ROUTE_FIELDSETS[0][1]['fields'].index( + 'description')) +ROUTE_FIELDSETS[3][1]['fields'] = ('ref_item', 'associated_file', + 'has_associated_marker') +POLYGON_FIELDSETS = deepcopy(MARKER_FIELDSETS) +POLYGON_FIELDSETS[0][1]['fields'][0] = 'polygon' +POLYGON_FIELDSETS[0][1]['fields'].pop(POLYGON_FIELDSETS[0][1]['fields'].index( + 'description')) +POLYGON_FIELDSETS.pop(3) + + +class MarkerAdmin(admin.ModelAdmin): + """ + Specialized the Point field. + """ + search_fields = ("name",) + list_display = ('name', 'status', 'start_date', 'end_date') + list_filter = ('status', AreaMarkerListFilter, CategoriesListFilter, + HasCategoriesListFilter, 'start_date', 'end_date') + actions = [validate, disable, managed_modified, export_to_kml, + export_to_shapefile, export_to_csv] + exclude = ['submiter_session_key', 'import_key', 'import_version', + 'available_date', 'ref_item'] + readonly_fields = [ + 'submiter_email', 'submiter_comment', 'import_source', + 'submiter_name', 'ref_item', 'modified_since_import', 'route'] + form = MarkerAdminForm + fieldsets = MARKER_FIELDSETS + inlines = [MultimediaInline, PictureInline] + has_properties = True + + def get_fieldsets(self, request, obj=None): + """ + Manage properties in fieldsets. + """ + fieldsets = super(MarkerAdmin, self).get_fieldsets(request, obj) + newfieldsets = list(fieldsets) + if self.has_properties: + main_fields = newfieldsets[0][1]['fields'] + for pm in PropertyModel.objects.filter(available=True)\ + .order_by('order').all(): + pm_name = pm.getNamedId() + if pm_name not in main_fields: + main_fields.append(pm_name) + return newfieldsets + + def queryset(self, request): + qs = self.model._default_manager.get_query_set() + qs = moderator_right(request.user, qs, geo_type='marker') + ordering = self.ordering or () + if ordering: + qs = qs.order_by(*ordering) + return qs.distinct() + + def admin_modification(self, request, item_id): + ''' + Redirect to the marker modification form + ''' + return managed_modified( + self, request, Marker.objects.filter(pk=item_id)) + + def get_urls(self): + from django.conf.urls.defaults import patterns, url + urls = super(MarkerAdmin, self).get_urls() + my_urls = patterns( + '', + url(r'^admin_modification/(?P<item_id>\d+)/$', + self.admin_site.admin_view(self.admin_modification), + name='admin-modification'), + ) + return my_urls + urls + + def get_form(self, request, obj=None, **kwargs): + # remove dynamic field to prevent admin check + kwargs['fields'] = flatten_fieldsets(self.declared_fieldsets) + form = super(MarkerAdmin, self).get_form(request, obj, **kwargs) + q = request.user.subcategory_limit_to + if not q.count(): + return form + form = type('MarkerAdminLimit', (form,), + {'categories_choices': [ + (l.subcategory.pk, unicode(l.subcategory)) + for l in q.all()]}) + return form + + +class RouteAdmin(MarkerAdmin): + """ + Specialized the Route field. + """ + search_fields = ("name",) + list_display = ('name', 'status') + list_filter = ('status', AreaRouteListFilter, 'categories') + exclude = ['height', 'width'] + form = RouteAdminForm + readonly_fields = ('associated_file', 'ref_item', 'has_associated_marker') + actions = [validate, disable, managed_modified, export_to_kml, + export_to_shapefile, export_to_csv] + fieldsets = ROUTE_FIELDSETS + inlines = [] + has_properties = False + + def queryset(self, request): + qs = self.model._default_manager.get_query_set() + qs = moderator_right(request.user, qs, geo_type='route') + ordering = self.ordering or () + if ordering: + qs = qs.order_by(*ordering) + return qs + + def admin_modification(self, request, item_id): + ''' + Redirect to the route modification form + ''' + return managed_modified(self, request, + Route.objects.filter(pk=item_id)) + + +class PolygonAdmin(MarkerAdmin): + """ + Specialized the Polygon field. + """ + list_filter = ('status', AreaPolygonListFilter, 'categories') + form = PolygonAdminForm + actions = [validate, disable, managed_modified, export_to_kml, + export_to_shapefile, export_to_csv] + readonly_fields = [ + 'submiter_email', 'submiter_comment', 'import_source', + 'submiter_name', 'ref_item', 'modified_since_import'] + exclude = ['submiter_session_key', 'import_key', 'import_version', + 'ref_item'] + inlines = [] + fieldsets = POLYGON_FIELDSETS + + +class LayerInline(admin.TabularInline): + model = AreaLayers + extra = 1 + + +class AreaAdmin(admin.ModelAdmin): + """ + Specialized the area field. + """ + form = AreaAdminForm + exclude = ['upper_left_corner', 'lower_right_corner'] + inlines = [LayerInline] + list_display = ['name', 'order', 'available', 'default'] + + +def importing(modeladmin, request, queryset): + for importer in queryset: + importer.state = unicode(tasks.IMPORT_MESSAGES['import_pending'][0]) + importer.save() + tasks.importing(importer.pk) +importing.short_description = _(u"Import") + + +def cancel_import(modeladmin, request, queryset): + for importer in queryset: + importer.state = tasks.IMPORT_MESSAGES['import_cancel'][0] + importer.save() +cancel_import.short_description = _(u"Cancel import") + + +def cancel_export(modeladmin, request, queryset): + for importer in queryset: + importer.state = tasks.IMPORT_MESSAGES['export_cancel'][0] + importer.save() +cancel_export.short_description = _(u"Cancel export") + + +def export_to_osm(modeladmin, request, queryset): + if queryset.count() > 1: + messages.error(request, + _(u"Can manage only one OSM export at a time.")) + return HttpResponseRedirect(request.get_full_path()) + importer = queryset.all()[0] + if Marker.objects.filter(categories__in=importer.categories.all(), + status='I').count(): + messages.error(request, _(u"You must treat all item with the status " + u"\"imported\" before exporting to OSM.")) + return HttpResponseRedirect(request.get_full_path()) + if importer.importer_type != 'OSM': + messages.error(request, + _(u"Only OSM importer are managed for export.")) + return HttpResponseRedirect(request.get_full_path()) + item_nb = Marker.objects.filter( + status='A', categories=importer.categories.all(), not_for_osm=False, + modified_since_import=True, route=None).count() + if not item_nb: + messages.error(request, + _(u"No point of interest are concerned by this " + u"export.")) + return HttpResponseRedirect(request.get_full_path()) + form = None + if request.method == 'POST' and ( + 'email' in request.POST or 'api' in request.POST + or 'password' in request.POST): + form = OSMForm(request.POST) + if form.is_valid(): + importer.state = unicode( + tasks.IMPORT_MESSAGES['export_pending'][0]) + importer.save() + tasks.exporting(importer.pk, form.cleaned_data) + messages.success(request, _(u"Export launched.")) + return HttpResponseRedirect(request.get_full_path()) + else: + form = OSMForm() + msg_item = _(u"%s point(s) of interest concerned by this export before " + u"bounding box filter.") % item_nb + return render_to_response('admin/chimere/osm_export.html', + {'item': importer, 'form': form, + 'msg_item': msg_item}, + context_instance=RequestContext(request)) +export_to_osm.short_description = _(u"Export to osm") + + +class ImporterKeyInline(admin.TabularInline): + model = ImporterKeyCategories + extra = 1 + + +class ImporterAdmin(admin.ModelAdmin): + form = ImporterAdminForm + list_display = ('importer_type', 'display_categories', 'default_name', + 'source', 'state', 'filtr') + list_filter = ('importer_type', 'categories') + readonly_fields = ('state',) + actions = [importing, cancel_import, export_to_osm, cancel_export] + inlines = [ImporterKeyInline] +admin.site.register(Importer, ImporterAdmin) + + +class PageAdmin(admin.ModelAdmin): + """ + Use the TinyMCE widget for the page content + """ + form = PageAdminForm + + +class NewsAdmin(admin.ModelAdmin): + """ + Use the TinyMCE widget for the news content + """ + form = NewsAdminForm + + +class SubcatInline(admin.TabularInline): + model = SubCategory + extra = 1 + + +class CategoryAdmin(admin.ModelAdmin): + """ + Use the TinyMCE widget for categories + """ + form = CategoryAdminForm + inlines = [SubcatInline] + list_display = ['name', 'order'] + + +class ColorInline(admin.TabularInline): + model = Color + + +class ColorThemeAdmin(admin.ModelAdmin): + inlines = [ColorInline] + + +class IconAdmin(admin.ModelAdmin): + exclude = ['height', 'width'] + list_display = ['name'] + + +class PropertyModelChoiceInline(admin.TabularInline): + model = PropertyModelChoice + extra = 1 + + +class PropertyModelAdmin(admin.ModelAdmin): + list_display = ('name', 'order', 'available') + inlines = [PropertyModelChoiceInline] + +# register of differents database fields +admin.site.register(Page, PageAdmin) +admin.site.register(News, NewsAdmin) +admin.site.register(Category, CategoryAdmin) +admin.site.register(Icon, IconAdmin) +admin.site.register(Marker, MarkerAdmin) +admin.site.register(models.Route, RouteAdmin) +admin.site.register(models.Polygon, PolygonAdmin) +if not settings.CHIMERE_HIDE_PROPERTYMODEL: + admin.site.register(PropertyModel, PropertyModelAdmin) +admin.site.register(Area, AreaAdmin) +admin.site.register(ColorTheme, ColorThemeAdmin) +admin.site.register(Layer) |
