kopia lustrzana https://github.com/OpenDroneMap/WebODM
Add duplicate task functionality, post process pipeline step
rodzic
dee1e0d880
commit
8fa2cc9643
|
@ -210,6 +210,23 @@ class TaskViewSet(viewsets.ViewSet):
|
|||
|
||||
return Response({'success': True}, status=status.HTTP_200_OK)
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def duplicate(self, request, pk=None, project_pk=None):
|
||||
"""
|
||||
Duplicate a task
|
||||
"""
|
||||
get_and_check_project(request, project_pk, ('change_project', ))
|
||||
try:
|
||||
task = self.queryset.get(pk=pk, project=project_pk)
|
||||
except (ObjectDoesNotExist, ValidationError):
|
||||
raise exceptions.NotFound()
|
||||
|
||||
new_task = task.duplicate()
|
||||
if new_task:
|
||||
return Response({'success': True, 'task': TaskSerializer(new_task).data}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({'error': _("Cannot duplicate task")}, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, project_pk=None):
|
||||
project = get_and_check_project(request, project_pk, ('change_project', ))
|
||||
|
||||
|
|
|
@ -373,6 +373,41 @@ class Task(models.Model):
|
|||
else:
|
||||
return {}
|
||||
|
||||
def duplicate(self):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
task = Task.objects.get(pk=self.pk)
|
||||
task.pk = None
|
||||
task.name = gettext('Copy of %(task)s') % {'task': self.name}
|
||||
task.created_at = timezone.now()
|
||||
task.save()
|
||||
task.refresh_from_db()
|
||||
|
||||
logger.info("Duplicating {} to {}".format(self, task))
|
||||
|
||||
for img in self.imageupload_set.all():
|
||||
img.pk = None
|
||||
img.task = task
|
||||
|
||||
prev_name = img.image.name
|
||||
img.image.name = assets_directory_path(task.id, task.project.id,
|
||||
os.path.basename(img.image.name))
|
||||
|
||||
img.save()
|
||||
|
||||
try:
|
||||
# Try to use hard links first
|
||||
shutil.copytree(self.task_path(), task.task_path(), copy_function=os.link)
|
||||
except Exception as e:
|
||||
logger.warning("Cannot duplicate task using hard links, will use normal copy instead: {}".format(str(e)))
|
||||
shutil.copytree(self.task_path(), task.task_path())
|
||||
|
||||
return task
|
||||
except Exception as e:
|
||||
logger.warning("Cannot duplicate task: {}".format(str(e)))
|
||||
|
||||
return False
|
||||
|
||||
def get_asset_download_path(self, asset):
|
||||
"""
|
||||
Get the path to an asset download
|
||||
|
|
|
@ -51,6 +51,11 @@ export default {
|
|||
action: "odm_report",
|
||||
label: _("Report"),
|
||||
icon: "far fa-file-alt"
|
||||
},
|
||||
{
|
||||
action: "odm_postprocess",
|
||||
label: _("Postprocess"),
|
||||
icon: "fa fa-cog"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ class TaskList extends React.Component {
|
|||
refreshInterval={3000}
|
||||
onDelete={this.deleteTask}
|
||||
onMove={this.moveTask}
|
||||
onDuplicate={this.refresh}
|
||||
hasPermission={this.props.hasPermission}
|
||||
history={this.props.history} />
|
||||
))}
|
||||
|
|
|
@ -22,6 +22,7 @@ class TaskListItem extends React.Component {
|
|||
refreshInterval: PropTypes.number, // how often to refresh info
|
||||
onDelete: PropTypes.func,
|
||||
onMove: PropTypes.func,
|
||||
onDuplicate: PropTypes.func,
|
||||
hasPermission: PropTypes.func
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,8 @@ class TaskListItem extends React.Component {
|
|||
friendlyTaskError: "",
|
||||
pluginActionButtons: [],
|
||||
view: "basic",
|
||||
showMoveDialog: false
|
||||
showMoveDialog: false,
|
||||
actionLoading: false,
|
||||
}
|
||||
|
||||
for (let k in props.data){
|
||||
|
@ -198,11 +200,12 @@ class TaskListItem extends React.Component {
|
|||
).done(json => {
|
||||
if (json.success){
|
||||
this.refresh();
|
||||
if (options.success !== undefined) options.success();
|
||||
if (options.success !== undefined) options.success(json);
|
||||
}else{
|
||||
this.setState({
|
||||
actionError: json.error || options.defaultError || _("Cannot complete operation."),
|
||||
actionButtonsDisabled: false
|
||||
actionButtonsDisabled: false,
|
||||
expanded: true
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@ -211,6 +214,9 @@ class TaskListItem extends React.Component {
|
|||
actionError: options.defaultError || _("Cannot complete operation."),
|
||||
actionButtonsDisabled: false
|
||||
});
|
||||
})
|
||||
.always(() => {
|
||||
if (options.always !== undefined) options.always();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -279,6 +285,19 @@ class TaskListItem extends React.Component {
|
|||
this.setState({showMoveDialog: true});
|
||||
}
|
||||
|
||||
handleDuplicateTask = () => {
|
||||
this.setState({actionLoading: true});
|
||||
this.genActionApiCall("duplicate", {
|
||||
success: (json) => {
|
||||
if (json.task){
|
||||
if (this.props.onDuplicate) this.props.onDuplicate(json.task);
|
||||
}
|
||||
},
|
||||
always: () => {
|
||||
this.setState({actionLoading: false});
|
||||
}})();
|
||||
}
|
||||
|
||||
getRestartSubmenuItems(){
|
||||
const { task } = this.state;
|
||||
|
||||
|
@ -398,6 +417,7 @@ class TaskListItem extends React.Component {
|
|||
pendingActions.REMOVE,
|
||||
pendingActions.RESTART].indexOf(task.pending_action) !== -1);
|
||||
const editable = this.props.hasPermission("change") && [statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1;
|
||||
const actionLoading = this.state.actionLoading;
|
||||
|
||||
let expanded = "";
|
||||
if (this.state.expanded){
|
||||
|
@ -439,6 +459,11 @@ class TaskListItem extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
if ([statusCodes.QUEUED, statusCodes.RUNNING, null].indexOf(task.status) !== -1 &&
|
||||
(task.processing_node || imported) && this.props.hasPermission("change")){
|
||||
addActionButton(_("Cancel"), "btn-primary", "glyphicon glyphicon-remove-circle", this.genActionApiCall("cancel", {defaultError: _("Cannot cancel task.")}));
|
||||
}
|
||||
|
||||
if ([statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1 &&
|
||||
task.processing_node &&
|
||||
this.props.hasPermission("change") &&
|
||||
|
@ -533,11 +558,11 @@ class TaskListItem extends React.Component {
|
|||
|
||||
{stats && stats.gsd ?
|
||||
<div className="labels">
|
||||
<strong>{_("Average GSD:")} </strong> {stats.gsd.toFixed(2)} cm<br/>
|
||||
<strong>{_("Average GSD:")} </strong> {parseFloat(stats.gsd.toFixed(2)).toLocaleString()} cm<br/>
|
||||
</div> : ""}
|
||||
{stats && stats.area ?
|
||||
<div className="labels">
|
||||
<strong>{_("Area:")} </strong> {stats.area.toFixed(2)} m²<br/>
|
||||
<strong>{_("Area:")} </strong> {parseFloat(stats.area.toFixed(2)).toLocaleString()} m²<br/>
|
||||
</div> : ""}
|
||||
{stats && stats.pointcloud && stats.pointcloud.points ?
|
||||
<div className="labels">
|
||||
|
@ -661,7 +686,7 @@ class TaskListItem extends React.Component {
|
|||
if (editable){
|
||||
taskActions.push(
|
||||
<li key="move"><a href="javascript:void(0)" onClick={this.handleMoveTask}><i className="fa fa-arrows-alt"></i>{_("Move")}</a></li>,
|
||||
<li key="duplicate"><a href="javascript:void(0)"><i className="fa fa-copy"></i>{_("Duplicate")}</a></li>
|
||||
<li key="duplicate"><a href="javascript:void(0)" onClick={this.handleDuplicateTask}><i className="fa fa-copy"></i>{_("Duplicate")}</a></li>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -677,6 +702,9 @@ class TaskListItem extends React.Component {
|
|||
}));
|
||||
}
|
||||
|
||||
let taskActionsIcon = "fa-ellipsis-h";
|
||||
if (actionLoading) taskActionsIcon = "fa-circle-notch fa-spin fa-fw";
|
||||
|
||||
return (
|
||||
<div className="task-list-item">
|
||||
{this.state.showMoveDialog ?
|
||||
|
@ -705,8 +733,8 @@ class TaskListItem extends React.Component {
|
|||
<div className="col-sm-1 text-right">
|
||||
{taskActions.length > 0 ?
|
||||
<div className="btn-group">
|
||||
<button disabled={disabled} className="btn task-actions btn-secondary btn-xs dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i className="fa fa-ellipsis-h"></i>
|
||||
<button disabled={disabled || actionLoading} className="btn task-actions btn-secondary btn-xs dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i className={"fa " + taskActionsIcon}></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-right">
|
||||
{taskActions}
|
||||
|
|
Ładowanie…
Reference in New Issue