diff options
| -rw-r--r-- | chimere/admin.py | 60 | ||||
| -rw-r--r-- | chimere/forms.py | 34 | ||||
| -rw-r--r-- | chimere/migrations/0037_auto__add_unique_area_order__add_field_importer_associate_marker_to_wa.py | 251 | ||||
| -rw-r--r-- | chimere/models.py | 70 | ||||
| -rw-r--r-- | chimere/templates/admin/managed_modified.html | 47 | ||||
| -rw-r--r-- | chimere/templates/chimere/blocks/ol_map.html | 31 | ||||
| -rw-r--r-- | chimere/templates/chimere/detail.html | 3 | ||||
| -rw-r--r-- | chimere/templatetags/chimere_tags.py | 12 | ||||
| -rw-r--r-- | chimere/tests.py | 125 | ||||
| -rw-r--r-- | chimere/utils.py | 3 | ||||
| -rw-r--r-- | chimere/views.py | 2 | 
11 files changed, 596 insertions, 42 deletions
| diff --git a/chimere/admin.py b/chimere/admin.py index 8229183..b34deaa 100644 --- a/chimere/admin.py +++ b/chimere/admin.py @@ -24,12 +24,13 @@ import datetime  from django import forms  from django.conf import settings -from django.contrib import admin +from django.contrib import admin, messages +from django.core.exceptions import ObjectDoesNotExist  from django.db.models import Q -from django.http import HttpResponse +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: @@ -87,6 +88,49 @@ def export_to_csv(modeladmin, request, queryset):      return response  export_to_csv.short_description = _(u"Export to CSV") +def managed_modified(modeladmin, request, queryset): +    if queryset.count() != 1: +        messages.error(request, _(u"Only one item can be managed at a " +                                  u"time.")) +        return HttpResponseRedirect(request.get_full_path()) + +    item = queryset.all()[0] +    if not item.status == 'M': +        try: +            item = modeladmin.model.objects.get(ref_item=item, status='M') +        except ObjectDoesNotExist: +            messages.error(request, _(u"No modified item associated " +                                      u"to the selected item.")) +            return HttpResponseRedirect(request.get_full_path()) +    item_ref = item.ref_item +    if request.POST.get('rapprochement'): +        couple = [(item, item_ref)] +        if hasattr(item, 'associated_marker'): +            couple.append((item.associated_marker, item_ref.associated_marker)) +        for it, it_ref in couple: +            for k in request.POST: +                if not request.POST[k]: +                    continue +                if hasattr(it_ref, k): +                    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(request.get_full_path()) +    return render_to_response('admin/managed_modified.html', +                              {'item':item, 'item_ref':item_ref}, +                              context_instance=RequestContext(request)) +managed_modified.short_description = _(u"Managed modified items") +  class PictureInline(admin.TabularInline):      model = PictureFile      extra = 1 @@ -108,7 +152,8 @@ class MarkerAdmin(admin.ModelAdmin):      search_fields = ("name",)      list_display = ('name', 'status')      list_filter = ('status', 'categories') -    actions = [validate, export_to_kml, export_to_shapefile, export_to_csv] +    actions = [validate, 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', @@ -139,7 +184,8 @@ class RouteAdmin(admin.ModelAdmin):      exclude = ['height', 'width']      form = RouteAdminForm      readonly_fields = ('associated_file',) -    actions = [validate, export_to_kml, export_to_shapefile, export_to_csv] +    actions = [validate, managed_modified, export_to_kml, export_to_shapefile, +               export_to_csv]      def queryset(self, request):          qs = self.model._default_manager.get_query_set() @@ -165,6 +211,7 @@ class AreaAdmin(admin.ModelAdmin):      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: @@ -208,6 +255,9 @@ class PageAdmin(admin.ModelAdmin):      """      form = PageAdminForm +class PropertyModelAdmin(admin.ModelAdmin): +    list_display = ('name', 'order', 'available') +  class NewsAdmin(admin.ModelAdmin):      """      Use the TinyMCE widget for the news content diff --git a/chimere/forms.py b/chimere/forms.py index 2380f3c..c6998c8 100644 --- a/chimere/forms.py +++ b/chimere/forms.py @@ -227,7 +227,7 @@ class MarkerAdminFormBase(forms.ModelForm):          Custom save method in order to manage associated properties          """          new_marker = super(MarkerAdminFormBase, self).save(*args, **keys) -        if 'status' not in self.cleaned_data: +        if 'status' not in self.cleaned_data and not new_marker.status:              new_marker.status = 'S'          if new_marker.status == 'A':              tz = UTC() @@ -316,7 +316,7 @@ class RouteAdminForm(forms.ModelForm):          Custom save method in order to manage associated properties          """          new_route = super(RouteAdminForm, self).save(*args, **keys) -        if 'status' not in self.cleaned_data: +        if 'status' not in self.cleaned_data and not new_route.status:              new_route.status = 'S'          new_route.save()          return new_route @@ -510,6 +510,17 @@ class AreaAdminForm(forms.ModelForm):          """          Custom initialization method in order to manage area          """ +        if args: +            vals = args[0] +            for k in ('upper_left_lat', 'upper_left_lon', +                      'lower_right_lat', 'lower_right_lon'): +                v = vals.get(k) +                try: +                    v = float(v) +                except ValueError: +                    v = None +                if not v: +                    args[0][k] = None          if 'instance' in keys and keys['instance']:              instance = keys['instance']              dct = {'area':(instance.upper_left_corner, @@ -520,6 +531,25 @@ class AreaAdminForm(forms.ModelForm):                  keys['initial'] = dct          super(AreaAdminForm, self).__init__(*args, **keys) +    def clean(self): +        ''' +        Verify that the area is not empty +        ''' +        if not self.cleaned_data.get('upper_left_lat') \ +           and not self.cleaned_data.get('upper_left_lon') \ +           and not self.cleaned_data.get('lower_right_lat') \ +           and not self.cleaned_data.get('lower_right_lon') \ +           and not self.cleaned_data.get('area'): +            msg = _(u"No area selected.") +            raise forms.ValidationError(msg) +        if self.cleaned_data.get('order'): +            q = Area.objects.filter(order=self.cleaned_data.get('order')) +            if q.count(): +                msg= _(u"The area \"%s\" has the same order, you need to " +                       u" choose another one.") % unicode(q.all()[0]) +                raise forms.ValidationError(msg) +        return self.cleaned_data +      def save(self, *args, **keys):          """          Custom save method in order to manage area diff --git a/chimere/migrations/0037_auto__add_unique_area_order__add_field_importer_associate_marker_to_wa.py b/chimere/migrations/0037_auto__add_unique_area_order__add_field_importer_associate_marker_to_wa.py new file mode 100644 index 0000000..4b40fb9 --- /dev/null +++ b/chimere/migrations/0037_auto__add_unique_area_order__add_field_importer_associate_marker_to_wa.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + +    def forwards(self, orm): +        # Adding unique constraint on 'Area', fields ['order'] +        db.create_unique('chimere_area', ['order']) + +        # Adding field 'Importer.associate_marker_to_way' +        db.add_column('chimere_importer', 'associate_marker_to_way', +                      self.gf('django.db.models.fields.BooleanField')(default=True), +                      keep_default=False) + +        # Adding field 'Route.has_associated_marker' +        db.add_column('chimere_route', 'has_associated_marker', +                      self.gf('django.db.models.fields.BooleanField')(default=True), +                      keep_default=False) + + +    def backwards(self, orm): +        # Removing unique constraint on 'Area', fields ['order'] +        db.delete_unique('chimere_area', ['order']) + +        # Deleting field 'Importer.associate_marker_to_way' +        db.delete_column('chimere_importer', 'associate_marker_to_way') + +        # Deleting field 'Route.has_associated_marker' +        db.delete_column('chimere_route', 'has_associated_marker') + + +    models = { +        'chimere.area': { +            'Meta': {'ordering': "('order', 'name')", 'object_name': 'Area'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), +            'default_subcategories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False', 'blank': 'True'}), +            'dynamic_categories': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), +            'external_css': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'layers': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'through': "orm['chimere.AreaLayers']", 'to': "orm['chimere.Layer']"}), +            'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}), +            'restrict_to_extent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'db_table': "'chimere_subcategory_areas'", 'to': "orm['chimere.SubCategory']"}), +            'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), +            'urn': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}), +            'welcome_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'chimere.arealayers': { +            'Meta': {'ordering': "('order',)", 'object_name': 'AreaLayers'}, +            'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Area']"}), +            'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Layer']"}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.category': { +            'Meta': {'ordering': "['order']", 'object_name': 'Category'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.color': { +            'Meta': {'ordering': "['order']", 'object_name': 'Color'}, +            'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}), +            'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'order': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.colortheme': { +            'Meta': {'object_name': 'ColorTheme'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.icon': { +            'Meta': {'object_name': 'Icon'}, +            'height': ('django.db.models.fields.IntegerField', [], {}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'width': ('django.db.models.fields.IntegerField', [], {}) +        }, +        'chimere.importer': { +            'Meta': {'object_name': 'Importer'}, +            'associate_marker_to_way': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'default_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'filtr': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}), +            'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'srid': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'state': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'zipped': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) +        }, +        'chimere.layer': { +            'Meta': {'object_name': 'Layer'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'layer_code': ('django.db.models.fields.TextField', [], {'max_length': '300'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.marker': { +            'Meta': {'ordering': "('status', 'name')", 'object_name': 'Marker'}, +            'available_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'point': ('chimere.widgets.PointField', [], {}), +            'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_marker'", 'null': 'True', 'to': "orm['chimere.Marker']"}), +            'route': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'associated_marker'", 'null': 'True', 'to': "orm['chimere.Route']"}), +            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), +            'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}) +        }, +        'chimere.multimediafile': { +            'Meta': {'object_name': 'MultimediaFile'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'multimedia_files'", 'to': "orm['chimere.Marker']"}), +            'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.MultimediaType']"}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) +        }, +        'chimere.multimediatype': { +            'Meta': {'object_name': 'MultimediaType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'iframe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'media_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.news': { +            'Meta': {'object_name': 'News'}, +            'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.Area']", 'null': 'True', 'blank': 'True'}), +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'content': ('django.db.models.fields.TextField', [], {}), +            'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}) +        }, +        'chimere.picturefile': { +            'Meta': {'object_name': 'PictureFile'}, +            'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pictures'", 'to': "orm['chimere.Marker']"}), +            'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), +            'thumbnailfile': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'thumbnailfile_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'thumbnailfile_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'chimere.property': { +            'Meta': {'object_name': 'Property'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}), +            'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}), +            'value': ('django.db.models.fields.TextField', [], {}) +        }, +        'chimere.propertymodel': { +            'Meta': {'ordering': "('order',)", 'object_name': 'PropertyModel'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'mandatory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {}), +            'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'properties'", 'blank': 'True', 'to': "orm['chimere.SubCategory']"}), +            'type': ('django.db.models.fields.CharField', [], {'max_length': '1'}) +        }, +        'chimere.route': { +            'Meta': {'ordering': "('status', 'name')", 'object_name': 'Route'}, +            'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}), +            'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), +            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'has_associated_marker': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_route'", 'null': 'True', 'to': "orm['chimere.Route']"}), +            'route': ('chimere.widgets.RouteField', [], {}), +            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), +            'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), +            'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), +            'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'chimere.routefile': { +            'Meta': {'ordering': "('name',)", 'object_name': 'RouteFile'}, +            'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), +            'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}) +        }, +        'chimere.subcategory': { +            'Meta': {'ordering': "['category', 'order']", 'object_name': 'SubCategory'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subcategories'", 'to': "orm['chimere.Category']"}), +            'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}), +            'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), +            'order': ('django.db.models.fields.IntegerField', [], {'default': '1000'}), +            'submission': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) +        }, +        'chimere.tinyurl': { +            'Meta': {'object_name': 'TinyUrl'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        } +    } + +    complete_apps = ['chimere']
\ No newline at end of file diff --git a/chimere/models.py b/chimere/models.py index 55f5342..183e22d 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -338,6 +338,8 @@ class Importer(models.Model):                        verbose_name=_(u"Associated subcategories"))      state = models.CharField(_(u"State"), max_length=200,                               blank=True, null=True) +    associate_marker_to_way = models.BooleanField(_(u"Automatically associate "\ +                                           u"a marker to a way"), default=False)      class Meta:          verbose_name = _(u"Importer") @@ -514,6 +516,10 @@ class Marker(GeographicItem):      def geometry(self):          return self.point.wkt +    @property +    def geom_attr(self): +        return 'point' +      class Meta:          ordering = ('status', 'name')          verbose_name = _(u"Point of interest") @@ -726,10 +732,10 @@ IFRAME_LINKS = {          (re.compile(r'dailymotion.com/video/([A-Za-z0-9]*)_[A-Za-z0-9_-]*'),           re.compile(r'dailymotion.com/embed/video/([A-Za-z0-9]*)'),           re.compile("http://www.dailymotion.com/embed/video/%s")), -          'http://www.dailymotion.com/embed/video/%s'), -    'vimeo':((re.compile(r'vimeo.com/([A-Za-z0-9]*)'), -              re.compile(r'vimeo.com/video/([A-Za-z0-9]*)')), -               "http://player.vimeo.com/video/%s") +         'http://www.dailymotion.com/embed/video/%s'), +    'vimeo':((re.compile(r'vimeo.com/video/([A-Za-z0-9]*)'), +              re.compile(r'vimeo.com/([A-Za-z0-9]*)'),), +              "http://player.vimeo.com/video/%s")  }  class MultimediaExtension(models.Model): @@ -975,6 +981,8 @@ class Route(GeographicItem):                            null=True, height_field='height', width_field='width')      height = models.IntegerField(_(u"Height"), blank=True, null=True)      width = models.IntegerField(_(u"Width"), blank=True, null=True) +    has_associated_marker = models.BooleanField(_(u"Has an associated marker"), +                                                default=True)      objects = models.GeoManager()      def __unicode__(self): @@ -1010,6 +1018,10 @@ class Route(GeographicItem):      def geometry(self):          return self.route.wkt +    @property +    def geom_attr(self): +        return 'route' +      def get_init_multi(self):          if not self.associated_marker.count():              return [] @@ -1075,27 +1087,31 @@ def route_post_save(sender, **kwargs):          return      geometry_post_save(pre_save_route_values)(sender, **kwargs)      instance = kwargs['instance'] -    marker_fields = [f.attname for f in Marker._meta.fields] -    route_fields = [f.attname for f in Route._meta.fields] -    marker_dct = dict([(k, getattr(instance, k)) for k in marker_fields -                       if k in route_fields and k not in ('id', 'ref_item_id')]) -    marker_dct['point'] = "SRID=%d;POINT(%f %f)" % (instance.route.srid, -                              instance.route[0][0], instance.route[0][1]) -    marker, created = Marker.objects.get_or_create(route=instance, -                                                   defaults=marker_dct) -    if not created: -        marker.status = instance.status -    marker.save() -    properties = {} -    for pm in instance.properties(): -        prop = instance.getProperty(pm) -        if prop: -            properties[pm.pk] = prop.python_value -    # fix mis-initialized markers -    if created: -        for cat in instance.categories.all(): -            marker.categories.add(cat) -    marker.saveProperties(properties) + +    # manage associated marker +    if instance.has_associated_marker: +        marker_fields = [f.attname for f in Marker._meta.fields] +        route_fields = [f.attname for f in Route._meta.fields] +        marker_dct = dict([(k, getattr(instance, k)) for k in marker_fields +                           if k in route_fields and k not in ('id', 'ref_item_id')]) +        marker_dct['point'] = "SRID=%d;POINT(%f %f)" % (instance.route.srid, +                                  instance.route[0][0], instance.route[0][1]) +        marker, created = Marker.objects.get_or_create(route=instance, +                                                       defaults=marker_dct) +        if not created: +            marker.status = instance.status +            marker.point = marker_dct['point'] +        marker.save() +        properties = {} +        for pm in instance.properties(): +            prop = instance.getProperty(pm) +            if prop: +                properties[pm.pk] = prop.python_value +        # fix mis-initialized markers +        if created: +            for cat in instance.categories.all(): +                marker.categories.add(cat) +        marker.saveProperties(properties)  post_save.connect(route_post_save, sender=Route) @@ -1104,7 +1120,7 @@ def sync_m2m_route(sender, **kwargs):          return      route = kwargs['instance']      marker = route.associated_marker -    if not marker.count: +    if not marker.count():          return      marker = marker.all()[0]      marker.categories.clear() @@ -1238,7 +1254,7 @@ class Area(models.Model, SimpleArea):                             unique=True)      welcome_message = models.TextField(_(u"Welcome message"), blank=True,                                         null=True) -    order = models.IntegerField(_(u"Order")) +    order = models.IntegerField(_(u"Order"), unique=True)      available = models.BooleanField(_(u"Available"))      upper_left_corner = models.PointField(_(u"Upper left corner"),              default='POINT(0 0)', srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) diff --git a/chimere/templates/admin/managed_modified.html b/chimere/templates/admin/managed_modified.html new file mode 100644 index 0000000..c55650d --- /dev/null +++ b/chimere/templates/admin/managed_modified.html @@ -0,0 +1,47 @@ +{% extends "admin/base_site.html" %} +{% load chimere_tags i18n admin_static %} + +{% block extrahead %} +<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" /> +<script src="http://www.openlayers.org/api/OpenLayers.js"></script> +{% endblock %} + + +{% block content %} +<p class='errornote'>{% trans "Be careful: after validation, the modified item will be deleted. There is no roll-back." %}</p> +<fieldset class='module'> +<form method='POST' action='.'> +{% csrf_token %} +<input type='hidden' name='action' value='managed_modified'/> +<input type='hidden' name='rapprochement' value='1'/> +<input type='hidden' name='_selected_action' value='{{item.pk}}'/> +<table> +<thead> +<tr><th> </th><th>{% trans "Reference" %}</th><th>{% trans "Modified item" %}</th><th>{% trans "Accept modification" %}</th></tr> +</thead> +<tbody> +<tr><th>{% trans "Name" %}</th><td>{{item_ref}}</td><td>{{item}}</td><td><input type='checkbox' name='name'/></td></tr> +<tr> +    <th>{% trans "Categories" %}</th> +    <td>{% for cat in item_ref.categories.all %}{%if forloop.counter0 %}, {%endif%}{{cat}}{%endfor%}</td> +    <td>{% for cat in item.categories.all %}{%if forloop.counter0 %}, {%endif%}{{cat}}{%endfor%}</td> +    <td><input type='checkbox' name='categories'/></td> +</tr> +<tr><th>{% trans "Emplacement" %}</th><td>{{item_ref|ol_map:'map_ref_id'}}</td><td>{{item|ol_map:'map_id'}}</td><td><input type='checkbox' name='{{item.geom_attr}}'/></td></tr> +<tr><th>{% trans "Description" %}</th><td>{{item_ref.description|safe}}</td><td>{{item.description|safe}}</td><td><input type='checkbox' name='description'/></td></tr> +{% for property_ref in item_ref.getProperties %} +{% for property in item.getProperties %} +{% ifequal property_ref.propertymodel property.propertymodel %} +<tr><th>{{property.propertymodel.name}}</th><td>{{property_ref.value|safe}}</td><td>{{property.value|safe}}</td><td><input type='checkbox' name='property_{{property.propertymodel.pk}}'/></td></tr> +{% endifequal %} +{% endfor %} +{% endfor %} +</tbody> +</table> +</fieldset> +<div class='submit-row'> +<p class='deletelink-box'><a href="{% if item.point %}{% url admin:chimere_marker_changelist %}{%else%}{% url admin:chimere_route_changelist %}{%endif%}">{% trans "Back to list" %}</a></p> +<input class='default' type='submit' value='{% trans "Validate" %}'/> +</div> +</form> +{% endblock %} diff --git a/chimere/templates/chimere/blocks/ol_map.html b/chimere/templates/chimere/blocks/ol_map.html new file mode 100644 index 0000000..a12651c --- /dev/null +++ b/chimere/templates/chimere/blocks/ol_map.html @@ -0,0 +1,31 @@ +{% load unlocalize_point %} +<div id="{{map_id}}" style="height:250px;width:250px;"></div> +<script> +    {{map_id}} = new OpenLayers.Map("{{map_id}}"); +    {{map_id}}.addLayer(new OpenLayers.Layer.OSM()); +    var fromProjection = new OpenLayers.Projection("EPSG:4326"); +    var toProjection = new OpenLayers.Projection("EPSG:900913"); +    {% ifequal geom_type 'point' %} +    var lat = '{{geom.y|unlocalize_point}}'; +    var lon = '{{geom.x|unlocalize_point}}'; +    var position = new OpenLayers.LonLat(lon, lat).transform(fromProjection, +                                                             toProjection); + +    var {{map_id}}markers = new OpenLayers.Layer.Markers( "Markers" ); +    {{map_id}}.addLayer({{map_id}}markers); +    {{map_id}}markers.addMarker(new OpenLayers.Marker(position)); +    var zoom = 18; +    {{map_id}}.setCenter(position, zoom); +    {% else %} +    var wkt_reader = new OpenLayers.Format.WKT({ +                'internalProjection':toProjection, +                'externalProjection':fromProjection +    }); +    var {{map_id}}vectors = new OpenLayers.Layer.Vector("Vector Layer"); +    {{map_id}}.addLayer({{map_id}}vectors); +    var {{map_id}}features = wkt_reader.read('{{geom.wkt}}'); +    {{map_id}}vectors.addFeatures([{{map_id}}features]); + +    {{map_id}}.zoomToExtent({{map_id}}vectors.getDataExtent()); +    {% endifequal %} +</script> diff --git a/chimere/templates/chimere/detail.html b/chimere/templates/chimere/detail.html index 7fd2b2b..134c03e 100644 --- a/chimere/templates/chimere/detail.html +++ b/chimere/templates/chimere/detail.html @@ -28,6 +28,9 @@      <a href='#' class='show_gallery_link'>{% trans "Show multimedia gallery" %}</a>      {% endif %}      </div> +    <a href='{% if marker.route %}{% url chimere:editroute-item area_name_slash|default_if_none:"" marker.route.pk "" %}{%else%}{% url chimere:edit-item area_name_slash|default_if_none:"" marker.pk "" %}{%endif%}'> +        {% trans "Submit an amendment" %} +    </a>      {% if moderator_emails %}      <a href="mailto:?from={{moderator_emails}}&subject={% trans "Propose amendment" %}&body={% trans "I would like to propose an amendment for this item:"%} {{share_url}}">          {% trans "Propose amendment" %} diff --git a/chimere/templatetags/chimere_tags.py b/chimere/templatetags/chimere_tags.py index db62c64..b73413a 100644 --- a/chimere/templatetags/chimere_tags.py +++ b/chimere/templatetags/chimere_tags.py @@ -12,6 +12,7 @@ from django.core.urlresolvers import reverse  from django.db.models import Q, Count  from django.template import defaultfilters  from django.utils.translation import ugettext as _ +from django.template.loader import render_to_string  from chimere.models import Marker, Area, News, SubCategory, MultimediaType  from chimere.widgets import get_map_layers @@ -287,3 +288,14 @@ def share_bar(context, name='', email_only=False):      context['share_networks'] = [(defaultfilters.slugify(name), url, icon)                  for name, url, icon in settings.CHIMERE_SHARE_NETWORKS]      return context + +@register.filter(name='ol_map') +def ol_map(item, arg='map_id'): +    geom, geom_type = None, None +    if hasattr(item, 'point'): +        geom, geom_type = item.point, 'point' +    elif hasattr(item, 'route'): +        geom, geom_type = item.route, 'route' +    rendered = render_to_string('chimere/blocks/ol_map.html', {'geom': geom, +                                                               'map_id':arg}) +    return rendered diff --git a/chimere/tests.py b/chimere/tests.py index 7de79d8..f8b41b1 100644 --- a/chimere/tests.py +++ b/chimere/tests.py @@ -9,13 +9,18 @@ test_path = os.path.abspath(__file__)  test_dir_path = os.path.dirname(test_path) + os.sep  from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.gis.geos import GEOSGeometry +from django.contrib.messages.storage.fallback import FallbackStorage  from django.core.urlresolvers import reverse  from django.template import Context  from django.test import TestCase +from django.test.client import Client +from chimere.admin import managed_modified, MarkerAdmin  from chimere.models import Area, Icon, Importer, Category, SubCategory, Marker,\                             Route, News -from chimere.forms import MarkerForm +from chimere.forms import MarkerForm, AreaAdminForm  from chimere.templatetags.chimere_tags import display_news  from chimere.utils import ShapefileManager @@ -28,7 +33,7 @@ def areas_setup():                  available=True,                  upper_left_corner='SRID=4326;POINT(-3 47.5)',                  lower_right_corner='SRID=4326;POINT(-2.5 47)') -    return [area_1] +    return [area_1, area_2]  def subcategory_setup():      category = Category.objects.create(name='Main category', @@ -118,7 +123,7 @@ class KMLImporterTest(TestCase, ImporterTest):          importer2 = Importer.objects.create(importer_type='KML',           source=test_dir_path+'tests/sample.kml', -         filtr="Subcategory 1") +         filtr="Subcategory 1", associate_marker_to_way=True)          importer2.categories.add(subcategory_1)          importer2.categories.add(subcategory_2) @@ -136,14 +141,14 @@ class KMLImporterTest(TestCase, ImporterTest):  class ShapefileImporterTest(TestCase, ImporterTest):      def setUp(self): -        subcategory_1, subcategory_2 = subcategory_setup() +        self.subcategory_1, self.subcategory_2 = subcategory_setup()          importer = Importer.objects.create(importer_type='SHP',              source=test_dir_path+'tests/sample_nodes.shp.zip', zipped=True) -        importer.categories.add(subcategory_1) +        importer.categories.add(self.subcategory_1)          importer2 = Importer.objects.create(importer_type='SHP',              source=test_dir_path+'tests/sample_ways.shp.zip',              zipped=True) -        importer2.categories.add(subcategory_2) +        importer2.categories.add(self.subcategory_2)          self.marker_importers = [(importer, 29),                                   (importer2, 5),] @@ -152,6 +157,23 @@ class ShapefileImporterTest(TestCase, ImporterTest):      def test_export(self):          filename, zip_stream = ShapefileManager.export(Marker.objects.all()) +    def test_associate_marker_to_way(self): +        importer, nb = self.marker_importers[1] + +        importer.associate_marker_to_way = True +        importer.save() +        nb, nb_updated, res = importer.manager.get() +        nb = Marker.objects.filter(categories__pk=self.subcategory_2.pk).count() +        self.assertEqual(nb, 5) +        Marker.objects.filter(categories__pk=self.subcategory_2.pk).delete() +        Route.objects.filter(categories__pk=self.subcategory_2.pk).delete() + +        importer.associate_marker_to_way = False +        importer.save() +        nb, nb_updated, res = importer.manager.get() +        nb = Marker.objects.filter(categories__pk=self.subcategory_2.pk).count() +        self.assertEqual(nb, 0) +  class OSMImporterTest(TestCase, ImporterTest):      def setUp(self):          subcategory_1, subcategory_2 = subcategory_setup() @@ -213,7 +235,54 @@ class MarkerFormTest(TestCase):          form = MarkerForm(data)          self.assertEqual(form.is_valid(), False) -class CategoryTest(TestCase): +class AreaTest(TestCase): +    def setUp(self): +        self.areas = areas_setup() + +    def test_area_availability(self): +        area_1 = self.areas[0] +        area_1.available = False +        area_1.save() +        response = self.client.get('/%s/' % area_1.urn) +        self.assertRedirects(response, '/') + +class AreaAdminFormTest(TestCase): +    def setUp(self): +        self.areas = areas_setup() + +    def test_area_default(self): +        area_1, area_2 = self.areas[0], self.areas[1] +        area_1.default = True +        area_1.save() +        area_2.default = True +        area_2.save() +        area_1 = Area.objects.get(urn=area_1.urn) +        self.assertEqual(area_1.default, False) + +    def test_area_creation(self): +        base_data = {'name':u'New test', 'order':3, 'available':True, +                'urn':'area-new', +                'upper_left_lat':48.5, +                'upper_left_lon':-5, +                'lower_right_lat':48, +                'lower_right_lon':-4, +                'upper_left_corner':'SRID=4326;POINT(0 0)', +                'lower_right_corner':'SRID=4326;POINT(0 0)'} +        # order already given +        data = base_data.copy() +        data['order'] = 1 +        form = AreaAdminForm(data) +        self.assertEqual(form.is_valid(), False) +        # empty area +        data = base_data.copy() +        data.update({'upper_left_lat': 0, +                     'upper_left_lon': 0, +                     'lower_right_lat': 0, +                     'lower_right_lon': 0}) +        form = AreaAdminForm(data) +        self.assertEqual(form.is_valid(), False) + +class DynamicCategoryTest(TestCase):      def setUp(self):          self.areas = areas_setup()          self.markers = marker_setup() @@ -244,3 +313,45 @@ class NewsTest(TestCase):          self.assertEqual(len(context['news_lst']), 2)          context = display_news(Context({'area_name':'area-2'}))          self.assertEqual(len(context['news_lst']), 1) + +class RapprochementTest(TestCase): +    def setUp(self): +        self.areas = areas_setup() +        self.markers = marker_setup() +        self.adminuser = User.objects.create_superuser('admin', +                                                  'admin@test.com', +                                                  'pass') +        self.client.login(username='admin', password='pass') + +    def test_managed_modified(self): +        ref_marker = self.markers[0] +        new_vals = {'name':"Marker 1 - modified", +                    'point':GEOSGeometry('SRID=4326;POINT(-4 48)')} +        values = {'status':'M', 'ref_item':ref_marker} +        values.update(new_vals) +        modified_marker = Marker.objects.create(**values) +        modified_marker.categories.add(ref_marker.categories.all()[0]) +        response = self.client.post('/admin/chimere/marker/', +                       data={'action':['managed_modified'], +                             'index':0, 'rapprochement':1, +                             'name':1, 'point':1, +                             '_selected_action':[unicode(ref_marker.pk)] +                            }) +        ref_marker = Marker.objects.get(pk=ref_marker.pk) +        self.assertEqual(Marker.objects.filter(ref_item=ref_marker, +                                               status='M').count(), 0) +        for k in new_vals: +            self.assertEqual(getattr(ref_marker, k), new_vals[k]) + +class RouteTest(TestCase): +    def setUp(self): +        self.subcategories = subcategory_setup() + +    def test_route_creation(self): +        route_1 = Route.objects.create(name='Route 1', +                      route='SRID=4326;LINESTRING (30 10, 10 30, 40 40)') +        self.assertEqual(Marker.objects.filter(route=route_1).count(), 1) +        route_2 = Route.objects.create(name='Route 1', +                      route='SRID=4326;LINESTRING (30 10, 10 30, 40 40)', +                      has_associated_marker=False) +        self.assertEqual(Marker.objects.filter(route=route_2).count(), 0) diff --git a/chimere/utils.py b/chimere/utils.py index ad26db2..42f3b06 100644 --- a/chimere/utils.py +++ b/chimere/utils.py @@ -106,6 +106,9 @@ class ImportManager:              values.update({                  'import_source':self.importer_instance.source})              values['status'] = 'I' +            if not self.importer_instance.associate_marker_to_way\ +              and cls.__name__ == 'Route': +                values['has_associated_marker'] = False              try:                  item = cls.objects.create(**values)              except TypeError: diff --git a/chimere/views.py b/chimere/views.py index 2a93f9a..a6b369b 100644 --- a/chimere/views.py +++ b/chimere/views.py @@ -87,7 +87,7 @@ def get_base_response(area_name=""):      area = None      if area_name:          try: -            area = Area.objects.get(urn=area_name) +            area = Area.objects.get(urn=area_name, available=True)          except ObjectDoesNotExist:              return None, redirect(reverse('chimere:index'))      else: | 
