From d782048e83a850f7f5cb37db974904eb4709c0cc Mon Sep 17 00:00:00 2001 From: Rizky Maulana Nugraha Date: Fri, 10 Feb 2017 06:36:13 +0700 Subject: [PATCH] Additional features and functionality: based on issue inasafe/inasafe-realtime#35 Celery: - Limit prefetch multiplier explicitly to 1 (avoid hanging worker) Earthquake: - Add MMI Output FileField - Add download button for MMI Output zip - Auto zoom to first earthquake in the table Flood: - Fix signals to recalculate total affected population and flooded RW - Add Data Source Field, since it needs to support PetaBencana - Add code branching to handle old hazard data and new PetaBencana - Add download button for Hazard File and Impact Layers - Change download button icon to follow Earthquake pattern - Auto zoom to first flood in the table Ash: - Add Impact Files FileField - Add download button for Impact Files zip - Change download button icon to follow Earthquake pattern - Auto zoom to first ash event in the table --- django_project/core/settings/celery_config.py | 1 + .../migrations/0026_auto_20170209_1905.py | 32 +++++ .../migrations/0027_flood_data_source.py | 20 +++ django_project/realtime/models/ash.py | 9 ++ django_project/realtime/models/earthquake.py | 8 ++ django_project/realtime/models/flood.py | 7 + .../realtime/serializers/ash_serializer.py | 21 +++ .../serializers/earthquake_serializer.py | 2 + .../realtime/serializers/flood_serializer.py | 1 + django_project/realtime/signals/flood.py | 2 +- .../realtime/static/realtime/css/realtime.css | 10 +- .../realtime/static/realtime/js/ash/ash.js | 125 +++++++++++++++--- .../static/realtime/js/earthquake/shake.js | 69 ++++++++-- .../static/realtime/js/flood/flood.js | 112 +++++++++++++--- .../realtime/static/realtime/js/realtime.js | 3 +- .../js/templates/ash/popup_content.jst | 38 +++++- .../js/templates/earthquake/popup_content.jst | 44 ++++-- django_project/realtime/tasks/flood.py | 30 +++-- .../templates/realtime/ash/index.html | 36 ++++- .../templates/realtime/earthquake/index.html | 36 +++-- .../templates/realtime/flood/index.html | 38 ++++-- django_project/realtime/views/ash.py | 18 ++- django_project/realtime/views/earthquake.py | 16 +-- django_project/realtime/views/flood.py | 6 +- 24 files changed, 563 insertions(+), 121 deletions(-) create mode 100644 django_project/realtime/migrations/0026_auto_20170209_1905.py create mode 100644 django_project/realtime/migrations/0027_flood_data_source.py diff --git a/django_project/core/settings/celery_config.py b/django_project/core/settings/celery_config.py index 87dec8f9..954840c4 100644 --- a/django_project/core/settings/celery_config.py +++ b/django_project/core/settings/celery_config.py @@ -22,6 +22,7 @@ CELERY_DEFAULT_ROUTING_KEY = "default" CELERY_CREATE_MISSING_QUEUES = True CELERYD_CONCURRENCY = 1 +CELERYD_PREFETCH_MULTIPLIER = 1 CELERY_QUEUES = [ Queue('default', routing_key='default'), diff --git a/django_project/realtime/migrations/0026_auto_20170209_1905.py b/django_project/realtime/migrations/0026_auto_20170209_1905.py new file mode 100644 index 00000000..eb282059 --- /dev/null +++ b/django_project/realtime/migrations/0026_auto_20170209_1905.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('realtime', '0025_auto_20170206_2046'), + ] + + operations = [ + migrations.AddField( + model_name='ash', + name='impact_files', + field=models.FileField(help_text=b'Impact files processed zipped', upload_to=b'ash/impact_files/%Y/%m/%d', null=True, verbose_name=b'Impact Files', blank=True), + preserve_default=True, + ), + migrations.AddField( + model_name='earthquake', + name='mmi_output', + field=models.FileField(help_text=b'MMI related file, layers, and data, zipped.', upload_to=b'earthquake/mmi_output', null=True, verbose_name=b'MMI related file zipped', blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='ash', + name='eruption_height', + field=models.IntegerField(default=0, verbose_name=b'Eruption height in metres'), + preserve_default=True, + ), + ] diff --git a/django_project/realtime/migrations/0027_flood_data_source.py b/django_project/realtime/migrations/0027_flood_data_source.py new file mode 100644 index 00000000..ab094f98 --- /dev/null +++ b/django_project/realtime/migrations/0027_flood_data_source.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('realtime', '0026_auto_20170209_1905'), + ] + + operations = [ + migrations.AddField( + model_name='flood', + name='data_source', + field=models.CharField(default=None, max_length=255, blank=True, help_text=b'The source of the hazard data used for analysis', null=True, verbose_name=b'The source of hazard data'), + preserve_default=True, + ), + ] diff --git a/django_project/realtime/models/ash.py b/django_project/realtime/models/ash.py index bd64d134..082fbc13 100644 --- a/django_project/realtime/models/ash.py +++ b/django_project/realtime/models/ash.py @@ -35,6 +35,13 @@ class Meta: upload_to='ash/hazard_file/%Y/%m/%d', blank=False ) + impact_files = models.FileField( + verbose_name='Impact Files', + help_text='Impact files processed zipped', + upload_to='ash/impact_files/%Y/%m/%d', + blank=True, + null=True + ) event_time = models.DateTimeField( verbose_name='Event Date and Time', help_text='The time the ash happened.', @@ -67,6 +74,8 @@ def delete(self, using=None): # delete all report if self.hazard_file: self.hazard_file.delete() + if self.impact_files: + self.impact_files.delete() return super(Ash, self).delete(using=using) diff --git a/django_project/realtime/models/earthquake.py b/django_project/realtime/models/earthquake.py index 6a0eeab4..28876eea 100644 --- a/django_project/realtime/models/earthquake.py +++ b/django_project/realtime/models/earthquake.py @@ -22,6 +22,12 @@ class Meta: upload_to='earthquake/grid', blank=True, null=True) + mmi_output = models.FileField( + verbose_name='MMI related file zipped', + help_text='MMI related file, layers, and data, zipped.', + upload_to='earthquake/mmi_output', + blank=True, + null=True) magnitude = models.FloatField( verbose_name='The magnitude', help_text='The magnitude of the event.') @@ -61,6 +67,8 @@ def delete(self, using=None): # delete all report if self.shake_grid: self.shake_grid.delete() + if self.mmi_output: + self.mmi_output.delete() for report in self.reports.all(): report.delete(using=using) super(Earthquake, self).delete(using=using) diff --git a/django_project/realtime/models/flood.py b/django_project/realtime/models/flood.py index da5ffc48..348964a7 100644 --- a/django_project/realtime/models/flood.py +++ b/django_project/realtime/models/flood.py @@ -81,6 +81,13 @@ class Meta: max_length=20, unique=True, blank=False) + data_source = models.CharField( + verbose_name='The source of hazard data', + help_text='The source of the hazard data used for analysis', + max_length=255, + blank=True, + null=True, + default=None) time = models.DateTimeField( verbose_name='Date and Time', help_text='The time the flood reported.', diff --git a/django_project/realtime/serializers/ash_serializer.py b/django_project/realtime/serializers/ash_serializer.py index 6b1c875b..4705f86e 100644 --- a/django_project/realtime/serializers/ash_serializer.py +++ b/django_project/realtime/serializers/ash_serializer.py @@ -117,6 +117,8 @@ class Meta: 'url', 'id', 'volcano', + 'hazard_file', + 'impact_files', 'reports', 'event_time', 'task_status', @@ -132,13 +134,32 @@ class AshGeoJsonSerializer(GeoFeatureModelSerializer): def get_location(self, obj): return obj.volcano.location + def get_event_id_formatted(self, serializer_field, obj): + """ + :param serializer_field: + :type serializer_field: CustomSerializerMethodField + :param obj: + :type obj: Ash + :return: + """ + dateformat = '%Y%m%d%H%M%S%z' + return '%s-%s' % ( + obj.event_time.strftime(dateformat), + obj.volcano.volcano_name) + + # auto bind to get_url method + event_id_formatted = CustomSerializerMethodField() + class Meta: model = Ash geo_field = 'location' id = 'id', fields = ( 'id', + 'event_id_formatted', 'volcano', + 'hazard_file', + 'impact_files', 'event_time', 'alert_level', 'task_status', diff --git a/django_project/realtime/serializers/earthquake_serializer.py b/django_project/realtime/serializers/earthquake_serializer.py index ee2d910d..9adad04a 100644 --- a/django_project/realtime/serializers/earthquake_serializer.py +++ b/django_project/realtime/serializers/earthquake_serializer.py @@ -106,6 +106,7 @@ class Meta: 'url', 'shake_id', 'shake_grid', + 'mmi_output', 'magnitude', 'time', 'depth', @@ -125,6 +126,7 @@ class Meta: fields = ( 'shake_id', 'shake_grid', + 'mmi_output', 'magnitude', 'time', 'depth', diff --git a/django_project/realtime/serializers/flood_serializer.py b/django_project/realtime/serializers/flood_serializer.py index 45a5252b..edce937a 100644 --- a/django_project/realtime/serializers/flood_serializer.py +++ b/django_project/realtime/serializers/flood_serializer.py @@ -139,6 +139,7 @@ class Meta: fields = ( 'url', 'event_id', + 'data_source', 'event_id_formatted', 'time', 'time_description', diff --git a/django_project/realtime/signals/flood.py b/django_project/realtime/signals/flood.py index 389cab57..34fc3c73 100644 --- a/django_project/realtime/signals/flood.py +++ b/django_project/realtime/signals/flood.py @@ -38,6 +38,6 @@ def flood_post_save( chain( process_hazard_layer.si(instance), process_impact_layer.si(instance), - recalculate_impact_info(instance))() + recalculate_impact_info.si(instance))() except Exception as e: LOGGER.exception(e) diff --git a/django_project/realtime/static/realtime/css/realtime.css b/django_project/realtime/static/realtime/css/realtime.css index 47017d54..0044d339 100644 --- a/django_project/realtime/static/realtime/css/realtime.css +++ b/django_project/realtime/static/realtime/css/realtime.css @@ -263,4 +263,12 @@ th.dynatable-head a, th.dynatable-head a:hover { /* ******************** */ .timepicker-picker td.separator{ line-height: 54px; -} \ No newline at end of file +} + + +/* ******************** */ +/* Dropdown in table */ +/* ******************** */ +.btn-group .dropdown-menu{ + position: absolute; +} diff --git a/django_project/realtime/static/realtime/js/ash/ash.js b/django_project/realtime/static/realtime/js/ash/ash.js index bece2b3d..93c38b47 100644 --- a/django_project/realtime/static/realtime/js/ash/ash.js +++ b/django_project/realtime/static/realtime/js/ash/ash.js @@ -106,15 +106,15 @@ function createShowEventHandler(map, markers, map_events) { /** * Closure to create handler for showReport - * use magic number 000 for url placeholder + * use magic number for url placeholder * - * @param {string} report_url A report url that contains shake_id placeholder - * @return {function} Open the report based on shake_id in a new tab + * @param {string} report_url A report url that contains event_id placeholder + * @return {function} Open the report based on event_id in a new tab */ function createShowReportHandler(report_url) { var showReportHandler = function (id) { var url = report_url; - // replace magic number 000 with shake_id + // replace magic number function createFindWithId(id){ return function (event) { return event.properties.id == id; @@ -171,6 +171,54 @@ function createShowReportHandler(report_url) { return showReportHandler; } +/** + * Closure to create handler for downloadReport + * use magic number for url placeholder + * + * @param {string} report_url A report url that contains event_id placeholder + * @return {function} Open the report based on event_id in a new tab + */ +function createDownloadReportHandler(report_url) { + var downloadReportHandler = function (id) { + var url = report_url; + // replace magic number 000 with shake_id + function createFindWithId(id){ + return function (event) { + return event.properties.id == id; + } + } + var findWithId = createFindWithId(id); + var feature = event_json.features.find(findWithId); + var volcano_name = feature.properties.volcano.volcano_name; + var event_time = feature.properties.event_time; + var event_time_string = moment(event_time).format('YYYYMMDDHHmmssZZ'); + var event_id_formatted = feature.properties.event_id_formatted; + var task_status = feature.properties.task_status; + if(task_status == 'PENDING'){ + alert("Report is currently being generated. Refresh this page later."); + return; + } + else if(task_status == 'FAILED'){ + alert("Report failed to generate."); + return; + } + url = url.replace('VOLCANOTEMPLATENAME', volcano_name) + .replace('1234567890123456789', event_time_string); + $.get(url, function (data) { + if (data && data.report_map) { + var pdf_url = data.report_map; + SaveToDisk(pdf_url, event_id_formatted+'-'+data.language+'.pdf'); + } + }).fail(function(e){ + console.log(e); + if(e.status == 404){ + alert("No Report recorded for this event."); + } + }); + }; + return downloadReportHandler; +} + /** * Create Action Writer based on button_templates @@ -200,17 +248,64 @@ function createActionRowWriter(button_templates, date_format) { var $span = $(''); for (var i = 0; i < button_templates.length; i++) { var button = button_templates[i]; - var $inner_button = $(''); - $inner_button.addClass('row-action-icon') - $inner_button.addClass(button.css_class); - $inner_button.attr('title', button.name); - $inner_button.text(button.label); - var $button = $(''); - $button.addClass('btn btn-primary row-action-container'); - $button.attr('title', button.name); - $button.attr('onclick', button.handler + "('" + record.id + "')"); - $button.append($inner_button); - $span.append($button); + if(button.type == 'simple-button') { + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr('title', button.name); + var $button = $(''); + $button.addClass('btn btn-primary row-action-container'); + $button.attr('title', button.name); + $button.attr('onclick', button.handler + "('" + record.id + "')"); + $button.append($inner_button); + $span.append($button); + } + else if(button.type == 'dropdown'){ + var $button = $(''); + $button.addClass('btn btn-primary dropdown-toggle row-action-container'); + $button.attr('title', button.name); + $button.attr('data-toggle', 'dropdown'); + $button.attr('aria-haspopup', 'true'); + $button.attr('aria-expanded', 'false'); + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr(button.name); + $button.append($inner_button); + var $menu = $(''); + $menu.addClass('dropdown-menu'); + for(var j=0;j < button.actions.length;j++){ + var action = button.actions[j]; + if(action.active && $.isFunction(action.active) && !action.active(record)){ + continue; + } + var $li = $('
  • '); + var $action = $(''); + if(action.href == undefined){ + $action.attr('href', '#'); + } + else if($.isFunction(action.href)){ + $action.attr('href', action.href(record)); + } + + if(action.download && $.isFunction(action.download)){ + $action.attr('download', action.download(record)); + } + + if(action.handler){ + $action.attr('onclick', action.handler + "('" + record.id + "')"); + } + + $action.text(action.text); + $li.append($action); + $menu.append($li); + } + var $group = $('
    '); + $group.addClass('btn-group'); + $group.append($button); + $group.append($menu); + $span.append($group); + } } tr += '' + $span.html() + ''; diff --git a/django_project/realtime/static/realtime/js/earthquake/shake.js b/django_project/realtime/static/realtime/js/earthquake/shake.js index 2e3f9465..a82a6c80 100644 --- a/django_project/realtime/static/realtime/js/earthquake/shake.js +++ b/django_project/realtime/static/realtime/js/earthquake/shake.js @@ -464,17 +464,64 @@ function createActionRowWriter(button_templates, date_format) { var $span = $(''); for (var i = 0; i < button_templates.length; i++) { var button = button_templates[i]; - var $inner_button = $(''); - $inner_button.addClass('row-action-icon') - $inner_button.addClass(button.css_class); - $inner_button.attr('title', button.name); - $inner_button.text(button.label); - var $button = $(''); - $button.addClass('btn btn-primary row-action-container'); - $button.attr('title', button.name); - $button.attr('onclick', button.handler + "('" + record.shake_id + "')"); - $button.append($inner_button); - $span.append($button); + if(button.type == 'simple-button') { + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr('title', button.name); + var $button = $(''); + $button.addClass('btn btn-primary row-action-container'); + $button.attr('title', button.name); + $button.attr('onclick', button.handler + "('" + record.shake_id + "')"); + $button.append($inner_button); + $span.append($button); + } + else if(button.type == 'dropdown'){ + var $button = $(''); + $button.addClass('btn btn-primary dropdown-toggle row-action-container'); + $button.attr('title', button.name); + $button.attr('data-toggle', 'dropdown'); + $button.attr('aria-haspopup', 'true'); + $button.attr('aria-expanded', 'false'); + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr(button.name); + $button.append($inner_button); + var $menu = $(''); + $menu.addClass('dropdown-menu'); + for(var j=0;j < button.actions.length;j++){ + var action = button.actions[j]; + if(action.active && $.isFunction(action.active) && !action.active(record)){ + continue; + } + var $li = $('
  • '); + var $action = $(''); + if(action.href == undefined){ + $action.attr('href', '#'); + } + else if($.isFunction(action.href)){ + $action.attr('href', action.href(record)); + } + + if(action.download && $.isFunction(action.download)){ + $action.attr('download', action.download(record)); + } + + if(action.handler){ + $action.attr('onclick', action.handler + "('" + record.shake_id + "')"); + } + + $action.text(action.text); + $li.append($action); + $menu.append($li); + } + var $group = $('
    '); + $group.addClass('btn-group'); + $group.append($button); + $group.append($menu); + $span.append($group); + } } tr += '' + $span.html() + ''; diff --git a/django_project/realtime/static/realtime/js/flood/flood.js b/django_project/realtime/static/realtime/js/flood/flood.js index 67451239..b912adb9 100644 --- a/django_project/realtime/static/realtime/js/flood/flood.js +++ b/django_project/realtime/static/realtime/js/flood/flood.js @@ -166,7 +166,6 @@ function createShowFeaturesHandler(event_features_url){ // Deselect row in table var $table = $("#realtime-table"); - console.log($table); $table.find("tr.success").removeClass('success'); // Select row $table.find("td:contains("+event_id+")").closest('tr').addClass('success'); @@ -252,17 +251,17 @@ function createShowImpactMapHandler(report_url) { /** * Closure to create handler for downloadReport * @param {string} report_url A report url that contains shake_id placeholder - * @return {function} Download the report based on shake_id + * @return {function} Download the impact map based on event_id */ -function createDownloadReportHandler(report_url) { - var downloadReportHandler = function (shake_id) { +function createDownloadImpactMapHandler(report_url) { + var downloadImpactMapHandler = function (event_id) { var url = report_url; - // replace magic number 000 with shake_id - url = url.replace('000', shake_id); + // replace magic number 0000000000-6-rw with event_id + url = url.replace('0000000000-6-rw', event_id); $.get(url, function (data) { - if (data && data.report_pdf) { - var pdf_url = data.report_pdf; - SaveToDisk(pdf_url, data.shake_id+'-'+data.language+'.pdf'); + if (data && data.impact_map) { + var pdf_url = data.impact_map; + SaveToDisk(pdf_url, data.event_id+'-'+data.language+'.pdf'); } }).fail(function(e){ console.log(e); @@ -271,7 +270,32 @@ function createDownloadReportHandler(report_url) { } }); }; - return downloadReportHandler; + return downloadImpactMapHandler; +} + +/** + * Closure to create handler for downloadHazardLayer + * @return {function} Download the hazard layer based on event_id + */ +function createDownloadHazardLayerHandler() { + var downloadHazardLayerHandler = function (event_id) { + var event = undefined; + for(var i=0;i < event_json.length;i++){ + event = event_json[i]; + if(event_id == event.event_id){ + break; + } + } + + if(event == undefined){ + alert("Event not found"); + return; + } + + var url = event.hazard_layer; + SaveToDisk(url, event.event_id + '-hazard.zip'); + }; + return downloadHazardLayerHandler; } /** @@ -509,16 +533,64 @@ function createActionRowWriter(button_templates, date_format) { var $span = $(''); for (var i = 0; i < button_templates.length; i++) { var button = button_templates[i]; - var $inner_button = $(''); - $inner_button.addClass('row-action-icon') - $inner_button.addClass(button.css_class); - $inner_button.attr('title', button.name); - var $button = $(''); - $button.addClass('btn btn-primary row-action-container'); - $button.attr('title', button.name); - $button.attr('onclick', button.handler + "('" + record.event_id + "')"); - $button.append($inner_button); - $span.append($button); + if(button.type == 'simple-button') { + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr('title', button.name); + var $button = $(''); + $button.addClass('btn btn-primary row-action-container'); + $button.attr('title', button.name); + $button.attr('onclick', button.handler + "('" + record.event_id + "')"); + $button.append($inner_button); + $span.append($button); + } + else if(button.type == 'dropdown'){ + var $button = $(''); + $button.addClass('btn btn-primary dropdown-toggle row-action-container'); + $button.attr('title', button.name); + $button.attr('data-toggle', 'dropdown'); + $button.attr('aria-haspopup', 'true'); + $button.attr('aria-expanded', 'false'); + var $inner_button = $(''); + $inner_button.addClass('row-action-icon'); + $inner_button.addClass(button.css_class); + $inner_button.attr('title', button.name); + $button.append($inner_button); + var $menu = $(''); + $menu.addClass('dropdown-menu'); + for(var j=0;j < button.actions.length;j++){ + var action = button.actions[j]; + if(action.active && $.isFunction(action.active) && !action.active(record)){ + continue; + } + var $li = $('
  • '); + var $action = $(''); + if(action.href == undefined){ + $action.attr('href', '#'); + } + else if($.isFunction(action.href)){ + $action.attr('href', action.href(record)); + } + + if(action.download && $.isFunction(action.download)){ + $action.attr('download', action.download(record)); + } + + if(action.handler){ + $action.attr('onclick', action.handler + "('" + record.event_id + "')"); + } + + $action.text(action.text); + $li.append($action); + $menu.append($li); + } + var $group = $('
    '); + $group.addClass('btn-group'); + $group.append($button); + $group.append($menu); + $span.append($group); + } } tr += '' + $span.html() + ''; diff --git a/django_project/realtime/static/realtime/js/realtime.js b/django_project/realtime/static/realtime/js/realtime.js index cfdfa7bd..db2d05f5 100644 --- a/django_project/realtime/static/realtime/js/realtime.js +++ b/django_project/realtime/static/realtime/js/realtime.js @@ -55,6 +55,7 @@ function browser_identity(){ */ function SaveToDisk(fileURL, fileName) { if (!window.ActiveXObject) { + // emulate button click on a element var save = document.createElement('a'); save.href = fileURL; if(!browser_identity().is_safari){ @@ -70,7 +71,7 @@ function SaveToDisk(fileURL, fileName) { }); save.dispatchEvent(evt); - (window.URL || window.webkitURL).revokeObjectURL(save.href); + // (window.URL || window.webkitURL).revokeObjectURL(save.href); } // for IE < 11 diff --git a/django_project/realtime/static/realtime/js/templates/ash/popup_content.jst b/django_project/realtime/static/realtime/js/templates/ash/popup_content.jst index 074c0d5e..3c175390 100644 --- a/django_project/realtime/static/realtime/js/templates/ash/popup_content.jst +++ b/django_project/realtime/static/realtime/js/templates/ash/popup_content.jst @@ -42,13 +42,37 @@ onclick="showReportHandler('<%= id %>')"> - +
    + + +
    diff --git a/django_project/realtime/static/realtime/js/templates/earthquake/popup_content.jst b/django_project/realtime/static/realtime/js/templates/earthquake/popup_content.jst index b0717bc5..2c8230a7 100644 --- a/django_project/realtime/static/realtime/js/templates/earthquake/popup_content.jst +++ b/django_project/realtime/static/realtime/js/templates/earthquake/popup_content.jst @@ -30,19 +30,37 @@ onclick="showReportHandler('<%= shake_id %>')"> - - +
    + + +
    diff --git a/django_project/realtime/tasks/flood.py b/django_project/realtime/tasks/flood.py index 0e9623f6..e5df09f6 100644 --- a/django_project/realtime/tasks/flood.py +++ b/django_project/realtime/tasks/flood.py @@ -7,6 +7,8 @@ import tempfile from zipfile import ZipFile +from django.contrib.gis.gdal.error import OGRIndexError + from realtime.app_settings import OSM_LEVEL_7_NAME, OSM_LEVEL_8_NAME from core.celery_app import app from django.conf import settings @@ -70,12 +72,17 @@ def process_hazard_layer(flood): rw = BoundaryAlias.objects.get(alias=OSM_LEVEL_8_NAME) for feat in layer: - pkey = feat.get('pkey') - level_name = feat.get('level_name') - # flooded = feat.get('flooded') - # count = feat.get('count') - parent_name = feat.get('parent_nam') - state = feat.get('state') + if not flood.data_source or flood.data_source == 'petajakarta': + upstream_id = feat.get('pkey') + level_name = feat.get('level_name') + parent_name = feat.get('parent_nam') + state = feat.get('state') + elif flood.data_source == 'petabencana': + upstream_id = feat.get('area_id') + level_name = feat.get('area_name') + parent_name = feat.get('parent_nam') + state = feat.get('state') + geometry = feat.geom geos_geometry = GEOSGeometry(geometry.geojson) @@ -91,7 +98,7 @@ def process_hazard_layer(flood): boundary_alias=kelurahan) except Boundary.DoesNotExist: boundary_kelurahan = Boundary.objects.create( - upstream_id=pkey, + upstream_id=upstream_id, geometry=geos_geometry, name=parent_name, boundary_alias=kelurahan) @@ -99,13 +106,13 @@ def process_hazard_layer(flood): try: boundary_rw = Boundary.objects.get( - upstream_id=pkey, boundary_alias=rw) + upstream_id=upstream_id, boundary_alias=rw) boundary_rw.geometry = geos_geometry boundary_rw.name = level_name boundary_rw.parent = boundary_kelurahan except Boundary.DoesNotExist: boundary_rw = Boundary.objects.create( - upstream_id=pkey, + upstream_id=upstream_id, geometry=geos_geometry, name=level_name, parent=boundary_kelurahan, @@ -168,7 +175,10 @@ def process_impact_layer(flood): for feat in layer: level_7_name = feat.get('NAMA_KELUR').strip() - hazard_class = feat.get('affected') + try: + hazard_class = feat.get('affected') + except OGRIndexError: + hazard_class = feat.get('safe_ag') population_affected = feat.get('Pop_Total') geometry = feat.geom geos_geometry = GEOSGeometry(geometry.geojson) diff --git a/django_project/realtime/templates/realtime/ash/index.html b/django_project/realtime/templates/realtime/ash/index.html index 00e338e3..f7346433 100644 --- a/django_project/realtime/templates/realtime/ash/index.html +++ b/django_project/realtime/templates/realtime/ash/index.html @@ -168,16 +168,42 @@

    {% trans "Ash Fall" %}

    {# Use magic number 000 for placeholders #} var report_url = '{% url "realtime:ash_report_detail" volcano_name='VOLCANOTEMPLATENAME' event_time='1234567890123456789' language=language.selected_language.id %}'; var showReportHandler = createShowReportHandler(report_url); + var downloadReportHandler = createDownloadReportHandler(report_url); var button_templates = [ { - name: 'Zoom', + type: 'simple-button', + name: '{% trans "Zoom" %}', css_class: 'glyphicon glyphicon-search', handler: 'showEventHandler' }, { - name: 'Report', + type: 'simple-button', + name: '{% trans "Report" %}', css_class: 'glyphicon glyphicon-file', handler: 'showReportHandler' + }, + { + type: 'dropdown', + name: '{% trans "Download" %}', + css_class: 'glyphicon glyphicon-download', + actions: [ + { + active: function(event){return event.hazard_file !== undefined;}, + text: '{% trans "Hazard File" %}', + href: function(event){return event.hazard_file;}, + download: function(event){return event.event_id_formatted+'-hazard.tif';} + }, + { + active: function(event){return event.impact_files !== undefined;}, + text: '{% trans "Impact Files" %}', + href: function(event){return event.impact_files;}, + download: function(event){return event.event_id_formatted+'-impact.zip';} + }, + { + text: '{% trans "Report" %}', + handler: 'downloadReportHandler' + } + ] } ]; @@ -199,6 +225,11 @@

    {% trans "Ash Fall" %}

    dynaTable.paginationPerPage.set(10); dynaTable.sorts.add('event_time', -1); dynaTable.process(); + + if(jsonTableContents.length > 0){ + var event_id = jsonTableContents[0].id + showEventHandler(event_id); + } } function onEachFeature(feature, layer) { @@ -237,7 +268,6 @@

    {% trans "Ash Fall" %}

    $.get(get_events_url, function (data) { event_json = data; getAshEventsJson(data); - mapFitAll(map, markers); }); }); diff --git a/django_project/realtime/templates/realtime/earthquake/index.html b/django_project/realtime/templates/realtime/earthquake/index.html index 191c32c4..800c7657 100644 --- a/django_project/realtime/templates/realtime/earthquake/index.html +++ b/django_project/realtime/templates/realtime/earthquake/index.html @@ -299,8 +299,7 @@

    InaSAFE

    // create dynatable var jsonTableContents = []; - var showEventHandler = createShowEventHandler(map, markers, - map_events); + var showEventHandler = createShowEventHandler(map, markers, map_events); {# Use magic number 000 for placeholder#} var report_url = '{% url "realtime:earthquake_report_detail" shake_id='000' language=language.selected_language.id %}'; var report_download_url = '{% url "realtime_report:report_pdf" shake_id='000' language=language.selected_language.id language2=language.selected_language.id %}'; @@ -310,25 +309,38 @@

    InaSAFE

    var downloadGridHandler = createDownloadGridHandler(grid_url); var button_templates = [ { + type: 'simple-button', name: '{% trans "Zoom" %}', css_class: 'glyphicon glyphicon-search', handler: 'showEventHandler' }, { + type: 'simple-button', name: '{% trans "Report" %}', css_class: 'glyphicon glyphicon-file', handler: 'showReportHandler' }, { + type: 'dropdown', name: '{% trans "Download" %}', css_class: 'glyphicon glyphicon-download', - handler: 'downloadReportHandler' - }, - { - name: '{% trans "Grid" %}', - css_class: 'glyphicon glyphicon-download', - handler: 'downloadGridHandler', - label: 'XML' + actions: [ + { + text: '{% trans "Report" %}', + handler: 'downloadReportHandler' + }, + { + active: function(event){return event.shake_grid != undefined;}, + text: '{% trans "Grid XML" %}', + handler: 'downloadGridHandler' + }, + { + active: function(event){return event.mmi_output != undefined;}, + text: '{% trans "MMI output" %}', + href: function(event){return event.mmi_output;}, + download: function(event){return event.shake_id+'-mmi.zip'} + } + ] } ]; @@ -351,6 +363,11 @@

    InaSAFE

    dynaTable.paginationPerPage.set(20); dynaTable.sorts.add('time', -1); dynaTable.process(); + + if(jsonTableContents.length > 0){ + var shake_id = jsonTableContents[0].shake_id; + showEventHandler(shake_id); + } } function onEachFeature(feature, layer) { @@ -417,7 +434,6 @@

    InaSAFE

    event_json = data; getEarthquakeEventsJson(data); modifyMapDescriptions(".map-title"); - mapFitAll(map, markers); }); // add content filter handler diff --git a/django_project/realtime/templates/realtime/flood/index.html b/django_project/realtime/templates/realtime/flood/index.html index fd8ca2ae..80c686ad 100644 --- a/django_project/realtime/templates/realtime/flood/index.html +++ b/django_project/realtime/templates/realtime/flood/index.html @@ -237,24 +237,44 @@

    {% trans "Flood" %}

    var report_url = '{% url "realtime:flood_report_detail" event_id='0000000000-6-rw' language=language.selected_language.id %}'; var showReportHandler = createShowReportHandler(report_url); var showImpactMapHandler = createShowImpactMapHandler(report_url); -{# var downloadReportHandler = createDownloadReportHandler(report_url);#} + var downloadImpactMapHandler = createDownloadImpactMapHandler(report_url); var button_templates = [ { - name: 'Show', + type: 'simple-button', + name: '{% trans "Show" %}', css_class: 'glyphicon glyphicon-search', handler: 'showFeaturesHandler' }, { - name: 'Impact Map', - css_class: 'glyphicon glyphicon-globe', + type: 'simple-button', + name: '{% trans "Report" %}', + css_class: 'glyphicon glyphicon-file', handler: 'showImpactMapHandler' }, -{# {#} -{# name: 'Download',#} -{# css_class: 'glyphicon glyphicon-download',#} -{# handler: 'downloadReportHandler'#} -{# }#} + { + type: 'dropdown', + name: '{% trans "Download" %}', + css_class: 'glyphicon glyphicon-download', + actions: [ + { + active: function(event){return event.hazard_layer !== undefined;}, + text: '{% trans "Hazard Layer" %}', + href: function(event){return event.hazard_layer;}, + download: function(event){return event.event_id+'-hazard.zip';} + }, + { + active: function(event){return event.impact_layer !== undefined;}, + text: '{% trans "Impact Layer" %}', + href: function(event){return event.impact_layer;}, + download: function(event){return event.event_id+'-impact.zip';} + }, + { + text: '{% trans "Impact Map" %}', + handler: 'downloadImpactMapHandler' + } + ] + } ]; $(document).ready(function(){ diff --git a/django_project/realtime/views/ash.py b/django_project/realtime/views/ash.py index 43b426fb..82af22ab 100644 --- a/django_project/realtime/views/ash.py +++ b/django_project/realtime/views/ash.py @@ -165,7 +165,7 @@ class AshDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, queryset = Ash.objects.all() serializer_class = AshSerializer lookup_field = 'id' - parser_classes = [JSONParser, FormParser] + parser_classes = [JSONParser, FormParser, MultiPartParser] permission_classes = (DjangoModelPermissionsOrAnonReadOnly, ) def get(self, request, volcano_name=None, event_time=None): @@ -192,12 +192,18 @@ def get(self, request, volcano_name=None, event_time=None): serializer = self.get_serializer(instance) return Response(serializer.data) - def put(self, request, *args, **kwargs): + def put(self, request, volcano_name=None, event_time=None, + *args, **kwargs): try: - event_id = request.data['id'] - if event_id: - event = Ash.objects.get(id=event_id) - event.hazard_file.delete() + if volcano_name and event_time: + instance = Ash.objects.get( + volcano__volcano_name__iexact=volcano_name, + event_time=parse(event_time)) + self.kwargs.update(id=instance.id) + if 'hazard_file' in request.FILES and instance.hazard_file: + instance.hazard_file.delete() + if 'impact_files' in request.FILES and instance.impact_files: + instance.impact_files.delete() retval = self.update(request, partial=True, *args, **kwargs) return retval else: diff --git a/django_project/realtime/views/earthquake.py b/django_project/realtime/views/earthquake.py index 2c37c465..1034eaa5 100644 --- a/django_project/realtime/views/earthquake.py +++ b/django_project/realtime/views/earthquake.py @@ -182,21 +182,13 @@ def put(self, request, *args, **kwargs): data = request.data shake_id = kwargs.get('shake_id') or data.get('shake_id') instance = Earthquake.objects.get(shake_id=shake_id) - if instance.shake_grid: + if 'shake_grid' in request.FILES and instance.shake_grid: instance.shake_grid.delete() - if 'shake_grid' in request.data: - # posting shake grid means only updating its shake_grid - # properties - request.data['shake_id'] = shake_id - request.data['location'] = instance.location - request.data['location_description'] = \ - instance.location_description - request.data['time'] = instance.time - request.data['magnitude'] = instance.magnitude - request.data['depth'] = instance.depth + if 'mmi_output' in request.FILES and instance.mmi_output: + instance.mmi_output.delete() except Earthquake.DoesNotExist: pass - retval = self.update(request, *args, **kwargs) + retval = self.update(request, partial=True, *args, **kwargs) track_rest_push(request) return retval diff --git a/django_project/realtime/views/flood.py b/django_project/realtime/views/flood.py index 0bf1405b..0ae0903e 100644 --- a/django_project/realtime/views/flood.py +++ b/django_project/realtime/views/flood.py @@ -140,8 +140,10 @@ def put(self, request, *args, **kwargs): event_id = request.data['event_id'] if event_id: event = Flood.objects.get(event_id=event_id) - event.hazard_layer.delete() - event.impact_layer.delete() + if 'hazard_layer' in request.FILES and event.hazard_layer: + event.hazard_layer.delete() + if 'impact_layer' in request.FILES and event.impact_layer: + event.impact_layer.delete() retval = self.update(request, partial=True, *args, **kwargs) return retval else: