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) | 
