-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port the worldmap map notes application. Fixes #4101
- Loading branch information
Showing
13 changed files
with
324 additions
and
1 deletion.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
29
geonode/contrib/worldmap/mapnotes/migrations/0001_initial.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
71 changes: 71 additions & 0 deletions
71
geonode/contrib/worldmap/mapnotes/templates/mapnotes/annotation.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters