Skip to content

Commit

Permalink
Split pipeline submit into Save and Save as..., as part of #751.
Browse files Browse the repository at this point in the history
  • Loading branch information
donkirkby committed Feb 20, 2019
1 parent e4623f9 commit 190a5c3
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 15 deletions.
49 changes: 48 additions & 1 deletion kive/container/ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime, timedelta
from wsgiref.util import FileWrapper

from django.core.files.base import File
from django.db.models import Q
from django.db.models.aggregates import Count
from django.http import HttpResponse
Expand All @@ -12,6 +13,8 @@
from rest_framework import permissions
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

Expand All @@ -21,6 +24,7 @@
ContainerSerializer, ContainerAppSerializer, \
ContainerFamilyChoiceSerializer, ContainerRunSerializer, BatchSerializer, \
ContainerArgumentSerializer, ContainerDatasetSerializer, ContainerLogSerializer
from file_access_utils import use_field_file
from kive.ajax import CleanCreateModelMixin, RemovableModelViewSet, \
SearchableModelMixin, IsDeveloperOrGrantedReadOnly, StandardPagination, \
IsGrantedReadCreate, GrantedModelMixin, IsGrantedReadOnly
Expand Down Expand Up @@ -137,12 +141,43 @@ class ContainerChoiceViewSet(ReadOnlyModelViewSet, SearchableModelMixin):
description__icontains=value))


class ContainerRenderer(JSONRenderer):
""" Render the Raw data form for content_put to hold current content. """
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context['view'].action == 'content_put':
data = dict(renderer_context['response'].data)

# Remove ignored fields.
data.pop('files', None)
data.pop('id', None)

# Add new fields that trigger a copy.
data['new_tag'] = None
data['new_description'] = None
rendered = super(ContainerRenderer, self).render(data, accepted_media_type, renderer_context)
return rendered


class ContainerJSONParser(JSONParser):
renderer_class = ContainerRenderer


class ContainerViewSet(CleanCreateModelMixin,
RemovableModelViewSet,
SearchableModelMixin):
""" A Singularity container.
Query parameters:
Extra actions:
* Container Apps - a list of apps in this container
* Download - download the container file
* Container Removal Plan - standard removal plan, including child records
* Container Content - pipeline definition for archive containers. You can
also PUT to this endpoint to update the pipeline definition. If your
PUT data includes `new_tag`, then it will write the new pipeline to
a copy of the container.
Container list query parameters:
* is_granted - true For administrators, this limits the list to only include
records that the user has been explicitly granted access to. For other
Expand All @@ -169,6 +204,7 @@ class ContainerViewSet(CleanCreateModelMixin,
serializer_class = ContainerSerializer
permission_classes = (permissions.IsAuthenticated, IsDeveloperOrGrantedReadOnly)
pagination_class = StandardPagination
parser_classes = [ContainerJSONParser, FormParser, MultiPartParser]
filters = dict(
family_id=lambda queryset, value: queryset.filter(
family_id=value),
Expand Down Expand Up @@ -220,9 +256,20 @@ def content_put(self, request, pk=None):
container = self.get_object()
content = request.data
status_code = HttpResponseBadRequest.status_code
new_tag = content.get('new_tag')
new_description = content.get('new_description')
if 'pipeline' not in content:
response_data = dict(pipeline=['This field is required.'])
elif new_tag and Container.objects.filter(tag=new_tag).exists():
response_data = dict(new_tag=['Tag already exists.'])
else:
if new_tag:
container.pk = None # Saves a copy.
container.tag = new_tag
if new_description:
container.description = new_description
with use_field_file(container.file):
container.file.save(container.file.name, File(container.file))
container.write_content(content)
container.save()
response_data = container.get_content()
Expand Down
3 changes: 2 additions & 1 deletion kive/container/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@ def get_content(self, add_default=True):
content = dict(files=sorted(entry.name
for entry in archive.infolist()
if not entry.name.startswith('kive/')),
pipeline=pipeline)
pipeline=pipeline,
id=self.pk)
return content

def create_new_pipeline_revision(self, tag=None, description=None):
Expand Down
22 changes: 20 additions & 2 deletions kive/container/static/container/container_content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ CanvasListeners.initResizeListeners(canvasState);
*/
let text = $("#initial_data").text();
let loader = new Pipeline(canvasState);
let $containerPk = $('#id_container_pk');
let $memory = $("#id_memory");
let $threads = $("#id_threads");
let $error = $('#id_submit_error');
Expand Down Expand Up @@ -88,6 +89,8 @@ pipelineCheckCompleteness();
let $ctrl_nav = $("#id_ctrl_nav");
let $add_menu = $('#id_add_ctrl');
let $view_menu = $('#id_view_ctrl');
let $save_as_ctrl = $('#id_save_as_ctrl');
let $submit_as_ctrl = $('#id_submit_as_button');

/* anonymous */ new Dialog( $('#id_defaults_ctrl'), $ctrl_nav.find("li[data-rel='#id_defaults_ctrl']") );
/* anonymous */ new ViewDialog( $view_menu, $ctrl_nav.find("li[data-rel='#id_view_ctrl']") );
Expand All @@ -98,6 +101,7 @@ let method_dialog = new MethodDialog(
$add_menu.find("li[data-rel='#id_method_ctrl']"),
loader.container);
let output_dialog = new OutputDialog( $('#id_output_ctrl'), $add_menu.find("li[data-rel='#id_output_ctrl']") );
let save_as_dialog = new Dialog( $save_as_ctrl, $submit_as_ctrl );

$add_menu.click('li', function() { add_menu.hide(); });

Expand Down Expand Up @@ -132,13 +136,27 @@ $view_menu.find('#autolayout_btn').click(
/**
* Part 5/8: Initialize the submission of this page
*/
$('#id_pipeline_form').submit(buildPipelineSubmit(
$('#form_ctrl').click(buildPipelineSubmit(
canvasState,
$('#id_container_pk'),
$containerPk,
$memory,
$threads,
$error
));
$('#id_confirm_save_as').click(buildPipelineSubmit(
canvasState,
$containerPk,
$memory,
$threads,
$error,
$('#id_new_tag'),
$('#id_new_description'),
save_as_dialog
));
$('#id_cancel_save_as').click(function(e) {
e.preventDefault();
save_as_dialog.cancel();
});

/**
* Part 6/8: Initialize context menu and register actions
Expand Down
7 changes: 6 additions & 1 deletion kive/container/static/container/drydock.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions kive/container/static/container/io/PipelineApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export interface PipelineData {
export interface Container {
files: string[];
pipeline: PipelineData;
id?: number;
new_tag?: string;
new_description?: string;
}
export interface PipelineConfig {
parent_family: string;
Expand Down
23 changes: 20 additions & 3 deletions kive/container/static/container/io/pipeline_submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import { CanvasState } from "../canvas/drydock";
import { RestApi } from "../rest_api.service";
import { serializePipeline } from "./serializer";
import {PipelineConfig} from "@container/io/PipelineApi";
import {Dialog} from "@container/pipeline_dialogs";

export function buildPipelineSubmit(
canvasState: CanvasState,
$container_pk: JQuery,
$memory: JQuery, // in MB
$threads: JQuery,
$error: JQuery) {
$error: JQuery,
$new_tag?: JQuery,
$new_description?: JQuery,
$save_as_dialog?: Dialog) {

if ($container_pk.length === 0) {
throw 'Container primary key element could not be found.';
Expand All @@ -25,6 +29,12 @@ export function buildPipelineSubmit(
if ($error.length === 0) {
throw "User error message element could not be found.";
}
if ($new_tag !== undefined && $new_tag.length === 0) {
throw "New tag element could not be found.";
}
if ($new_description !== undefined && $new_description.length === 0) {
throw "New description element could not be found.";
}

/*
* Trigger AJAX transaction on submitting form.
Expand All @@ -42,12 +52,19 @@ export function buildPipelineSubmit(
memory: memory,
threads: threads
};
if ($new_tag !== undefined) {
form_data.new_tag = $new_tag.val();
form_data.new_description = $new_description.val();
}

submitPipelineAjax(container_pk, form_data, $error);

} catch (e) {
submitError(e, $error);
}
if ($save_as_dialog !== undefined) {
$save_as_dialog.hide();
}
};
}

Expand Down Expand Up @@ -89,9 +106,9 @@ function submitPipelineAjax(container_pk, form_data, $error) {
return RestApi.put(
'/api/containers/' + container_pk + '/content/',
JSON.stringify(form_data),
function() {
function(data) {
$(window).off('beforeunload');
window.location.href = '/container_update/' + container_pk;
window.location.href = '/container_update/' + data.id;
},
function (xhr, status, error) {
let json = xhr.responseJSON;
Expand Down
1 change: 1 addition & 0 deletions kive/container/static/container/pipeline_dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class Dialog {
this.show();
// do not bubble up (which would hit document.click again)
e.stopPropagation();
e.preventDefault();
});
// capture mouse/key events
jqueryRef.on('click mousedown keydown', e => e.stopPropagation() );
Expand Down
16 changes: 11 additions & 5 deletions kive/container/templates/container/container_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,22 @@
</ul>

<form id="id_pipeline_form">{% csrf_token %}
<div id="form_ctrl">
<input type="submit" id="id_submit_button" value="Submit">
<div class="indicator-light"> </div>
<div class="errortext" id="id_submit_error"></div>
</div>
<table id="form_ctrls"><tr>
<td><span id="form_ctrl">
<input type="submit" id="id_submit_button" value="Save">
<div class="indicator-light"> </div>
<div class="errortext" id="id_submit_error"></div>
</span></td>
<td>
<input type="submit" id="id_submit_as_button" value="Save as...">
</td>
</tr></table>
</form>

<input id="id_pipeline_action" value="revise" type="hidden">
{% include 'container/content_defaults_dialog.tpl.html' with dlg_id="id_defaults_ctrl" %}
{% include 'container/content_view_dialog.tpl.html' with dlg_id="id_view_ctrl" %}
{% include 'container/content_save_as_dialog.tpl.html' with dlg_id="id_save_as_ctrl" %}

<div id="id_add_ctrl" class="ctrl_menu">
<ul>
Expand Down
20 changes: 20 additions & 0 deletions kive/container/templates/container/content_save_as_dialog.tpl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div id="{{ dlg_id }}" class="ctrl_menu">
<h3>Copy</h3>
<p>Copy to a new container.</p>

<h5>New tag</h5>
<input id="id_new_tag"
maxlength="128"
name="tag"
type="text"
value="{{ object.tag }}">
<h5>New description</h5>
<textarea id="id_new_description"
rows="5"
maxlength="1000"
name="description">{{ object.description }}</textarea>

<button id="id_confirm_save_as">OK</button>

<button id="id_cancel_save_as">Cancel</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ <h3>View Options</h3>
</div>

<button id="id_revert">Revert</button>
<button id="id_update">Check for Updates</button>

<button id="autolayout_btn">Automatically arrange nodes</button>
</div>
Loading

0 comments on commit 190a5c3

Please sign in to comment.