From 64b222fd3cf2ce7f37197fc0fcebe0d050e33109 Mon Sep 17 00:00:00 2001 From: Pierros Papadeas Date: Fri, 2 Feb 2018 17:29:40 +0100 Subject: [PATCH] Add observation metadata fields and display them on observation_view page --- network/api/serializers.py | 6 +- .../migrations/0034_auto_20180202_1358.py | 41 ++++++ network/base/models.py | 2 + network/static/css/app.scss | 6 + network/static/js/observation_view.js | 4 + .../json-viewer/jquery.json-viewer.css | 45 ++++++ .../json-viewer/jquery.json-viewer.js | 139 ++++++++++++++++++ network/templates/base/observation_view.html | 21 +++ package.json | 5 +- yarn.lock | 4 + 10 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 network/base/migrations/0034_auto_20180202_1358.py create mode 100644 network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.css create mode 100644 network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.js diff --git a/network/api/serializers.py b/network/api/serializers.py index b95a31d..6371e46 100644 --- a/network/api/serializers.py +++ b/network/api/serializers.py @@ -19,9 +19,9 @@ class ObservationSerializer(serializers.ModelSerializer): class Meta: model = Observation - fields = ('id', 'start', 'end', 'ground_station', 'transmitter', - 'norad_cat_id', 'payload', 'waterfall', 'demoddata', 'station_name', - 'station_lat', 'station_lng', 'vetted_status') + fields = ('id', 'start', 'end', 'ground_station', 'transmitter', 'norad_cat_id', + 'payload', 'waterfall', 'demoddata', 'station_name', 'station_lat', + 'station_lng', 'vetted_status', 'client_version', 'client_metadata') read_only_fields = ['id', 'start', 'end', 'observation', 'ground_station', 'transmitter', 'norad_cat_id', 'station_name', 'station_lat', 'station_lng'] diff --git a/network/base/migrations/0034_auto_20180202_1358.py b/network/base/migrations/0034_auto_20180202_1358.py new file mode 100644 index 0000000..b50bf65 --- /dev/null +++ b/network/base/migrations/0034_auto_20180202_1358.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-02-02 13:58 +from __future__ import unicode_literals + +from django.db import migrations, models +import network.base.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0033_auto_20171228_1515'), + ] + + operations = [ + migrations.AddField( + model_name='observation', + name='client_metadata', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='observation', + name='client_version', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='demoddata', + name='payload_demod', + field=models.FileField(blank=True, null=True, upload_to=network.base.models._name_obs_demoddata), + ), + migrations.AlterField( + model_name='observation', + name='payload', + field=models.FileField(blank=True, null=True, upload_to=network.base.models._name_obs_files), + ), + migrations.AlterField( + model_name='observation', + name='waterfall', + field=models.ImageField(blank=True, null=True, upload_to=network.base.models._name_obs_files), + ), + ] diff --git a/network/base/models.py b/network/base/models.py index bcbb5b3..731edc0 100644 --- a/network/base/models.py +++ b/network/base/models.py @@ -351,6 +351,8 @@ class Observation(models.Model): end = models.DateTimeField() ground_station = models.ForeignKey(Station, related_name='observations', on_delete=models.SET_NULL, null=True, blank=True) + client_version = models.CharField(max_length=255, blank=True) + client_metadata = models.TextField(blank=True) payload = models.FileField(upload_to=_name_obs_files, blank=True, null=True) waterfall = models.ImageField(upload_to=_name_obs_files, blank=True, null=True) vetted_datetime = models.DateTimeField(null=True, blank=True) diff --git a/network/static/css/app.scss b/network/static/css/app.scss index e332dee..75c81e9 100644 --- a/network/static/css/app.scss +++ b/network/static/css/app.scss @@ -344,6 +344,12 @@ span.datetime-time { margin-top: 5px; } +#json-renderer { + padding-left: 18px; + padding-top: 3px; + padding-bottom: 5px; +} + .timezone { margin: -5px 0 15px 2px; } diff --git a/network/static/js/observation_view.js b/network/static/js/observation_view.js index fb2b3c4..eb4eb87 100644 --- a/network/static/js/observation_view.js +++ b/network/static/js/observation_view.js @@ -104,6 +104,10 @@ $(document).ready(function() { }); } + //JSON pretty renderer + var metadata = $('#json-renderer').data('json'); + $('#json-renderer').jsonViewer(metadata, {collapsed: true}); + // Hotkeys bindings $(document).bind('keyup', function(event){ if (event.which == 88) { diff --git a/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.css b/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.css new file mode 100644 index 0000000..d6143f9 --- /dev/null +++ b/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.css @@ -0,0 +1,45 @@ +/* Syntax highlighting for JSON objects */ +ul.json-dict, ol.json-array { + list-style-type: none; + margin: 0 0 0 1px; + border-left: 1px dotted #ccc; + padding-left: 2em; +} +.json-string { + color: #0B7500; +} +.json-literal { + color: #1A01CC; + font-weight: bold; +} + +/* Toggle button */ +a.json-toggle { + position: relative; + color: inherit; + text-decoration: none; +} +a.json-toggle:focus { + outline: none; +} +a.json-toggle:before { + color: #aaa; + content: "\25BC"; /* down arrow */ + position: absolute; + display: inline-block; + width: 1em; + left: -1em; +} +a.json-toggle.collapsed:before { + content: "\25B6"; /* left arrow */ +} + +/* Collapsable placeholder links */ +a.json-placeholder { + color: #aaa; + padding: 0 1em; + text-decoration: none; +} +a.json-placeholder:hover { + text-decoration: underline; +} diff --git a/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.js b/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.js new file mode 100644 index 0000000..104de9d --- /dev/null +++ b/network/static/lib/jquery.json-viewer/json-viewer/jquery.json-viewer.js @@ -0,0 +1,139 @@ +/** + * jQuery json-viewer + * @author: Alexandre Bodelot + */ +(function($){ + + /** + * Check if arg is either an array with at least 1 element, or a dict with at least 1 key + * @return boolean + */ + function isCollapsable(arg) { + return arg instanceof Object && Object.keys(arg).length > 0; + } + + /** + * Check if a string represents a valid url + * @return boolean + */ + function isUrl(string) { + var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/ + return regexp.test(string); + } + + /** + * Transform a json object into html representation + * @return string + */ + function json2html(json) { + html = ''; + if (typeof json === 'string') { + json = json.replace(/&/g, '&').replace(//g, '>'); + if (isUrl(json)) + html += '' + json + ''; + else + html += '"' + json + '"'; + } + else if (typeof json === 'number') { + html += '' + json + ''; + } + else if (typeof json === 'boolean') { + html += '' + json + ''; + } + else if (json === null) { + html += 'null'; + } + else if (json instanceof Array) { + if (json.length > 0) { + html += '[
    '; + for (var i = 0; i < json.length; ++i) { + html += '
  1. ' + // Add toggle button if item is collapsable + if (isCollapsable(json[i])) + html += ''; + + html += json2html(json[i]); + // Add comma if item is not last + if (i < json.length - 1) + html += ','; + html += '
  2. '; + } + html += '
]'; + } + else { + html += '[]'; + } + } + else if (typeof json === 'object') { + var key_count = Object.keys(json).length; + if (key_count > 0) { + html += '{}'; + } + else { + html += '{}'; + } + } + return html; + } + + /** + * jQuery plugin method + */ + $.fn.jsonViewer = function(json, options) { + // jQuery chaining + return this.each(function() { + + // Transform to HTML + var html = json2html(json) + if (isCollapsable(json)) + html = '' + html; + + // Insert HTML in target DOM element + $(this).html(html); + + // Bind click on toggle buttons + $(this).off('click'); + $(this).on('click', 'a.json-toggle', function() { + var target = $(this).toggleClass('collapsed').siblings('ul.json-dict, ol.json-array'); + target.toggle(); + if (target.is(':visible')) { + target.siblings('.json-placeholder').remove(); + } + else { + var count = target.children('li').length; + var placeholder = count + (count > 1 ? ' items' : ' item'); + target.after('' + placeholder + ''); + } + return false; + }); + + // Simulate click on toggle button when placeholder is clicked + $(this).on('click', 'a.json-placeholder', function() { + $(this).siblings('a.json-toggle').click(); + return false; + }); + + if (typeof options == "object" && options.collapsed == true) { + // Trigger click to collapse all nodes + $(this).find('a.json-toggle').click(); + } + }); + }; +})(jQuery); diff --git a/network/templates/base/observation_view.html b/network/templates/base/observation_view.html index 8bcb22d..1ca370b 100644 --- a/network/templates/base/observation_view.html +++ b/network/templates/base/observation_view.html @@ -5,6 +5,10 @@ {% block title %} - Observation {{ observation.id }}{% endblock %} +{% block css %} + +{% endblock css %} + {% block content %}
@@ -159,6 +163,22 @@ {{ observation.set_azimuth }}°
+ {% if observation.client_version %} +
+ Client Version + + {{ observation.client_version }} + +
+ {% endif %} + {% if observation.client_metadata %} +
+ Metadata + +

+          
+
+ {% endif %}
{% if observation.has_audio or observation.waterfall %} Downloads @@ -315,6 +335,7 @@ + diff --git a/package.json b/package.json index 689ce94..8298677 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dnt-helper": "schalkneethling/dnt-helper", "eonasdan-bootstrap-datetimepicker": "^4.17.47", "jquery": "^3.2.1", + "jquery.json-viewer": "^1.1.0", "mapbox-gl": "^0.40.1", "moment": "^2.18.1", "urijs": "^1.18.12", @@ -43,6 +44,8 @@ "urijs/src/URI.min.js", "dnt-helper/js/dnt-helper.js", "bootstrap-slider/dist/bootstrap-slider.min.js", - "bootstrap-slider/dist/css/bootstrap-slider.min.css" + "bootstrap-slider/dist/css/bootstrap-slider.min.css", + "jquery.json-viewer/json-viewer/jquery.json-viewer.js", + "jquery.json-viewer/json-viewer/jquery.json-viewer.css" ] } diff --git a/yarn.lock b/yarn.lock index 794a9d2..20bd160 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1788,6 +1788,10 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" +jquery.json-viewer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jquery.json-viewer/-/jquery.json-viewer-1.1.0.tgz#e97878cfa64c938a1eaf8d25cdf61f04d53df78d" + "jquery@^1.8.3 || ^2.0 || ^3.0", jquery@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"