Skip to content

Commit

Permalink
Port the worldmap map notes application. Fixes #4101
Browse files Browse the repository at this point in the history
  • Loading branch information
capooti committed Nov 29, 2018
1 parent c171816 commit 62474d6
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 1 deletion.
Empty file.
12 changes: 12 additions & 0 deletions geonode/contrib/worldmap/mapnotes/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .models import MapNote
from django.contrib import admin


class MapNoteAdmin(admin.ModelAdmin):
list_display = ('id', 'map', 'title', 'content', 'owner', 'created_dttm', 'modified_dttm')
date_hierarchy = 'created_dttm'
search_fields = ['title', 'content']
ordering = ('-created_dttm',)


admin.site.register(MapNote, MapNoteAdmin)
29 changes: 29 additions & 0 deletions geonode/contrib/worldmap/mapnotes/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-11-29 19:08
from __future__ import unicode_literals

from django.conf import settings
import django.contrib.gis.db.models.fields
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

operations = [
migrations.CreateModel(
name='MapNote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geometry', django.contrib.gis.db.models.fields.GeometryField(blank=True, null=True, srid=4326)),
('created_dttm', models.DateTimeField(auto_now_add=True)),
('modified_dttm', models.DateTimeField(auto_now=True)),
('content', models.TextField(blank=True, null=True, verbose_name='Content')),
('title', models.CharField(blank=True, max_length=255, null=True, verbose_name='Title')),
('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='maps.Map')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
19 changes: 19 additions & 0 deletions geonode/contrib/worldmap/mapnotes/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib.gis.db import models
from geonode.people.models import Profile
from geonode.maps.models import Map
from django.utils.translation import ugettext_lazy as _


class MapNote(models.Model):
geometry = models.GeometryField(srid=4326, null=True, blank=True)
owner = models.ForeignKey(Profile)
map = models.ForeignKey(Map)
created_dttm = models.DateTimeField(auto_now_add=True)
modified_dttm = models.DateTimeField(auto_now=True)
content = models.TextField(_('Content'), blank=True, null=True)
title = models.CharField(_('Title'), max_length=255, blank=True, null=True)

def owner_id(self):
return self.owner.id

objects = models.GeoManager()
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{% load i18n %}
{% load dialogos_tags %}
<div id="annotation_page_div">
<script type="text/javascript">
{% autoescape off %}
function addComment() {
var comment = Ext.get("id_comment").getValue();
var path = "{{request.path}}";
Ext.Ajax.request({
url: "{% comment_target annotation %}",
method: "POST",
params: {
"comment": comment,
"next": path
},
success: function(){
Ext.get("annotation_page_div").load({
url: "{{ request.path }}",
scripts: true
});
},
failure: function(response,opts){
Ext.msg("Error", "Could not save comment - are you logged in?");
}
});
}
{% endautoescape %}
</script>

<h3>{{ annotation.title }}</h3>
<p>{{ annotation.content|linebreaks }}</p>
<hr/>
<p>{% trans "Author" %} : <a href="/profiles/{{ annotation.owner.username }}">{{ annotation.owner.username }}</a></p>
<p>{% trans "Date" %}: {{ annotation.modified_dttm }}</p>

<h3>{% trans "Comments" %}</h3>
<div class="comments_container">
{% comments annotation as comments %}
{% for comment in comments %}
<div class="comment">
<div class="comment_content">
{{ comment.comment|escape|urlize|safe }}
</div>
<p class="comment_author"><a href="{{ comment.author.get_absolute_url }}">{{ comment.author.get_full_name|default:comment.author|capfirst }}</a>
commented <span class="comment_ago">
{% blocktrans with comment.submit_date|timesince as age %}
{{ age }} ago
{% endblocktrans %}
</span>
</p>
</div>
{% endfor %}

{% if request.user.is_authenticated %}
<h3>{% trans "Post a comment" %}</h3>
{% comment_form annotation as comment_form %}
<form method="POST" id="comment_submission_form" action="{% comment_target annotation %}" onsubmit="addComment();return false;">
{% csrf_token %}
<div class="comment_box">
{{ comment_form.comment }}
</div>
<div class="comment_post">
<input type="submit" value="{% trans "Submit" %}" />
</div>
<input type="hidden" id="id_next" name="next" value="{{ request.path }}" />
</form>
{% else %}
<p><a href="{% url auth_login %}?next=/maps/{{annotation.map.id}}">{% trans "Login to add a comment" %}</a></p>
{% endif %}
</div>
</div>
95 changes: 95 additions & 0 deletions geonode/contrib/worldmap/mapnotes/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import json
from django.test import TestCase, Client


class MapNotesTest(TestCase):
fixtures = ['mapnotes_data.json']

def test_get_notes_map(self):
c = Client()
response = c.get("/annotations/1?bbox=-180.0,-90.0,180.0,90.0")
note_json = json.loads(response.content)
self.assertEquals(2, len(note_json["features"]))
for feature in note_json["features"]:
self.assertTrue(feature["id"] == 1 or feature["id"] == 2)

response = c.get("/annotations/2?bbox=-180.0,-90.0,180.0,90.0")
note_json = json.loads(response.content)
self.assertEquals(1, len(note_json["features"]))
for feature in note_json["features"]:
self.assertTrue(feature["id"] == 3)

def test_get_notes_bbox(self):
c = Client()
response = c.get("/annotations/1?bbox=-180.0,-90.0,-150.0,-60.0")
note_json = json.loads(response.content)
self.assertEquals(0, len(note_json["features"]))

response = c.get("/annotations/1?bbox=-180.0,-90.0,0.0,0.0")
note_json = json.loads(response.content)
self.assertEquals(1, len(note_json["features"]))

def test_get_specific_note(self):
c = Client()
response = c.get("/annotations/1/2")
note_json = json.loads(response.content)
self.assertEquals(1, len(note_json["features"]))
self.assertEquals(2, note_json["features"][0]["id"])
self.assertEquals("Map Note 2", note_json["features"][0]["properties"]["title"])

def test_create_new_note(self):
json_payload = """
{"type":"FeatureCollection",
"features":[
{"type":"Feature","properties":{
"title":"A new note",
"content":"This is my new note"},
"geometry":{"type":"Point","coordinates":[0.08789062499998361,17.81145608856474]}
}
]}
"""
c = Client()
c.login(username='bobby', password='bob')
response = c.post("/annotations/1", data=json_payload, content_type="application/json")
note_json = json.loads(response.content)
self.assertEquals(1, len(note_json["features"]))
self.assertEquals(4, note_json["features"][0]["id"])
self.assertEquals([0.08789062499998361, 17.81145608856474], note_json["features"][0]["geometry"]["coordinates"])
self.assertEquals("This is my new note", note_json["features"][0]["properties"]["content"])

def test_modify_existing_note(self):
json_payload = """
{"type":"FeatureCollection",
"features":[
{"type":"Feature","properties":{
"title":"A modified note",
"content":"This is my new note, modified"},
"geometry":{"type":"Point","coordinates":[0.38789062499998361,23.81145608856474]}
}
]}
"""
c = Client()
c.login(username='bobby', password='bob')
response = c.post("/annotations/1/1", data=json_payload, content_type="application/json")
note_json = json.loads(response.content)
self.assertEquals(1, len(note_json["features"]))
self.assertEquals(1, note_json["features"][0]["id"])
self.assertEquals([0.38789062499998361, 23.81145608856474], note_json["features"][0]["geometry"]["coordinates"])
self.assertEquals("This is my new note, modified", note_json["features"][0]["properties"]["content"])
self.assertEquals("A modified note", note_json["features"][0]["properties"]["title"])

def test_note_security(self):
json_payload = """
{"type":"FeatureCollection",
"features":[
{"type":"Feature","properties":{
"title":"Dont modify me",
"content":"This note should not be edited"},
"geometry":{"type":"Point","coordinates":[40.38789062499998361,43.81145608856474]}
}
]}
"""
c = Client()
c.login(username='bobby', password='bob')
response = c.post("/annotations/1/2", data=json_payload, content_type="application/json")
self.assertEquals(403, response.status_code)
8 changes: 8 additions & 0 deletions geonode/contrib/worldmap/mapnotes/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.conf.urls import url
from .views import annotations, annotation_details

urlpatterns = [
url(r'^annotations/(?P<mapid>[0-9]*)$', annotations, name='annotations'),
url(r'^annotations/(?P<mapid>[0-9]*)/(?P<id>[0-9]*)$', annotations, name='annotations'),
url(r'^annotations/(?P<id>[0-9]*)/details$', annotation_details, name='annotation_details'),
]
86 changes: 86 additions & 0 deletions geonode/contrib/worldmap/mapnotes/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from django.http import HttpResponse
import django.contrib.gis.geos as geos
from django.contrib.gis.gdal.envelope import Envelope
from django.shortcuts import render
from vectorformats.Formats import Django, GeoJSON
from geonode.maps.models import Map
from .models import MapNote
from django.views.decorators.csrf import csrf_exempt
import re


def serialize(features, properties=None):
if not properties:
properties = ['title', 'description', 'owner_id']
djf = Django.Django(geodjango="geometry", properties=properties)
geoj = GeoJSON.GeoJSON()
jsonstring = geoj.encode(djf.decode(features))
return jsonstring


def applyGeometry(obj, feature):
geometry_type = feature.geometry['type']
if geometry_type.startswith("Multi"):
geomcls = getattr(geos, feature.geometry['type'].replace("Multi", ""))
geoms = []
for item in feature.geometry['coordinates']:
geoms.append(geomcls(*item))
geom = getattr(geos, feature.geometry['type'])(geoms)
else:
geomcls = getattr(geos, feature.geometry['type'])
geom = geomcls(*feature.geometry['coordinates'])
obj.geometry = geom
for key, value in feature.properties.items():
if key != 'owner_id':
setattr(obj, key, value)
obj.save()
return obj


@csrf_exempt
def annotations(request, mapid, id=None):
geoj = GeoJSON.GeoJSON()
if id is not None:
obj = MapNote.objects.get(pk=id)
map_obj = Map.objects.get(id=mapid)
if request.method == "DELETE":
if request.user.id == obj.owner_id or request.user.has_perm('maps.change_map', obj=map_obj):
obj.delete()
return HttpResponse(status=200)
else:
return HttpResponse(status=403)
elif request.method != "GET":
if request.user.id == obj.owner_id:
features = geoj.decode(request.raw_post_data)
obj = applyGeometry(obj, features[0])
else:
return HttpResponse(status=403)
return HttpResponse(serialize([obj], ['title', 'content', 'owner_id']), status=200)
if request.method == "GET":
bbox = [float(n) for n in re.findall('[0-9\.\-]+', request.GET["bbox"])]
features = MapNote.objects.filter(map=Map.objects.get(pk=mapid), geometry__intersects=Envelope(bbox).wkt)
else:
if request.user.id is not None:
features = geoj.decode(request.body)
created_features = []
for feature in features:
obj = MapNote(map=Map.objects.get(id=mapid), owner=request.user)
obj = applyGeometry(obj, feature)
created_features.append(obj)
features = created_features
else:
return HttpResponse(status=301)
data = serialize(features, ['title', 'content', 'owner_id'])
if 'callback' in request:
data = '%s(%s);' % (request['callback'], data)
return HttpResponse(data, "text/javascript")
return HttpResponse(data, "application/json")


@csrf_exempt
def annotation_details(request, id):
annotation = MapNote.objects.get(pk=id)
return render(request, 'mapnotes/annotation.html', {
"owner_id": request.user.id,
"annotation": annotation,
})
1 change: 1 addition & 0 deletions geonode/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,7 @@
'geoexplorer-worldmap',
'geonode.contrib.worldmap.gazetteer',
'geonode.contrib.worldmap.wm_extra',
'geonode.contrib.worldmap.mapnotes',
'geonode.contrib.createlayer',
)
# WorldMap Gazetter settings
Expand Down
2 changes: 1 addition & 1 deletion geonode/social/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def comment_post_save(instance, sender, created, **kwargs):
""" Send a notification when a comment to a layer, map or document has
been submitted
"""
notice_type_label = '%s_comment' % instance.content_object.class_name.lower()
notice_type_label = '%s_comment' % instance.content_type.model.lower()
recipients = get_notification_recipients(notice_type_label, instance.author)
send_notification(recipients, notice_type_label, {"instance": instance})

Expand Down
1 change: 1 addition & 0 deletions geonode/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
if settings.USE_WORLDMAP:
urlpatterns += [url(r'', include('geonode.contrib.worldmap.wm_extra.urls', namespace='worldmap'))]
urlpatterns += [url(r'', include('geonode.contrib.worldmap.gazetteer.urls', namespace='gazetteer'))]
urlpatterns += [url(r'', include('geonode.contrib.worldmap.mapnotes.urls', namespace='mapnotes'))]

urlpatterns += [

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ datautil==0.4
dicttoxml==1.7.4
django-geoexplorer-worldmap==4.0.60
geopy==1.14.0
vectorformats==0.1

#production
uWSGI==2.0.17
Expand Down

0 comments on commit 62474d6

Please sign in to comment.