From ead2e93d389eef6daf6dce2a4eb2f2b0a0efaf22 Mon Sep 17 00:00:00 2001 From: Mike Chagnon Date: Wed, 20 Jan 2021 12:49:13 -0500 Subject: [PATCH 1/2] switch many API calls for the backend into post requests --- ifcbdb/assets/js/bin.js | 30 +++++-- ifcbdb/dashboard/urls.py | 35 ++++++++ ifcbdb/dashboard/views.py | 9 ++ ifcbdb/templates/dashboard/bin.html | 135 +++++++++++++++------------- 4 files changed, 140 insertions(+), 69 deletions(-) diff --git a/ifcbdb/assets/js/bin.js b/ifcbdb/assets/js/bin.js index 133c0d2c..2a493c40 100644 --- a/ifcbdb/assets/js/bin.js +++ b/ifcbdb/assets/js/bin.js @@ -195,7 +195,11 @@ function updateBinStats(data) { } function updateBinMetadata() { - $.get("/api/metadata/" + _bin, function(data) { + let payload = { + csrfmiddlewaretoken: _csrf + }; + + $.post("/api/metadata/" + _bin, payload, function(data) { tbody = $("#bin-metadata tbody"); tbody.empty(); @@ -563,12 +567,14 @@ function loadMosaic(pageNumber) { _isMosaicLoading = false; }); - var mosaicUrl = "/api/mosaic/encoded_image/" + _bin + - "?view_size=" + viewSize + - "&scale_factor=" + scaleFactor + - "&page=" + pageNumber; + let payload = { + view_size: viewSize, + scale_factor: scaleFactor, + page: pageNumber, + csrfmiddlewaretoken: _csrf + }; - $.get(mosaicUrl, function(data) { + $.post("/api/mosaic/encoded_image/" + _bin, payload, function(data) { $("#mosaic").attr("src", "data:image/png;base64," + data); $("#mosaic-loading").hide(); $("#mosaic").show(); @@ -898,7 +904,11 @@ function updatePlotVariables(plotData) { } function initPlotData() { - $.get("/api/plot/" + _bin, function(data) { + let payload = { + csrfmiddlewaretoken: _csrf + }; + + $.post("/api/plot/" + _bin, payload, function(data) { _plotData = data; var plotXAxis = $("#plot-x-axis"); @@ -916,7 +926,11 @@ function initPlotData() { function updatePlotData() { // TODO: The plot container has a hard coded height on it that we should make dynamic. However, doing so causes // the plot, when rendering a second time, to revert back to the minimum height - $.get("/api/plot/" + _bin, function(data) { + let payload = { + csrfmiddlewaretoken: _csrf + }; + + $.post("/api/plot/" + _bin, payload, function(data) { _plotData = data; updatePlotVariables(data); diff --git a/ifcbdb/dashboard/urls.py b/ifcbdb/dashboard/urls.py index 87534d08..2fd61a96 100644 --- a/ifcbdb/dashboard/urls.py +++ b/ifcbdb/dashboard/urls.py @@ -100,30 +100,65 @@ def to_url(self, value): ################################## # Paths used for API/Ajax requests ################################## + + ## path('api/time-series/', views.generate_time_series, name='generate_time_series'), + + path('api/bin/', views.bin_data, name='bin_data'), path('api/bin/', views.bin_data), + + ## path('api/closest_bin', views.closest_bin, name='closest_bin'), # closest bin in time + + ## TODO: Not used (called in changeToNearestBin, which is not called)? Already a post path('api/nearest_bin', views.nearest_bin, name='nearest_bin'), + + ## TODO: Not used? path('api/mosaic/coordinates/', views.mosaic_coordinates, name='mosaic_coordintes'), + + ## TODO: Not used? path('api/mosaic/encoded_image/', views.mosaic_page_encoded_image, name='mosaic_page_encoded_image'), + + ## path('api/mosaic/image/.png', views.mosaic_page_image, name='mosaic_page_image'), + + ## TODO: Should be get? path('api/image//', views.image_metadata, name='image_metadata'), + + ## TODO: Should be get? path('api/image_data//', views.image_data, name='image_data'), + + ## TODO: Should be get? path('api/blob//', views.image_blob, name='image_blob'), + + ## TODO: Should be get? path('api/outline//', views.image_outline, name='image_outline'), + + + ## TODO: Changed to post, including one unused method: initPlotData. Deprecate/remove? path('api/plot/', views.plot_data, name='plot_data'), + + ## path('api/metadata/', views.bin_metadata, name='bin_metadata'), + path('api/bin_exists', views.bin_exists, name='bin_exists'), path('api/single_bin_exists', views.single_bin_exists, name='single_bin_exists'), path('api/bin_location', views.bin_location, name='bin_location'), path('api/filter_options', views.filter_options, name='filter_options'), path('api/has_products/', views.has_products, name='has_products'), + + ## path('api/search_bin_locations', views.search_bin_locations, name='search_bin_locations'), + + path('api/search_timeline_locations', views.search_timeline_locations, name='search_timeline_locations'), path('api/search_comments', views.search_comments, name='search_comments'), path('api/tags', views.tags, name='tags'), + + ## path('api/timeline_info', views.timeline_info, name='timeline_info'), + path('api/list_bins', views.list_bins, name='list_bins'), path('api/list_images/', views.list_images, name='list_images'), path('api/update_skip', views.update_skip, name='update_skip'), diff --git a/ifcbdb/dashboard/views.py b/ifcbdb/dashboard/views.py index 7bbe411f..fdd91922 100644 --- a/ifcbdb/dashboard/views.py +++ b/ifcbdb/dashboard/views.py @@ -496,6 +496,7 @@ def image_outline(request, bin_id, target): # TODO: Needs to change from width/height parameters to single widthXheight + def mosaic_coordinates(request, bin_id): width = int(request.GET.get("width", 800)) height = int(request.GET.get("height", 600)) @@ -517,6 +518,7 @@ def mosaic_page_image(request, bin_id): @cache_control(max_age=31557600) # client cache for 1y +@require_POST def mosaic_page_encoded_image(request, bin_id): arr = _mosaic_page_image(request, bin_id) @@ -838,6 +840,7 @@ def _mosaic_page_image(request, bin_id): # are needed to let the UI know that certain levels are "off limits" and avoid re-running data when we know it's # just going to force us down to a finer resolution anyway # TODO: Handle tag/instrument grouping +@require_POST def generate_time_series(request, metric,): resolution = request.GET.get("resolution", "auto") start = request.GET.get("start",None) @@ -925,6 +928,7 @@ def bin_data(request, bin_id): return JsonResponse(details) +@require_POST def closest_bin(request): bin_qs = filter_parameters_bin_query(request.POST) @@ -942,6 +946,7 @@ def closest_bin(request): }) +@require_POST def nearest_bin(request): bins = filter_parameters_bin_query(request.POST) start = request.POST.get('start') # limit to start time @@ -963,6 +968,7 @@ def nearest_bin(request): }) +@require_POST def plot_data(request, bin_id): b = get_object_or_404(Bin, pid=bin_id) try: @@ -1012,6 +1018,7 @@ def plot_data(request, bin_id): 'runSampleFast', ] +@require_POST def bin_metadata(request, bin_id): bin = get_object_or_404(Bin, pid=bin_id) @@ -1159,6 +1166,8 @@ def tags(request): cloud = Tag.cloud(dataset=dataset, instrument=instrument) return JsonResponse({'cloud': list(cloud)}) + +@require_POST def timeline_info(request): bin_qs = filter_parameters_bin_query(request.GET) diff --git a/ifcbdb/templates/dashboard/bin.html b/ifcbdb/templates/dashboard/bin.html index 7d4995a0..2b775775 100644 --- a/ifcbdb/templates/dashboard/bin.html +++ b/ifcbdb/templates/dashboard/bin.html @@ -696,26 +696,27 @@ function createTimeSeries(metric, defaultStartDate, defaultEndDate) { container = $("#primary-plot-container"); - currentMetric = metric; - var dataUrl = "/api/time-series/" + metric + - "?resolution=auto" + - "&dataset=" + _dataset + - "&instrument=" + _instrument + - "&tags=" + _tags + - "&cruise=" + _cruise + - "&sample_type=" + _sampleType; - - var currentRange = null; + + let payload = { + resolution: "auto", + dataset: _dataset, + instrument: _instrument, + tags: _tags, + cruise: _cruise, + sample_type: _sampleType, + csrfmiddlewaretoken: _csrf + }; + + let currentRange = null; if (plot) { currentRange = { "xaxis.range[0]": container[0].layout.xaxis.range[0], "xaxis.range[1]": container[0].layout.xaxis.range[1] } - dataUrl += - "&start=" + container[0].layout.xaxis.range[0] + - "&end=" + container[0].layout.xaxis.range[1]; + payload["start"] = container[0].layout.xaxis.range[0]; + payload["end"] = container[0].layout.xaxis.range[1]; } if (defaultStartDate != null && defaultEndDate != null) { @@ -724,47 +725,51 @@ "xaxis.range[1]": defaultEndDate } - dataUrl += - "&start=" + defaultStartDate + - "&end=" + defaultEndDate; + payload["start"] = defaultStartDate; + payload["end"] = defaultEndDate; } - plot = Plotly.d3.json(dataUrl, function(err, response) { - currentResolution = response["resolution"]; + $.post("/api/time-series/" + metric, payload, function(response) { + createPlotFromTimelineData(response, currentRange, metric); + }); +} - var initialConfig = getTimelineConfig(); - var initialData = getTimelineData(response, _binTimestamp, currentResolution); - timelineRelayoutResponse = getTimelineLayout(response, currentRange, metric); +function createPlotFromTimelineData(response, currentRange, metric) { + currentResolution = response["resolution"]; - Plotly.newPlot(container[0], initialData, timelineRelayoutResponse, initialConfig); + var initialConfig = getTimelineConfig(); + var initialData = getTimelineData(response, _binTimestamp, currentResolution); + timelineRelayoutResponse = getTimelineLayout(response, currentRange, metric); - highlightSelectedBinByDate(); + Plotly.newPlot(container[0], initialData, timelineRelayoutResponse, initialConfig); - // Add event to locate nearest bin to user's click - container[0].addEventListener('mousemove', function(event) { - var xaxis = container[0]._fullLayout.xaxis; - var margin = container[0]._fullLayout.margin.l; - var containerMargin = $(container[0]).offset().left; - var x = xaxis.p2c(event.x - margin - containerMargin); - var date = (new Date(x)).toISOString(); - $(container[0]).data('date', date); - }); + highlightSelectedBinByDate(); - container[0].addEventListener('click', function(event) { - // Prevent clicks on plotly controls (modebar actions) - if ($(event.target).parents(".modebar").length > 0) { - return - } + // Add event to locate nearest bin to user's click + container[0].addEventListener('mousemove', function(event) { + var xaxis = container[0]._fullLayout.xaxis; + var margin = container[0]._fullLayout.margin.l; + var containerMargin = $(container[0]).offset().left; + var x = xaxis.p2c(event.x - margin - containerMargin); + var date = (new Date(x)).toISOString(); + $(container[0]).data('date', date); + }); + + container[0].addEventListener('click', function(event) { + // Prevent clicks on plotly controls (modebar actions) + if ($(event.target).parents(".modebar").length > 0) { + return + } - _coordinates = []; - $("#previous-bin").addClass("disabled"); - $("#next-bin").addClass("disabled"); + _coordinates = []; + $("#previous-bin").addClass("disabled"); + $("#next-bin").addClass("disabled"); - changeToClosestBin($(container[0]).data("date")); - }); + changeToClosestBin($(container[0]).data("date")); + }); - // Add event to handle resolution change when zooming - container[0].on("plotly_relayout", function(relayoutResponse){ + // Add event to handle resolution change when zooming + container[0].on("plotly_relayout", function(relayoutResponse){ timelineValid = false; timelineRelayoutResponse = relayoutResponse; @@ -781,7 +786,6 @@ queryTimeline(container, metric); }); - }); } function queryTimeline(container, metric) { @@ -811,24 +815,26 @@ var start = timelineRelayoutResponse["xaxis.range[0]"]; var end = timelineRelayoutResponse["xaxis.range[1]"]; - var url = "/api/time-series/" + metric + - "?resolution=auto" + - "&dataset=" + _dataset + - "&instrument=" + _instrument + - "&tags=" + _tags + - "&cruise=" + _cruise - "&sample_type=" + _sampleType; + let payload = { + resolution: "auto", + dataset: _dataset, + instrument: _instrument, + tags: _tags, + cruise: _cruise, + sample_type: _sampleType, + csrfmiddlewaretoken: _csrf + } if(start) - url += "&start=" + start; + payload["start"] = start; if(end) - url += "&end=" + end; + payload["end"] = end; // validate the timeline, so that upon return from // the AJAX call we know if it's been invalidated in the meantime timelineValid = true; - $.get(url, function(dataResponse) { + $.post("/api/time-series/" + metric, payload, function(dataResponse) { // the timeline has been invalidated so this response is out of date // if we're not already deferring an attempt, do so if (!timelineValid) { @@ -1382,12 +1388,19 @@ // show timeline info {% if route == 'timeline' %} - var timeline_info_url = 'api/timeline_info?dataset='+_dataset+ - '&instrument='+_instrument+'&tags='+_tags+"&cruise="+_cruise+"&sample_type="+_sampleType; - $.get(timeline_info_url, function(data) { - $("#n_bins").html(data.n_bins+" bins, "+data.n_images+" images"); - $("#total-data-volume").html("("+filesize(data.total_data_volume)+")"); - }) + let timeline_info_payload = { + dataset: _dataset, + instrument: _instrument, + tags: _tags, + cruise: _cruise, + sample_type: _sampleType, + csrfmiddlewaretoken: _csrf + }; + + $.post("api/timeline_info", timeline_info_payload, function(data) { + $("#n_bins").html(data.n_bins+" bins, "+data.n_images+" images"); + $("#total-data-volume").html("("+filesize(data.total_data_volume)+")"); + }); var title = ""; {% if dataset %}title += " ยป {{ dataset.title }}";{% endif %} From 1cd50de2bdea81a38a6dd56d189dd023beaa7d05 Mon Sep 17 00:00:00 2001 From: Mike Chagnon Date: Fri, 22 Jan 2021 11:24:11 -0500 Subject: [PATCH 2/2] finished conversion from GET to POST on API calls --- ifcbdb/assets/js/bin.js | 20 ++++---- ifcbdb/assets/js/site.js | 30 +++++++++-- ifcbdb/dashboard/urls.py | 41 ++++++--------- ifcbdb/dashboard/views.py | 63 ++++++++++++++---------- ifcbdb/templates/dashboard/about.html | 6 +++ ifcbdb/templates/dashboard/bin.html | 29 +++++------ ifcbdb/templates/dashboard/comments.html | 1 + ifcbdb/templates/dashboard/datasets.html | 1 + ifcbdb/templates/dashboard/image.html | 3 +- ifcbdb/templates/dashboard/list.html | 17 ++++--- ifcbdb/templates/dashboard/no-bins.html | 6 +++ 11 files changed, 126 insertions(+), 91 deletions(-) diff --git a/ifcbdb/assets/js/bin.js b/ifcbdb/assets/js/bin.js index 2a493c40..d3160ac1 100644 --- a/ifcbdb/assets/js/bin.js +++ b/ifcbdb/assets/js/bin.js @@ -249,7 +249,7 @@ function updateBinDownloadLinks(data) { $("#download-features").attr("href", infix + _bin + "_features.csv"); $("#download-class-scores").attr("href", infix + _bin + "_class_scores.csv"); - $.get('/api/has_products/' + _bin, function(r) { + $.post('/api/has_products/' + _bin, { csrfmiddlewaretoken: _csrf }, function(r) { $("#download-blobs").toggle(r["has_blobs"]); $("#download-blobs-disabled").toggle(!r["has_blobs"]); @@ -533,12 +533,12 @@ function loadMosaic(pageNumber) { // indicate to the user that coordinates are loading $("#mosaic").css("cursor", "wait"); - var binDataUrl = "/api/bin/" + _bin + - "?view_size=" + viewSize + - "&scale_factor=" + scaleFactor + - "&" + buildFilterOptionsQueryString(true); + let binDataPayload = buildFilterOptionsPayload(true); + binDataPayload.view_size = viewSize; + binDataPayload.scale_factor = scaleFactor; + binDataPayload.csrfmiddlewaretoken = _csrf; - $.get(binDataUrl, function(data) { + $.post("/api/bin/" + _bin, binDataPayload, function(data) { // Update the coordinates for the image _coordinates = JSON.parse(data["coordinates"]); @@ -567,14 +567,14 @@ function loadMosaic(pageNumber) { _isMosaicLoading = false; }); - let payload = { + let imagePayload = { view_size: viewSize, scale_factor: scaleFactor, page: pageNumber, csrfmiddlewaretoken: _csrf }; - $.post("/api/mosaic/encoded_image/" + _bin, payload, function(data) { + $.post("/api/mosaic/encoded_image/" + _bin, imagePayload, function(data) { $("#mosaic").attr("src", "data:image/png;base64," + data); $("#mosaic-loading").hide(); $("#mosaic").show(); @@ -694,7 +694,7 @@ function changeMarker(index) { // isn't always accurate. It will be the location of the marker, which is based on the spidering, and we // need the actual location of that bin to plot it correctly. If we use the stored value, the marker will be // put at the edge of the spidering effect - $.get("/api/bin_location?pid=" + _bin, function(resp){ + $.post("/api/bin_location?pid=" + _bin, { csrfmiddlewaretoken: _csrf }, function(resp){ if (resp.lat && resp.lng) { // Create a new marker based on the information on the matched marker from the main list let newMarker = L.marker( @@ -726,7 +726,7 @@ function recenterMap() { if (_map == null) return; - // If the current bin si already selected, nothing more needs to be done + // If the current bin is already selected, nothing more needs to be done if (_selectedMarker != null && _selectedMarker.options.title == _bin) return; diff --git a/ifcbdb/assets/js/site.js b/ifcbdb/assets/js/site.js index 4c1f46e7..4ead016c 100644 --- a/ifcbdb/assets/js/site.js +++ b/ifcbdb/assets/js/site.js @@ -390,9 +390,10 @@ function updateTimelineFilters(wrapper, initialValues) { applyFilters.prop("disabled", false); } - var qs = buildFilterOptionsQueryString(false, dataset, instrument, tags, cruise, sampleType); + let payload = buildFilterOptionsPayload(false, dataset, instrument, tags, cruise, sampleType); + payload.csrfmiddlewaretoken = _csrf; - $.get("/api/filter_options?" + qs, function(data){ + $.post("/api/filter_options", payload, function(data){ reloadFilterDropdown(datasetFilter, data.dataset_options, dataset); reloadFilterDropdown(instrumentFilter, data.instrument_options, instrument, "IFCB"); reloadFilterDropdown(cruiseFilter, data.cruise_options, cruise); @@ -457,9 +458,10 @@ function applyFilters() { .map(function() {return $(this).val()}).get() .join(); - var qs = buildFilterOptionsQueryString(false, dataset, instrument, tags, cruise, sampleType); + let payload = buildFilterOptionsPayload(false, dataset, instrument, tags, cruise, sampleType); + payload.csrfmiddlewaretoken = _csrf; - $.get("/api/bin_exists?" + qs, function(data) { + $.post("/api/bin_exists", payload, function(data) { if (!data.exists) { alert("No bins were found matching the specified filters. Please update the filters and try again") return; @@ -485,7 +487,12 @@ function goToBin(pid) { if (!pid || pid.trim() == "") return; - $.get("/api/single_bin_exists?pid=" + pid.trim(), function(data){ + let payload = { + pid: pid.trim(), + csrfmiddlewaretoken: _csrf + }; + + $.post("/api/single_bin_exists", payload, function(data){ if (!data.exists) { alert("No matching bin was found. Please check the PID and try again"); return; @@ -529,6 +536,19 @@ function buildFilterOptionsQueryString(fromGlobals, dataset, instrument, tags, c return args.length == 0 ? "" : args.join("&"); } +function buildFilterOptionsPayload(fromGlobals, dataset, instrument, tags, cruise, sampleType) { + let args = buildFilterOptionsArray(fromGlobals, dataset, instrument, tags, cruise, sampleType); + let payload = {}; + + for (let i = 0; i < args.length; i++) { + var parts = args[i].split("="); + + payload[parts[0]] = parts[1]; + } + + return payload; +} + function reloadFilterDropdown(dropdown, options, value, textPrefix) { dropdown.empty(); dropdown.append($("