From 9bb8b1bed5e6bd71f8db8c0a140b1ea2e6339eb7 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Wed, 4 Aug 2021 16:20:51 -0400 Subject: [PATCH] Task moving functionality working --- app/models/task.py | 3 -- app/plugins/functions.py | 2 +- app/static/app/js/components/FormDialog.jsx | 21 +++++++++---- .../app/js/components/MoveTaskDialog.jsx | 30 ++++++++++--------- app/static/app/js/components/ProjectList.jsx | 10 ++++++- .../app/js/components/ProjectListItem.jsx | 10 ++++--- app/static/app/js/components/TaskList.jsx | 12 ++++---- app/static/app/js/components/TaskListItem.jsx | 17 +++++++++-- docker-compose.yml | 1 + webodm.sh | 5 ++++ webodm/settings.py | 1 + 11 files changed, 74 insertions(+), 38 deletions(-) diff --git a/app/models/task.py b/app/models/task.py index cf8d1d95..9de1d1de 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -310,9 +310,6 @@ class Task(models.Model): self.move_assets(self.__original_project_id, self.project.id) self.__original_project_id = self.project.id - # Autovalidate on save - self.full_clean() - super(Task, self).save(*args, **kwargs) def assets_path(self, *args): diff --git a/app/plugins/functions.py b/app/plugins/functions.py index 1ca0384d..1092f15f 100644 --- a/app/plugins/functions.py +++ b/app/plugins/functions.py @@ -123,7 +123,7 @@ def build_plugins(): # Check for webpack.config.js (if we need to build it) if plugin.path_exists("public/webpack.config.js"): - if settings.DEV and webpack_watch_process_count() <= 2: + if settings.DEV and webpack_watch_process_count() <= 2 and settings.DEV_WATCH_PLUGINS: logger.info("Running webpack with watcher for {}".format(plugin.get_name())) subprocess.Popen(['webpack-cli', '--watch'], cwd=plugin.get_path("public")) elif not plugin.path_exists("public/build"): diff --git a/app/static/app/js/components/FormDialog.jsx b/app/static/app/js/components/FormDialog.jsx index 5597710c..0333c7f2 100644 --- a/app/static/app/js/components/FormDialog.jsx +++ b/app/static/app/js/components/FormDialog.jsx @@ -95,6 +95,10 @@ class FormDialog extends React.Component { hide(){ this.setState({showModal: false}); if (this.props.onHide) this.props.onHide(); + if (this.serverRequest){ + this.serverRequest.abort(); + this.serverRequest = null; + } } handleSave(e){ @@ -105,13 +109,20 @@ class FormDialog extends React.Component { let formData = {}; if (this.props.getFormData) formData = this.props.getFormData(); - this.props.saveAction(formData).fail(e => { - this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || _("Could not apply changes")}); - }).always(() => { + this.serverRequest = this.props.saveAction(formData); + if (this.serverRequest){ + this.serverRequest.fail(e => { + this.setState({error: e.message || (e.responseJSON || {}).detail || e.responseText || _("Could not apply changes")}); + }).always(() => { + this.setState({saving: false}); + this.serverRequest = null; + }).done(() => { + this.hide(); + }); + }else{ this.setState({saving: false}); - }).done(() => { this.hide(); - }); + } } handleDelete(){ diff --git a/app/static/app/js/components/MoveTaskDialog.jsx b/app/static/app/js/components/MoveTaskDialog.jsx index 734bb097..0c8ce89a 100644 --- a/app/static/app/js/components/MoveTaskDialog.jsx +++ b/app/static/app/js/components/MoveTaskDialog.jsx @@ -27,7 +27,7 @@ class MoveTaskDialog extends React.Component { super(props); this.state = { - projectId: props.task.project.id, + projectId: props.task.project, projects: [], loading: true }; @@ -37,7 +37,7 @@ class MoveTaskDialog extends React.Component { } getFormData(){ - return this.state; + return {project: this.state.projectId}; } onShow(){ @@ -82,20 +82,22 @@ class MoveTaskDialog extends React.Component { getFormData={this.getFormData} onShow={this.onShow} ref={(domNode) => { this.dialog = domNode; }}> - {!this.state.loading ? -
- -
- +
+ {!this.state.loading ? +
+ +
+ +
+ : }
- : } ); } diff --git a/app/static/app/js/components/ProjectList.jsx b/app/static/app/js/components/ProjectList.jsx index c2d662ce..12cdad95 100644 --- a/app/static/app/js/components/ProjectList.jsx +++ b/app/static/app/js/components/ProjectList.jsx @@ -85,6 +85,12 @@ class ProjectList extends Paginated { }); } + handleTaskMoved = (task) => { + if (this["projectListItem_" + task.project]){ + this["projectListItem_" + task.project].newTaskAdded(); + } + } + render() { if (this.state.loading){ return (
); @@ -95,9 +101,11 @@ class ProjectList extends Paginated {
    {this.state.projects.map(p => ( { this["projectListItem_" + p.id] = domNode }} key={p.id} data={p} - onDelete={this.handleDelete} + onDelete={this.handleDelete} + onTaskMoved={this.handleTaskMoved} history={this.props.history} /> ))}
diff --git a/app/static/app/js/components/ProjectListItem.jsx b/app/static/app/js/components/ProjectListItem.jsx index eb49af61..218ffc72 100644 --- a/app/static/app/js/components/ProjectListItem.jsx +++ b/app/static/app/js/components/ProjectListItem.jsx @@ -20,7 +20,8 @@ class ProjectListItem extends React.Component { static propTypes = { history: PropTypes.object.isRequired, data: PropTypes.object.isRequired, // project json - onDelete: PropTypes.func + onDelete: PropTypes.func, + onTaskMoved: PropTypes.func, } constructor(props){ @@ -305,8 +306,9 @@ class ProjectListItem extends React.Component { this.refresh(); } - taskMoved(){ - this.refresh(); + taskMoved(task){ + this.refresh(); + if (this.props.onTaskMoved) this.props.onTaskMoved(task); } handleDelete(){ @@ -575,7 +577,7 @@ class ProjectListItem extends React.Component { ref={this.setRef("taskList")} source={`/api/projects/${data.id}/tasks/?ordering=-created_at`} onDelete={this.taskDeleted} - onMove={this.taskMoved} + onTaskMoved={this.taskMoved} hasPermission={this.hasPermission} history={this.props.history} /> : ""} diff --git a/app/static/app/js/components/TaskList.jsx b/app/static/app/js/components/TaskList.jsx index 6d6279a5..67a8c071 100644 --- a/app/static/app/js/components/TaskList.jsx +++ b/app/static/app/js/components/TaskList.jsx @@ -10,7 +10,7 @@ class TaskList extends React.Component { history: PropTypes.object.isRequired, source: PropTypes.string.isRequired, // URL where to load task list onDelete: PropTypes.func, - onMove: PropTypes.func, + onTaskMoved: PropTypes.func, hasPermission: PropTypes.func.isRequired } @@ -71,11 +71,9 @@ class TaskList extends React.Component { if (this.props.onDelete) this.props.onDelete(id); } - moveTask(id){ - this.setState({ - tasks: this.state.tasks.filter(t => t.id !== id) - }); - if (this.props.onMove) this.props.onMove(id); + moveTask = (task) => { + this.refresh(); + if (this.props.onTaskMoved) this.props.onTaskMoved(task); } render() { @@ -85,7 +83,7 @@ class TaskList extends React.Component { }else if (this.state.error){ message = ({interpolate(_("Error: %(error)s"), {error: this.state.error})} {_("Try again")}); }else if (this.state.tasks.length === 0){ - message = ({_("This project has no tasks. Create one by uploading some images!")}); + message = (); } return ( diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index d76f05f5..9328599a 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -370,6 +370,18 @@ class TaskListItem extends React.Component { }; } + moveTaskAction = (formData) => { + if (formData.project !== this.state.task.project){ + return $.ajax({ + url: `/api/projects/${this.state.task.project}/tasks/${this.state.task.id}/`, + contentType: 'application/json', + data: JSON.stringify(formData), + dataType: 'json', + type: 'PATCH' + }).done(this.props.onMove); + }else return false; + } + render() { const task = this.state.task; const name = task.name !== null ? task.name : interpolate(_("Task #%(number)s"), { number: task.id }); @@ -626,10 +638,9 @@ class TaskListItem extends React.Component { } // Ability to change options - const canAddDelPerms = this.props.hasPermission("add") && this.props.hasPermission("delete"); const editable = [statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1; - if (canAddDelPerms){ + if (this.props.hasPermission("change")){ if (editable || (!task.processing_node)){ taskActions.push(
  • {_("Edit")}
  • ); } @@ -661,7 +672,7 @@ class TaskListItem extends React.Component { task={task} ref={(domNode) => { this.moveTaskDialog = domNode; }} onHide={() => this.setState({showMoveDialog: false})} - saveAction={() => {}} + saveAction={this.moveTaskAction} /> : ""}
    diff --git a/docker-compose.yml b/docker-compose.yml index 1a091090..2de0d85e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,7 @@ services: - WO_DEBUG - WO_BROKER - WO_DEV + - WO_DEV_WATCH_PLUGINS restart: unless-stopped oom_score_adj: 0 broker: diff --git a/webodm.sh b/webodm.sh index f4456ca7..865ea7d9 100755 --- a/webodm.sh +++ b/webodm.sh @@ -82,6 +82,10 @@ case $key in export WO_DEBUG=YES shift # past argument ;; + --dev-watch-plugins) + export WO_DEV_WATCH_PLUGINS=YES + shift # past argument + ;; --dev) export WO_DEBUG=YES export WO_DEV=YES @@ -148,6 +152,7 @@ usage(){ echo " --ssl-insecure-port-redirect Insecure port number to redirect from when SSL is enabled (default: $DEFAULT_SSL_INSECURE_PORT_REDIRECT)" echo " --debug Enable debug for development environments (default: disabled)" echo " --dev Enable development mode. In development mode you can make modifications to WebODM source files and changes will be reflected live. (default: disabled)" + echo " --dev-watch-plugins Automatically build plugins while in dev mode. (default: disabled)" echo " --broker Set the URL used to connect to the celery broker (default: $DEFAULT_BROKER)" echo " --detached Run WebODM in detached mode. This means WebODM will run in the background, without blocking the terminal (default: disabled)" exit diff --git a/webodm/settings.py b/webodm/settings.py index 2e0bc146..e9551635 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -52,6 +52,7 @@ WORKER_RUNNING = sys.argv[2:3] == ["worker"] # SECURITY WARNING: don't run with debug turned on a public facing server! DEBUG = os.environ.get('WO_DEBUG', 'YES') == 'YES' or TESTING DEV = os.environ.get('WO_DEV', 'NO') == 'YES' and not TESTING +DEV_WATCH_PLUGINS = DEV and os.environ.get('WO_DEV_WATCH_PLUGINS', 'NO') == 'YES' SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = os.environ.get('WO_SSL', 'NO') == 'YES' INTERNAL_IPS = ['127.0.0.1']