diff --git a/mapstory/apps/boxes/urls.py b/mapstory/apps/boxes/urls.py deleted file mode 100755 index a96e661a9..000000000 --- a/mapstory/apps/boxes/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import patterns, url - - -urlpatterns = patterns( - 'mapstory.apps.boxes.views', - url(r'^$', 'boxes', name='boxes'), -) diff --git a/mapstory/annotations/__init__.py b/mapstory/apps/storyframes/__init__.py similarity index 100% rename from mapstory/annotations/__init__.py rename to mapstory/apps/storyframes/__init__.py diff --git a/mapstory/apps/boxes/admin.py b/mapstory/apps/storyframes/admin.py similarity index 53% rename from mapstory/apps/boxes/admin.py rename to mapstory/apps/storyframes/admin.py index f4e9798b4..b9cdc34e9 100644 --- a/mapstory/apps/boxes/admin.py +++ b/mapstory/apps/storyframes/admin.py @@ -1,10 +1,10 @@ -from mapstory.apps.boxes.models import StoryBox +from mapstory.apps.storyframes.models import StoryFrame from django.contrib import admin -class StoryBoxAdmin(admin.ModelAdmin): +class StoryFrameAdmin(admin.ModelAdmin): list_display = ('id', 'map', 'title') list_filter = ('map',) search_fields = ('map__title', 'title', 'description',) -admin.site.register(StoryBox, StoryBoxAdmin) \ No newline at end of file +admin.site.register(StoryFrame, StoryFrameAdmin) \ No newline at end of file diff --git a/mapstory/annotations/forms.py b/mapstory/apps/storyframes/forms.py similarity index 84% rename from mapstory/annotations/forms.py rename to mapstory/apps/storyframes/forms.py index 321636425..ed9a92c5f 100755 --- a/mapstory/annotations/forms.py +++ b/mapstory/apps/storyframes/forms.py @@ -1,18 +1,17 @@ -import json - from django import forms +from mapstory.apps.storyframes.models import StoryFrame +from mapstory.apps.storyframes.utils import datetime_to_seconds +from mapstory.apps.storyframes.utils import make_point +from mapstory.apps.storyframes.utils import parse_date_time -from mapstory.annotations.models import Annotation -from mapstory.annotations.utils import datetime_to_seconds -from mapstory.annotations.utils import make_point -from mapstory.annotations.utils import parse_date_time +import json -class AnnotationForm(forms.ModelForm): +class StoryFrameForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.form_mode = kwargs.pop('form_mode', 'client') - super(AnnotationForm, self).__init__(*args, **kwargs) + super(StoryFrameForm, self).__init__(*args, **kwargs) def parse_float(self, name): val = self.data.get(name, None) @@ -42,7 +41,7 @@ def full_clean(self): self.data['the_geom'] = make_point(lon, lat) self._convert_time('start_time') self._convert_time('end_time') - super(AnnotationForm, self).full_clean() + super(StoryFrameForm, self).full_clean() self._errors.update(self._my_errors) def _convert_time(self, key): @@ -73,5 +72,5 @@ def _convert_time(self, key): self.data[key] = str(numeric) if numeric is not None else None class Meta: - model = Annotation + model = StoryFrame fields = '__all__' diff --git a/mapstory/apps/boxes/migrations/0001_initial.py b/mapstory/apps/storyframes/migrations/0001_initial.py similarity index 100% rename from mapstory/apps/boxes/migrations/0001_initial.py rename to mapstory/apps/storyframes/migrations/0001_initial.py diff --git a/mapstory/apps/storyframes/migrations/0002_auto_20180120_1449.py b/mapstory/apps/storyframes/migrations/0002_auto_20180120_1449.py new file mode 100644 index 000000000..5c56f7fb8 --- /dev/null +++ b/mapstory/apps/storyframes/migrations/0002_auto_20180120_1449.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('storyframes', '0001_initial'), + ] + + operations = [ + migrations.RenameModel('StoryBox', 'StoryFrame') + ] diff --git a/mapstory/annotations/migrations/__init__.py b/mapstory/apps/storyframes/migrations/__init__.py similarity index 100% rename from mapstory/annotations/migrations/__init__.py rename to mapstory/apps/storyframes/migrations/__init__.py diff --git a/mapstory/apps/boxes/models.py b/mapstory/apps/storyframes/models.py similarity index 77% rename from mapstory/apps/boxes/models.py rename to mapstory/apps/storyframes/models.py index a33e95d34..9f30e416b 100755 --- a/mapstory/apps/boxes/models.py +++ b/mapstory/apps/storyframes/models.py @@ -1,27 +1,27 @@ from django.db import models from mapstory.mapstories.models import Map -from mapstory.apps.boxes.utils import parse_date_time +from mapstory.apps.storyframes.utils import parse_date_time from datetime import datetime from django.contrib.gis.db import models as gis -class StoryBoxManager(models.Manager): +class StoryFrameManager(models.Manager): - def copy_map_story_boxes(self, source_id, target): + def copy_map_story_frames(self, source_id, target): source = Map.objects.get(id=source_id) copies = [] - print('copy from', source_id, source.storybox_set.all()) + print('copy from', source_id, source.storyframe_set.all()) print('to target', target.id) - for box in source.storybox_set.all(): - box.map = target - box.pk = None - copies.append(box) + for storyframe in source.storyframe_set.all(): + storyframe.map = target + storyframe.pk = None + copies.append(storyframe) print(copies) - StoryBox.objects.bulk_create(copies) + StoryFrame.objects.bulk_create(copies) -class StoryBox(models.Model): - objects = StoryBoxManager() +class StoryFrame(models.Model): + objects = StoryFrameManager() PLAYBACK_RATE = (('seconds', 'Seconds'),('minutes', 'Minutes'),) INTERVAL_RATE = (('minutes', 'Minutes'),('hours', 'Hours'), @@ -62,12 +62,12 @@ def end_time_str(self): return self._timefmt(self.end_time) if self.end_time else '' class Meta: - verbose_name_plural = "StoryBox" + verbose_name_plural = "StoryFrame" def map_copied(sender, source_id, **kw): try: - StoryBox.objects.copy_map_story_boxes(source_id, sender) + StoryFrame.objects.copy_map_story_frames(source_id, sender) except: import traceback traceback.print_exc() diff --git a/mapstory/apps/boxes/tests.py b/mapstory/apps/storyframes/tests.py similarity index 69% rename from mapstory/apps/boxes/tests.py rename to mapstory/apps/storyframes/tests.py index e5883459d..266d31a3d 100644 --- a/mapstory/apps/boxes/tests.py +++ b/mapstory/apps/storyframes/tests.py @@ -6,12 +6,12 @@ from mapstory.tests.populate_test_data import create_models from mapstory.mapstories.models import Map from django.core.urlresolvers import reverse -from .forms import StoryBoxForm +from .forms import StoryFrameForm from .utils import parse_date_time, datetime_to_seconds, make_point -from .models import StoryBox +from .models import StoryFrame from mapstory.tests.MapStoryTestMixin import MapStoryTestMixin from django.test import TestCase -from mapstory.apps.boxes.views import boxes +from .views import storyframes # @TODO Replace this with something better that doesn't specify a username and password. class AdminClient(Client): @@ -33,10 +33,10 @@ def setUp(self): create_models(type='map') - def test_box_form(self): + def test_storyframe_form(self): data = {} m = Map.objects.first() - form = StoryBoxForm(data=data) + form = StoryFrameForm(data=data) self.assertFalse(form.is_valid()) self.assertTrue('map' in form.errors.keys()) self.assertTrue('title' in form.errors.keys()) @@ -44,25 +44,25 @@ def test_box_form(self): data['map'] = m.id data['title'] = 'this is a test' - form = StoryBoxForm(data=data) + form = StoryFrameForm(data=data) self.assertTrue(form.is_valid()) data['geometry'] = make_point(-77.464599609375, 37.61423141542417) - form = StoryBoxForm(data=data) + form = StoryFrameForm(data=data) self.assertTrue(form.is_valid()) - def test_box_form_parse_float(self): + def test_storyframe_form_parse_float(self): """ - Tests the parse float method on the box form. + Tests the parse float method on the storyframe form. """ - form = StoryBoxForm(data={'test': '1234.234'}) + form = StoryFrameForm(data={'test': '1234.234'}) self.assertEqual(form.parse_float('test'), 1234.234) self.assertIsNone(form.parse_float('nope')) # ensure value errors get added to the form.errors dict - form = StoryBoxForm(data={'lat': 'nope'}) + form = StoryFrameForm(data={'lat': 'nope'}) form.full_clean() self.assertTrue('lat' in form.errors) @@ -100,63 +100,63 @@ def test_datetime_to_seconds(self): self.assertEqual(datetime_to_seconds(datetime.datetime(2009, 10, 21, 0, 0)), 1256083200) - def test_boxes_view(self): + def test_storyframes_view(self): """ - Tests various methods of the boxes view. + Tests various methods of the storyframes view. """ m = Map.objects.first() c = AdminClient() - response = c.get(reverse('boxes', args=[m.id])) + response = c.get(reverse('storyframes', args=[m.id])) self.assertEqual(response.status_code, 200) - response = c.delete(reverse('boxes', args=[m.id])) + response = c.delete(reverse('storyframes', args=[m.id])) self.assertEqual(response.status_code, 400) - response = c.put(reverse('boxes', args=[m.id])) + response = c.put(reverse('storyframes', args=[m.id])) self.assertEqual(response.status_code, 400) data = dict(map=m.id, title='this is a test', geometry=make_point(-77.464599609375, 37.61423141542417)) - response = c.post(reverse('boxes', args=[m.id]), data=data) + response = c.post(reverse('storyframes', args=[m.id]), data=data) self.assertEqual(response.status_code, 403) - # TODO: Test POST request with box payload + # TODO: Test POST request with storyframe payload c.login_as_admin() data = {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"playback":3,"playbackRate":"seconds","interval":1,"intervalRate":"years","title":"Sample Title","description":"Sample Description","start_time":946684800,"end_time":1577836800,"zoom":6,"center":[1766040.0266747456,729122.1624405942],"range":{"start":946684800000,"end":1577836800000},"speed":{"interval":31536000000,"seconds":3},"_offset":0,"_id":1444749022177}}]} - response = c.post(reverse('boxes', args=[m.id]), data=json.dumps(data), content_type='application/json') + response = c.post(reverse('storyframes', args=[m.id]), data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 200) - box = StoryBox.objects.first() + storyframe = StoryFrame.objects.first() feature = data['features'][0] - self.assertEqual(box.map, m) - self.assertEqual(box.title, feature['properties']['title']) - self.assertEqual(box.playback, feature['properties']['playback']) - self.assertEqual(box.playbackRate, feature['properties']['playbackRate']) - self.assertEqual(box.interval, feature['properties']['interval']) - self.assertEqual(box.intervalRate, feature['properties']['intervalRate']) - self.assertEqual(box.description, feature['properties']['description']) - self.assertEqual(box.start_time, feature['properties']['start_time']) - self.assertEqual(box.end_time, feature['properties']['end_time']) - self.assertEqual(box.zoom, feature['properties']['zoom']) - self.assertEqual(eval(box.center), feature['properties']['center']) - self.assertEqual(eval(box.speed), feature['properties']['speed']) - - -class BoxesViewUnitTests(TestCase): - def test__boxes_get(self): - self.assertIsNotNone(boxes) - - def test_boxes(self): + self.assertEqual(storyframe.map, m) + self.assertEqual(storyframe.title, feature['properties']['title']) + self.assertEqual(storyframe.playback, feature['properties']['playback']) + self.assertEqual(storyframe.playbackRate, feature['properties']['playbackRate']) + self.assertEqual(storyframe.interval, feature['properties']['interval']) + self.assertEqual(storyframe.intervalRate, feature['properties']['intervalRate']) + self.assertEqual(storyframe.description, feature['properties']['description']) + self.assertEqual(storyframe.start_time, feature['properties']['start_time']) + self.assertEqual(storyframe.end_time, feature['properties']['end_time']) + self.assertEqual(storyframe.zoom, feature['properties']['zoom']) + self.assertEqual(eval(storyframe.center), feature['properties']['center']) + self.assertEqual(eval(storyframe.speed), feature['properties']['speed']) + + +class StoryFramesViewUnitTests(TestCase): + def test_storyframes_get(self): + self.assertIsNotNone(storyframes) + + def test_storyframes(self): try: - boxes() + storyframes() self.fail("Expected an exception") except Exception as inst: - self.assertEqual(inst.message, "boxes() takes exactly 2 arguments (0 given)") + self.assertEqual(inst.message, 'storyframes() takes exactly 2 arguments (0 given)') try: - boxes({},{}) + storyframes({}, {}) self.fail("Expected an exception") except Exception as inst: self.assertIsNotNone(inst.message) diff --git a/mapstory/apps/storyframes/urls.py b/mapstory/apps/storyframes/urls.py new file mode 100755 index 000000000..f801725ed --- /dev/null +++ b/mapstory/apps/storyframes/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, url + + +urlpatterns = patterns( + 'mapstory.apps.storyframes.views', + url(r'^$', 'storyframes', name='storyframes'), +) diff --git a/mapstory/apps/boxes/utils.py b/mapstory/apps/storyframes/utils.py similarity index 100% rename from mapstory/apps/boxes/utils.py rename to mapstory/apps/storyframes/utils.py diff --git a/mapstory/apps/boxes/views.py b/mapstory/apps/storyframes/views.py similarity index 77% rename from mapstory/apps/boxes/views.py rename to mapstory/apps/storyframes/views.py index 775121b45..72a9d60bb 100755 --- a/mapstory/apps/boxes/views.py +++ b/mapstory/apps/storyframes/views.py @@ -1,8 +1,8 @@ from django.db import transaction from django.http import HttpResponse -from mapstory.apps.boxes.models import StoryBox -from mapstory.apps.boxes.forms import StoryBoxForm -from mapstory.apps.boxes.utils import unicode_csv_dict_reader +from .models import StoryFrame +from .forms import StoryFrameForm +from .utils import unicode_csv_dict_reader from geonode.utils import resolve_object from mapstory.mapstories.models import Map from geonode.utils import json_response @@ -11,26 +11,26 @@ import json -def _boxes_get(req, mapid): +def _storyframes_get(req, mapid): mapobj = resolve_object(req, Map, {'id': mapid}, permission='base.view_resourcebase') cols = ['title', 'description', 'start_time', 'end_time', 'center', 'speed', 'interval', 'playback', 'playbackRate', 'intervalRate', 'zoom'] - box = StoryBox.objects.filter(map=mapid) - box = box.order_by('start_time', 'end_time', 'title') + storyframe = StoryFrame.objects.filter(map=mapid) + storyframe = storyframe.order_by('start_time', 'end_time', 'title') if bool(req.GET.get('in_map', False)): - box = box.filter(in_map=True) + storyframe = storyframe.filter(in_map=True) if bool(req.GET.get('in_timeline', False)): - box = box.filter(in_timeline=True) + storyframe = storyframe.filter(in_timeline=True) if 'page' in req.GET: page = int(req.GET['page']) page_size = 25 start = page * page_size end = start + page_size - box = box[start:end] + storyframe = storyframe[start:end] if 'csv' in req.GET: response = HttpResponse(mimetype='text/csv') - response['Content-Disposition'] = 'attachment; filename=map-%s-boxes.csv' % mapobj.id + response['Content-Disposition'] = 'attachment; filename=map-%s-storyframes.csv' % mapobj.id response['Content-Encoding'] = 'utf-8' writer = csv.writer(response) writer.writerow(cols) @@ -39,7 +39,7 @@ def _boxes_get(req, mapid): # default csv writer chokes on unicode encode = lambda v: v.encode('utf-8') if isinstance(v, basestring) else str(v) get_value = lambda a, c: getattr(a, c) if c not in ('start_time', 'end_time') else '' - for a in box: + for a in storyframe: vals = [encode(get_value(a, c)) for c in cols] vals[sidx] = a.start_time_str vals[eidx] = a.end_time_str @@ -72,10 +72,10 @@ def encode(query_set): results.append(feature) return results - return json_response({'type':'FeatureCollection','features':encode(box)}) + return json_response({'type':'FeatureCollection','features':encode(storyframe)}) -def _boxes_post(req, mapid): +def _storyframes_post(req, mapid): mapobj = resolve_object(req, Map, {'id':mapid}, permission='base.change_resourcebase') # default action @@ -84,7 +84,7 @@ def _boxes_post(req, mapid): get_props = lambda r: r['properties'] # operation to run on completion finish = lambda: None - # track created boxes + # track created storyframes created = [] # csv or client to account for differences form_mode = 'client' @@ -110,9 +110,9 @@ def id_collector(form): form_mode = 'csv' content_type = 'text/html' get_props = lambda r: r - ids = list(StoryBox.objects.filter(map=mapobj).values_list('id', flat=True)) + ids = list(StoryFrame.objects.filter(map=mapobj).values_list('id', flat=True)) # delete existing, we overwrite - finish = lambda: StoryBox.objects.filter(id__in=ids).delete() + finish = lambda: StoryFrame.objects.filter(id__in=ids).delete() overwrite = True def error_format(row_errors): @@ -124,7 +124,7 @@ def error_format(row_errors): return 'The following rows had problems:" if action == 'delete': - StoryBox.objects.filter(pk__in=data['ids'], map=mapobj).delete() + StoryFrame.objects.filter(pk__in=data['ids'], map=mapobj).delete() return json_response({'success': True}) if action != 'upsert': @@ -132,7 +132,7 @@ def error_format(row_errors): try: with transaction.atomic(): - errors = _try_write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode) + errors = _try_write_storyframes(data, get_props, id_collector, mapobj, overwrite, form_mode) except RuntimeError as e: body = None if error_format: @@ -146,8 +146,8 @@ def error_format(row_errors): return json_response(body=body, errors=errors, content_type=content_type) -def _try_write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode): - errors = _write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode) +def _try_write_storyframes(data, get_props, id_collector, mapobj, overwrite, form_mode): + errors = _write_storyframes(data, get_props, id_collector, mapobj, overwrite, form_mode) if len(errors) > 0: raise RuntimeError @@ -155,22 +155,22 @@ def _try_write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode return errors -def _write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode): +def _write_storyframes(data, get_props, id_collector, mapobj, overwrite, form_mode): i = None errors = [] for i, r in enumerate(data): props = get_props(r) props['map'] = mapobj.id - box = None + storyframe = None id = r.get('id', None) if id and not overwrite: - box = StoryBox.objects.get(map=mapobj, pk=id) + storyframe = StoryFrame.objects.get(map=mapobj, pk=id) # form expects everything in the props, copy geometry in if 'geometry' in r: props['geometry'] = r['geometry'] props.pop('id', None) - form = StoryBoxForm(props, instance=box, form_mode=form_mode) + form = StoryFrameForm(props, instance=storyframe, form_mode=form_mode) if not form.is_valid(): errors.append((i, form.errors)) else: @@ -182,12 +182,12 @@ def _write_boxes(data, get_props, id_collector, mapobj, overwrite, form_mode): return errors -def boxes(req, mapid): - '''management of boxes for a given mapid''' +def storyframes(req, mapid): + '''management of storyframes for a given mapid''' if req.method == 'GET': - return _boxes_get(req, mapid) + return _storyframes_get(req, mapid) if req.method == 'POST': - return _boxes_post(req, mapid) + return _storyframes_post(req, mapid) return HttpResponse(status=400) diff --git a/mapstory/mapstories/models.py b/mapstory/mapstories/models.py index ec03958ce..75c04692e 100644 --- a/mapstory/mapstories/models.py +++ b/mapstory/mapstories/models.py @@ -20,52 +20,52 @@ def get_chapter_info(self): chapter_list = [] for chapter in self.chapters: - annotations = chapter.annotation_set.all() - pin_list = [] - for annotation in annotations: - pin_dict = { - 'title': annotation.title, - 'content': annotation.content, - 'media': annotation.media, - 'the_geom': annotation.the_geom, - 'start_time': annotation.start_time, - 'end_time': annotation.end_time, - 'in_timeline': annotation.in_timeline, - 'in_map': annotation.in_map, - 'appearance': annotation.appearance, - 'auto_show': annotation.auto_show, - 'pause_playback': annotation.pause_playback + storypins = chapter.storypin_set.all() + storypin_list = [] + for storypin in storypins: + storypin_dict = { + 'title': storypin.title, + 'content': storypin.content, + 'media': storypin.media, + 'the_geom': storypin.the_geom, + 'start_time': storypin.start_time, + 'end_time': storypin.end_time, + 'in_timeline': storypin.in_timeline, + 'in_map': storypin.in_map, + 'appearance': storypin.appearance, + 'auto_show': storypin.auto_show, + 'pause_playback': storypin.pause_playback } - pin_list.append(pin_dict) - - boxes = chapter.storybox_set.all() - box_list = [] - for box in boxes: - box_dict = { - 'title': box.title, - 'description': box.description, - 'the_geom': box.the_geom, - 'start_time': box.start_time, - 'end_time': box.end_time, - 'data': box.data, - 'center': box.center, - 'interval': box.interval, - 'intervalRate': box.intervalRate, - 'playback': box.playback, - 'playbackRate': box.playbackRate, - 'speed': box.speed, - 'zoom': box.zoom, - 'layers': box.layers, - 'resolution': box.resolution + storypin_list.append(storypin_dict) + + storyframes = chapter.storyframe_set.all() + storyframe_list = [] + for storyframe in storyframes: + storyframe_dict = { + 'title': storyframe.title, + 'description': storyframe.description, + 'the_geom': storyframe.the_geom, + 'start_time': storyframe.start_time, + 'end_time': storyframe.end_time, + 'data': storyframe.data, + 'center': storyframe.center, + 'interval': storyframe.interval, + 'intervalRate': storyframe.intervalRate, + 'playback': storyframe.playback, + 'playbackRate': storyframe.playbackRate, + 'speed': storyframe.speed, + 'zoom': storyframe.zoom, + 'layers': storyframe.layers, + 'resolution': storyframe.resolution } - box_list.append(box_dict) + storyframe_list.append(storyframe_dict) chapter_dict = { 'title': chapter.title, 'abstract': chapter.abstract, 'layers': chapter.layers, - 'storyframes': box_list, - 'storypins': pin_list + 'storyframes': storyframe_list, + 'storypins': storypin_list } chapter_list.append(chapter_dict) return chapter_list diff --git a/mapstory/settings/base.py b/mapstory/settings/base.py index 166568bef..496c1fbd1 100755 --- a/mapstory/settings/base.py +++ b/mapstory/settings/base.py @@ -88,7 +88,7 @@ 'notification', 'mapstory.apps.health_check_geoserver', 'mapstory.apps.thumbnails', - 'mapstory.annotations', + 'mapstory.storypins', 'mapstory.apps.journal', 'mapstory.apps.favorite', 'mapstory.apps.teams', @@ -112,7 +112,7 @@ MAPSTORY_APPS = ( - 'mapstory.apps.boxes', + 'mapstory.apps.storyframes', 'mapstory.apps.flag', # - temporarily using this instead of the flag app for django because they need to use AUTH_USER_MODEL ) diff --git a/mapstory/apps/boxes/__init__.py b/mapstory/storypins/__init__.py similarity index 100% rename from mapstory/apps/boxes/__init__.py rename to mapstory/storypins/__init__.py diff --git a/mapstory/annotations/admin.py b/mapstory/storypins/admin.py similarity index 57% rename from mapstory/annotations/admin.py rename to mapstory/storypins/admin.py index eed95a897..a4bba7981 100644 --- a/mapstory/annotations/admin.py +++ b/mapstory/storypins/admin.py @@ -1,12 +1,12 @@ from django.contrib import admin -from mapstory.annotations.models import Annotation +from mapstory.storypins.models import StoryPin -class AnnotationAdmin(admin.ModelAdmin): +class StoryPinAdmin(admin.ModelAdmin): list_display = ('id', 'map', 'title') list_filter = ('map', 'in_map', 'in_timeline',) search_fields = ('map__title', 'title', 'content',) -admin.site.register(Annotation, AnnotationAdmin) +admin.site.register(StoryPin, StoryPinAdmin) diff --git a/mapstory/apps/boxes/forms.py b/mapstory/storypins/forms.py similarity index 85% rename from mapstory/apps/boxes/forms.py rename to mapstory/storypins/forms.py index 66edebe0d..211f74923 100755 --- a/mapstory/apps/boxes/forms.py +++ b/mapstory/storypins/forms.py @@ -1,17 +1,18 @@ +import json + from django import forms -from mapstory.apps.boxes.models import StoryBox -from mapstory.apps.boxes.utils import datetime_to_seconds -from mapstory.apps.boxes.utils import make_point -from mapstory.apps.boxes.utils import parse_date_time -import json +from mapstory.storypins.models import StoryPin +from mapstory.storypins.utils import datetime_to_seconds +from mapstory.storypins.utils import make_point +from mapstory.storypins.utils import parse_date_time -class StoryBoxForm(forms.ModelForm): +class StoryPinForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.form_mode = kwargs.pop('form_mode', 'client') - super(StoryBoxForm, self).__init__(*args, **kwargs) + super(StoryPinForm, self).__init__(*args, **kwargs) def parse_float(self, name): val = self.data.get(name, None) @@ -41,7 +42,7 @@ def full_clean(self): self.data['the_geom'] = make_point(lon, lat) self._convert_time('start_time') self._convert_time('end_time') - super(StoryBoxForm, self).full_clean() + super(StoryPinForm, self).full_clean() self._errors.update(self._my_errors) def _convert_time(self, key): @@ -72,5 +73,5 @@ def _convert_time(self, key): self.data[key] = str(numeric) if numeric is not None else None class Meta: - model = StoryBox + model = StoryPin fields = '__all__' diff --git a/mapstory/annotations/migrations/0001_initial.py b/mapstory/storypins/migrations/0001_initial.py similarity index 100% rename from mapstory/annotations/migrations/0001_initial.py rename to mapstory/storypins/migrations/0001_initial.py diff --git a/mapstory/apps/boxes/migrations/__init__.py b/mapstory/storypins/migrations/__init__.py similarity index 100% rename from mapstory/apps/boxes/migrations/__init__.py rename to mapstory/storypins/migrations/__init__.py diff --git a/mapstory/storypins/migrations/rename_annotations_models.py b/mapstory/storypins/migrations/rename_annotations_models.py new file mode 100644 index 000000000..f58fbb2bc --- /dev/null +++ b/mapstory/storypins/migrations/rename_annotations_models.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('storypins', '0001_initial'), + ] + + operations = [ + migrations.RenameModel('Annotation', 'StoryPin') + ] diff --git a/mapstory/annotations/models.py b/mapstory/storypins/models.py similarity index 78% rename from mapstory/annotations/models.py rename to mapstory/storypins/models.py index 9e784bdbf..9eaf8931e 100755 --- a/mapstory/annotations/models.py +++ b/mapstory/storypins/models.py @@ -2,28 +2,28 @@ from datetime import datetime -from mapstory.annotations.utils import parse_date_time +from mapstory.storypins.utils import parse_date_time from mapstory.mapstories.models import Map -class AnnotationManager(models.Manager): +class StoryPinManager(models.Manager): - def copy_map_annotations(self, source_id, target): + def copy_map_storypins(self, source_id, target): source = Map.objects.get(id=source_id) copies = [] - print 'copy from', source_id, source.annotation_set.all() + print 'copy from', source_id, source.storypin_set.all() print 'to target', target.id - for ann in source.annotation_set.all(): + for ann in source.storypin_set.all(): ann.map = target ann.pk = None copies.append(ann) print copies - Annotation.objects.bulk_create(copies) + StoryPin.objects.bulk_create(copies) print 'yeah' -class Annotation(models.Model): - objects = AnnotationManager() +class StoryPin(models.Model): + objects = StoryPinManager() map = models.ForeignKey(Map) title = models.TextField() @@ -58,7 +58,7 @@ def end_time_str(self): def map_copied(sender, source_id, **kw): try: - Annotation.objects.copy_map_annotations(source_id, sender) + StoryPin.objects.copy_map_storypins(source_id, sender) except: print 'dammit jim' import traceback diff --git a/mapstory/annotations/tests.py b/mapstory/storypins/tests.py similarity index 72% rename from mapstory/annotations/tests.py rename to mapstory/storypins/tests.py index 07e089d02..19be7e6b8 100755 --- a/mapstory/annotations/tests.py +++ b/mapstory/storypins/tests.py @@ -8,14 +8,14 @@ from django.test.client import Client from django.test.utils import override_settings -from mapstory.annotations.models import Annotation -from mapstory.annotations.utils import unicode_csv_dict_reader -from mapstory.annotations.utils import make_point +from mapstory.storypins.models import StoryPin +from mapstory.storypins.utils import unicode_csv_dict_reader +from mapstory.storypins.utils import make_point from mapstory.mapstories.models import Map @override_settings(DEBUG=True) -class AnnotationsTest(TransactionTestCase): +class StoryPinsTest(TransactionTestCase): fixtures = ['initial_data.json', 'map_data.json', 'sample_admin.json'] c = Client() @@ -31,8 +31,8 @@ def create_user(self, username, password, **kwargs): return username, password - def get_x(self, annotation): - x_coord = int(json.loads(annotation.the_geom)['coordinates'][0]) + def get_x(self, storypin): + x_coord = int(json.loads(storypin.the_geom)['coordinates'][0]) return x_coord def setUp(self): @@ -51,7 +51,7 @@ def setUp(self): self.dummy = dummy self.admin_map = admin_map - def make_annotations(self, mapobj, cnt=100): + def make_storypins(self, mapobj, cnt=100): point = make_point(5, 23) end_time = 9999999999 for a in range(cnt): @@ -60,12 +60,12 @@ def make_annotations(self, mapobj, cnt=100): # make sure some geometries are missing geom = point if cnt % 2 == 0 else None # make the names sort nicely by title/number - Annotation.objects.create(title='ann%2d' % a, map=mapobj, + StoryPin.objects.create(title='ann%2d' % a, map=mapobj, start_time=start_time, end_time=end_time, the_geom=geom).save() - def test_copy_annotations(self): - self.make_annotations(self.dummy) + def test_copy_storypins(self): + self.make_storypins(self.dummy) admin_map = Map.objects.create(owner=self.admin, zoom=1, center_x=0, center_y=0, title='map2') # have to use a 'dummy' map to create the appropriate JSON @@ -73,28 +73,28 @@ def test_copy_annotations(self): target.id = None # let Django auto-gen a good PK target.save() - Annotation.objects.copy_map_annotations(self.dummy.id, target) + StoryPin.objects.copy_map_storypins(self.dummy.id, target) # make sure we have 100 and we can resolve the corresponding copies - self.assertEqual(100, target.annotation_set.count()) - for a in self.dummy.annotation_set.all(): - self.assertTrue(target.annotation_set.get(title=a.title)) + self.assertEqual(100, target.storypin_set.count()) + for a in self.dummy.storypin_set.all(): + self.assertTrue(target.storypin_set.get(title=a.title)) def test_get(self): - '''make 100 annotations and get them all as well as paging through''' - self.make_annotations(self.dummy) + '''make 100 storypins and get them all as well as paging through''' + self.make_storypins(self.dummy) - response = self.c.get(reverse('annotations', args=[self.dummy.id])) + response = self.c.get(reverse('storypins', args=[self.dummy.id])) rows = json.loads(response.content)['features'] self.assertEqual(100, len(rows)) for p in range(4): - response = self.c.get(reverse('annotations', args=[self.dummy.id]) + "?page=%s" % p) + response = self.c.get(reverse('storypins', args=[self.dummy.id]) + "?page=%s" % p) rows = json.loads(response.content)['features'] self.assertEqual(25, len(rows)) titles = [row['properties']['title'] for row in rows] # check the first title on each page - # because make_annotations() creates a set sorted by start_time, + # because make_storypins() creates a set sorted by start_time, # these should be: (ann 0, ann25, ann50, ann75) self.assertEqual('ann%2d' % (p * 25), titles[0]) @@ -102,8 +102,8 @@ def test_post(self): '''test post operations''' # make 1 and update it - self.make_annotations(self.dummy, 1) - ann = Annotation.objects.filter(map=self.dummy)[0] + self.make_storypins(self.dummy, 1) + ann = StoryPin.objects.filter(map=self.dummy)[0] data = json.dumps({ 'features': [{ 'geometry': {'type': 'Point', 'coordinates': [5.000000, 23.000000]}, @@ -116,14 +116,14 @@ def test_post(self): }] }) # without login, expect failure - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), data, "application/json") + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), data, "application/json") self.assertEqual(403, resp.status_code) # login and verify change accepted self.c.login(username="test_admin", password="test_admin") - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), data, "application/json") + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), data, "application/json") self.assertEqual(200, resp.status_code) - ann = Annotation.objects.get(id=ann.id) + ann = StoryPin.objects.get(id=ann.id) self.assertEqual(ann.title, "new title") self.assertEqual(self.get_x(ann), 5) self.assertEqual(ann.end_time, 1371136048) @@ -138,36 +138,36 @@ def test_post(self): } }] }) - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), data, "application/json") + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), data, "application/json") self.assertEqual(200, resp.status_code) resp = json.loads(resp.content) self.assertEqual(resp['success'], True) - ann = Annotation.objects.get(id=ann.id + 1) + ann = StoryPin.objects.get(id=ann.id + 1) self.assertEqual(ann.title, "new ann") def test_delete(self): '''test delete operations''' - # make 10 annotations, drop numbers 4-7 - self.make_annotations(self.dummy, 10) - current_anns = Annotation.objects.filter(map=self.dummy) + # make 10 storypins, drop numbers 4-7 + self.make_storypins(self.dummy, 10) + current_anns = StoryPin.objects.filter(map=self.dummy) titles_to_delete = [u'ann 4', u'ann 5', u'ann 6', u'ann 7'] ids_to_delete = [ann.id for ann in current_anns if ann.title in titles_to_delete] data = json.dumps({'action': 'delete', 'ids': ids_to_delete}) # verify failure before login - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), data, "application/json") + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), data, "application/json") self.assertEqual(403, resp.status_code) # now check success self.c.login(username="test_admin", password="test_admin") - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), data, "application/json") + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), data, "application/json") self.assertEqual(200, resp.status_code) # these are gone - ann = Annotation.objects.filter(id__in=ids_to_delete) + ann = StoryPin.objects.filter(id__in=ids_to_delete) self.assertEqual(0, ann.count()) # six remain - ann = Annotation.objects.filter(map=self.dummy) + ann = StoryPin.objects.filter(map=self.dummy) self.assertEqual(6, ann.count()) def test_csv_upload(self): @@ -175,7 +175,7 @@ def test_csv_upload(self): # @todo cleanup and break out into simpler cases - self.make_annotations(self.dummy, 2) + self.make_storypins(self.dummy, 2) header = u"id,title,content,lat,lon,start_time,end_time,appearance\n" @@ -190,34 +190,34 @@ def test_csv_upload(self): fp.seek(0) # verify failure before login - resp = self.c.post(reverse('annotations', args=[self.dummy.id]),{'csv':fp}) + resp = self.c.post(reverse('storypins', args=[self.dummy.id]),{'csv':fp}) self.assertEqual(403, resp.status_code) - # still only 2 annotations - self.assertEqual(2, Annotation.objects.filter(map=self.dummy.id).count()) + # still only 2 storypins + self.assertEqual(2, StoryPin.objects.filter(map=self.dummy.id).count()) # login, rewind the buffer and verify self.c.login(username="test_admin", password="test_admin") fp.seek(0) - resp = self.c.post(reverse('annotations', args=[self.dummy.id]),{'csv':fp}) + resp = self.c.post(reverse('storypins', args=[self.dummy.id]),{'csv':fp}) self.assertEqual(200, resp.status_code) # response type must be text/html for ext fileupload self.assertEqual('text/html', resp['content-type']) jsresp = json.loads(resp.content) self.assertEqual(True, jsresp['success']) - ann = Annotation.objects.filter(map=self.dummy.id) + ann = StoryPin.objects.filter(map=self.dummy.id) # we uploaded 3, the other 2 should be deleted (overwrite mode) self.assertEqual(3, ann.count()) - ann = Annotation.objects.get(title='bar foo') + ann = StoryPin.objects.get(title='bar foo') self.assertEqual(self.get_x(ann), 20.) - ann = Annotation.objects.get(title='bunk') + ann = StoryPin.objects.get(title='bunk') self.assertTrue(u'\u201c', ann.content) - ann = Annotation.objects.get(title='foo bar') + ann = StoryPin.objects.get(title='foo bar') self.assertEqual('foo bar', ann.title) self.assertEqual(self.get_x(ann), 10.) - resp = self.c.get(reverse('annotations',args=[self.dummy.id]) + "?csv") + resp = self.c.get(reverse('storypins',args=[self.dummy.id]) + "?csv") # verify each row has the same number of fields, even if some fields # are empty for l in resp.content.split('\r\n'): @@ -240,8 +240,8 @@ def test_csv_upload(self): ',\x93windows quotes\x94,yay,,,,' )) fp.seek(0) - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), {'csv': fp}) - ann = Annotation.objects.get(map=self.dummy.id) + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), {'csv': fp}) + ann = StoryPin.objects.get(map=self.dummy.id) # windows quotes are unicode now self.assertEqual(u'\u201cwindows quotes\u201d', ann.title) @@ -251,10 +251,10 @@ def test_csv_upload(self): str(header) * 2 )) fp.seek(0) - resp = self.c.post(reverse('annotations', args=[self.dummy.id]), {'csv': fp}) + resp = self.c.post(reverse('storypins', args=[self.dummy.id]), {'csv': fp}) self.assertEqual(400, resp.status_code) # there should only be one that we uploaded before - Annotation.objects.get(map=self.dummy.id) + StoryPin.objects.get(map=self.dummy.id) self.assertEqual('yay', ann.content) # and check for the errors related to the invalid data we sent diff --git a/mapstory/annotations/urls.py b/mapstory/storypins/urls.py similarity index 88% rename from mapstory/annotations/urls.py rename to mapstory/storypins/urls.py index 960f10247..0fac496f4 100755 --- a/mapstory/annotations/urls.py +++ b/mapstory/storypins/urls.py @@ -22,6 +22,6 @@ urlpatterns = patterns( - 'mapstory.annotations.views', - url(r'^maps/(?P\d+)/annotations$', 'annotations', name='annotations'), + 'mapstory.storypins.views', + url(r'^maps/(?P\d+)/storypins$', 'storypins', name='storypins'), ) diff --git a/mapstory/annotations/utils.py b/mapstory/storypins/utils.py similarity index 100% rename from mapstory/annotations/utils.py rename to mapstory/storypins/utils.py diff --git a/mapstory/annotations/views.py b/mapstory/storypins/views.py similarity index 82% rename from mapstory/annotations/views.py rename to mapstory/storypins/views.py index 7c85fb1ed..37d632037 100755 --- a/mapstory/annotations/views.py +++ b/mapstory/storypins/views.py @@ -7,16 +7,16 @@ from geonode.utils import json_response from geonode.utils import resolve_object -from mapstory.annotations.forms import AnnotationForm -from mapstory.annotations.models import Annotation -from mapstory.annotations.utils import unicode_csv_dict_reader +from mapstory.storypins.forms import StoryPinForm +from mapstory.storypins.models import StoryPin +from mapstory.storypins.utils import unicode_csv_dict_reader from mapstory.mapstories.models import Map -def _annotations_get(req, mapid): +def _storypins_get(req, mapid): mapobj = resolve_object(req, Map, {'id': mapid}, permission='base.view_resourcebase') cols = ['title', 'content', 'media', 'start_time', 'end_time', 'in_map', 'in_timeline', 'appearance', 'auto_show', 'pause_playback'] - ann = Annotation.objects.filter(map=mapid) + ann = StoryPin.objects.filter(map=mapid) ann = ann.order_by('start_time', 'end_time', 'title') if bool(req.GET.get('in_map', False)): ann = ann.filter(in_map=True) @@ -31,7 +31,7 @@ def _annotations_get(req, mapid): if 'csv' in req.GET: response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=map-%s-annotations.csv' % mapobj.id + response['Content-Disposition'] = 'attachment; filename=map-%s-storypins.csv' % mapobj.id response['Content-Encoding'] = 'utf-8' writer = csv.writer(response) writer.writerow(cols) @@ -68,7 +68,7 @@ def encode(query_set): return json_response({'type':'FeatureCollection','features':encode(ann)}) -def _annotations_post(req, mapid): +def _storypins_post(req, mapid): mapobj = resolve_object(req, Map, {'id':mapid}, permission='base.change_resourcebase') # default action @@ -77,7 +77,7 @@ def _annotations_post(req, mapid): get_props = lambda r: r['properties'] # operation to run on completion finish = lambda: None - # track created annotations + # track created storypins created = [] # csv or client to account for differences form_mode = 'client' @@ -103,9 +103,9 @@ def id_collector(form): form_mode = 'csv' content_type = 'text/html' get_props = lambda r: r - ids = list(Annotation.objects.filter(map=mapobj).values_list('id', flat=True)) + ids = list(StoryPin.objects.filter(map=mapobj).values_list('id', flat=True)) # delete existing, we overwrite - finish = lambda: Annotation.objects.filter(id__in=ids).delete() + finish = lambda: StoryPin.objects.filter(id__in=ids).delete() overwrite = True def error_format(row_errors): @@ -117,13 +117,13 @@ def error_format(row_errors): return 'The following rows had problems:" if action == 'delete': - Annotation.objects.filter(pk__in=data['ids'], map=mapobj).delete() + StoryPin.objects.filter(pk__in=data['ids'], map=mapobj).delete() return json_response({'success': True}) if action != 'upsert': return HttpResponse('%s not supported' % action, status=400) - errors = _write_annotations(data, get_props, id_collector, mapobj, overwrite, form_mode) + errors = _write_storypins(data, get_props, id_collector, mapobj, overwrite, form_mode) if errors: transaction.rollback() @@ -140,7 +140,7 @@ def error_format(row_errors): return json_response(body=body, errors=errors, content_type=content_type) -def _write_annotations(data, get_props, id_collector, mapobj, overwrite, form_mode): +def _write_storypins(data, get_props, id_collector, mapobj, overwrite, form_mode): i = None errors = [] for i, r in enumerate(data): @@ -149,13 +149,13 @@ def _write_annotations(data, get_props, id_collector, mapobj, overwrite, form_mo ann = None id = r.get('id', None) if id and not overwrite: - ann = Annotation.objects.get(map=mapobj, pk=id) + ann = StoryPin.objects.get(map=mapobj, pk=id) # form expects everything in the props, copy geometry in if 'geometry' in r: props['geometry'] = r['geometry'] props.pop('id', None) - form = AnnotationForm(props, instance=ann, form_mode=form_mode) + form = StoryPinForm(props, instance=ann, form_mode=form_mode) if not form.is_valid(): errors.append((i, form.errors)) else: @@ -167,11 +167,11 @@ def _write_annotations(data, get_props, id_collector, mapobj, overwrite, form_mo return errors -def annotations(req, mapid): - '''management of annotations for a given mapid''' +def storypins(req, mapid): + '''management of storypins for a given mapid''' if req.method == 'GET': - return _annotations_get(req, mapid) + return _storypins_get(req, mapid) if req.method == 'POST': - return _annotations_post(req, mapid) + return _storypins_post(req, mapid) return HttpResponse(status=400) diff --git a/mapstory/templates/viewer/story_viewer.js b/mapstory/templates/viewer/story_viewer.js index 1e775ea9b..1c10ac326 100644 --- a/mapstory/templates/viewer/story_viewer.js +++ b/mapstory/templates/viewer/story_viewer.js @@ -151,9 +151,9 @@ if (options.id) { stStoryMapBuilder.modifyStoryMap(self.storyMap, options); - var annotationsLoad = $http.get("/maps/" + options.id + "/annotations"); - var boxesLoad = $http.get("/maps/" + options.id + "/boxes"); - $q.all([annotationsLoad, boxesLoad]).then(function(values) { + var storypinsLoad = $http.get("/maps/" + options.id + "/storypins"); + var storyframesLoad = $http.get("/maps/" + options.id + "/storyframes"); + $q.all([storypinsLoad, storyframesLoad]).then(function(values) { StoryPinLayerManager.loadFromGeoJSON(values[0].data, self.storyMap.getMap().getView().getProjection(), true); StoryBoxLayerManager.loadFromGeoJSON(values[1].data, self.storyMap.getMap().getView().getProjection(), true); }); @@ -246,7 +246,7 @@ module.controller('viewerController', function($scope, $location, $injector, $lo $scope.timeControlsManager = $injector.instantiate(TimeControlsManager); $scope.mapManager = MapManager; - var values = {annotations: [], boxes: [], data: []}; + var values = {storypins: [], storyframes: [], data: []}; $scope.nextChapter = function(){ var nextChapter = Number(MapManager.storyChapter) + 1; diff --git a/mapstory/urls.py b/mapstory/urls.py index aefb73bff..89735042b 100755 --- a/mapstory/urls.py +++ b/mapstory/urls.py @@ -7,7 +7,7 @@ from django.views.generic import RedirectView from django.views.generic import TemplateView -from annotations.urls import urlpatterns as annotations_urls +from storypins.urls import urlpatterns as storypins_urls # to replace /api/base & /api/owners GeoNode routes with our own: # unregister old routes before geonode.urls.urlpatterns is imported from geonode.api.urls import api as geonode_api @@ -82,7 +82,7 @@ url(r'^blog/comments/', include('fluent_comments.urls')), # Maps - url(r'^maps/(?P\d+)/boxes$', include('mapstory.apps.boxes.urls')), + url(r'^maps/(?P\d+)/storyframes$', include('mapstory.apps.storyframes.urls')), url(r'^maps/new/data$', 'mapstory.views.new_map_json', name='new_map_json'), url(r'^maps/new_map', new_map, name='new_map'), url(r'^maps/(?P[^/]+)/save$', 'mapstory.views.save_story', name='save_story'), @@ -146,7 +146,7 @@ url(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', content_type="text/plain"), name='robots'), ) + geonode_layers_urlpatterns + layer_detail_patterns + urlpatterns -urlpatterns += annotations_urls +urlpatterns += storypins_urls urlpatterns += maploom_urls diff --git a/test.sh b/test.sh index 95ab60536..7087d04d8 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash -coverage run --branch --source=mapstory.apps ./manage.py test # && \ +coverage run --branch --source=mapstory ./manage.py test # && \ coverage report # && \ coverage html -d cover