diff options
author | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-11-19 01:08:51 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@peacefrogs.net> | 2012-11-19 01:08:51 +0100 |
commit | ded35ffba989c28d8dd515fc9c0f4e241038d668 (patch) | |
tree | e756e63c9dca53bdf0f23e68a367a16b927ab61b | |
parent | 5db6ae2fc14ebbec4b52151c7250ca0bba98bc02 (diff) | |
parent | 7d8c3719bb2dfaa70b1d6c5e2a19c53588091d3b (diff) | |
download | Chimère-ded35ffba989c28d8dd515fc9c0f4e241038d668.tar.bz2 Chimère-ded35ffba989c28d8dd515fc9c0f4e241038d668.zip |
Merge branch 'master' into saclay
Conflicts:
chimere/admin.py
chimere/models.py
chimere/templates/chimere/detail.html
chimere/templatetags/chimere_tags.py
chimere/tests.py
-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: |