Skip to content

Commit

Permalink
Merge pull request #63 from OdumInstitute/file_datatable_editing
Browse files Browse the repository at this point in the history
File datatable editing
  • Loading branch information
matthew-a-dunlap authored Aug 24, 2022
2 parents 9530ebe + 39d4a2c commit 94881de
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 96 deletions.
97 changes: 94 additions & 3 deletions corere/apps/file_datatable/static/file_datatable/file_datatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
processing: true,
stateSave: true,
paging: true,
select: 'single',
autoWidth: false,
// dom: 'Bftlp',
dom: 'Bfrtpl',
Expand All @@ -60,13 +59,19 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
{
data: 'path',
render: function(data,type,row,meta){
return row[0];
if(readonly) { return row[0]; }

//TODO: Stale, doing name first
return row[0] + '<span style="float:right;"><button class="btn btn-secondary btn-sm" type="button" onclick="show_edit_path_modal(\''+file_url_base+'\', \''+encodeURIComponent(row[1])+'\', \''+encodeURIComponent(row[0])+'\')" title="Edit file path" aria-label="Edit file path"><span class="fas fa-pencil-alt"></span></button></span>';

}
},
{
data: 'name',
render: function(data,type,row,meta){
return row[1];
if(readonly) { return row[1]; }

return row[1] + '<span style="float:right;"><button class="btn btn-secondary btn-sm" type="button" onclick="show_edit_name_modal(\''+file_url_base+'\', \''+encodeURIComponent(row[0])+'\', \''+encodeURIComponent(row[1])+'\')" title="Edit file name" aria-label="Edit file name"><span class="fas fa-pencil-alt"></span></button></span>';
}
},
],
Expand All @@ -78,4 +83,90 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
buttons: top_buttons
}
return config
}

//TODO: When we show these modals, we need to look at the length of the path/name (that is hidden) to determine the length of the input field

function show_edit_name_modal(file_url_base, file_path, old_name){
name_length = 260 - decodeURIComponent(file_path).length;
$('#name_modal_file_name_new').attr('maxlength', name_length);
$('#name_modal_file_name_old').val(decodeURIComponent(old_name));
$('#name_modal_file_path').val(file_path);
$('#name_modal_file_url_base').val(file_url_base);
$('#name_modal').modal('show');
}

function show_edit_path_modal(file_url_base, file_name, old_path){
path_length = 260 - decodeURIComponent(file_name).length;
$('#path_modal_file_path_new').attr('maxlength', path_length);
$('#path_modal_file_path_old').val(decodeURIComponent(old_path));
$('#path_modal_file_name').val(file_name);
$('#path_modal_file_url_base').val(file_url_base);
$('#path_modal').modal('show');
}

//TODO: Why are we only encoding one of the two text fields in both?
function submit_edit_name_modal_and_reload(){
file_name_old = encodeURIComponent($('#name_modal_file_name_old').val())
file_name_new = $('#name_modal_file_name_new').val()
file_url_base = $('#name_modal_file_url_base').val()
file_path = $('#name_modal_file_path').val()

var regex1 = /[*?"<>|;#:\\\/]/;
var regex2 = /\.\./;
if($.trim(file_name_new).length === 0 || regex1.test(file_name_new) || regex2.test(file_name_new)) {
$('#name_modal_sanitize_error').removeAttr('hidden');
return;
}

old_full_path = file_path + file_name_old
new_full_path = file_path + file_name_new

rename_url = file_url_base+'renamefile/?old_path='+old_full_path+'&new_path='+new_full_path
rename_and_refresh(rename_url, clear_name_modal, show_name_modal_generic_error)
}

function show_name_modal_generic_error() {
$('#name_modal_unexpected_error').removeAttr('hidden')
}

function clear_name_modal() {
$('#name_modal_sanitize_error').attr("hidden",true);
$('#name_modal_unexpected_error').attr("hidden",true);
$('#name_modal_file_name_new').val('')
$('#name_modal').modal('hide');
}

function submit_edit_path_modal_and_reload(){
file_path_old = encodeURIComponent($('#path_modal_file_path_old').val())
file_path_new = $('#path_modal_file_path_new').val()
file_url_base = $('#path_modal_file_url_base').val()
file_name = $('#path_modal_file_name').val()

//TODO: We need to ensure path begins and ends with /
var regex1 = /[^a-zA-Z0-9 /_\-\.]/;
var regex2 = /\.\./;
var regex3 = /\/\s*\//
if($.trim(file_path_new).length === 0 || regex1.test(file_path_new) || regex2.test(file_path_new) || regex3.test(file_path_new) || !file_path_new.startsWith("/") || !file_path_new.endsWith("/")) {
$('#path_modal_sanitize_error').removeAttr('hidden');
return;
}

old_full_path = file_path_old + file_name
new_full_path = file_path_new + file_name

rename_url = file_url_base+'renamefile/?old_path='+old_full_path+'&new_path='+new_full_path
rename_and_refresh(rename_url, clear_path_modal, show_path_modal_generic_error)

}

function show_path_modal_generic_error() {
$('#path_modal_unexpected_error').removeAttr('hidden')
}

function clear_path_modal() {
$('#path_modal_sanitize_error').attr("hidden",true);
$('#path_modal_unexpected_error').attr("hidden",true);
$('#path_modal_file_path_new').val('')
$('#path_modal').modal('hide');
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,68 @@
</table>
</div>

<div class="modal fade" id="name_modal" tabindex="-1" role="dialog" aria-labelledby="name_modalTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="name_modalTitle">Rename File</h5>
</div>
<div class="modal-body">
<table>
<div id="name_modal_sanitize_error" class="alert alert-danger" role="alert" hidden>File name cannot contain '..', be emtpy, or any of the following characters: * ? " < > | ; # : \ /</div>
<div id="name_modal_unexpected_error" class="alert alert-danger" role="alert" hidden>An unexpected error occurred. This may be due to a naming/path collision.</div>
<tr>
<td>Old Name: </td>
<td>&nbsp<input type="text" id="name_modal_file_name_old" value="" style="width:370px" disabled></td>
</tr>
<tr>
<td>New Name:</td>
<td>&nbsp<input type="text" id="name_modal_file_name_new" style="width:370px"></td>
</tr>
</table>
</div>
<div class="modal-footer">
<input type="hidden" id="name_modal_file_url_base" value="">
<input type="hidden" id="name_modal_file_path" value="">
<button type="button" class="btn btn-secondary" onclick="clear_name_modal()">Close</button>
<button type="button" id="name_modal_save_changes" class="btn btn-primary" onclick="submit_edit_name_modal_and_reload()">Save changes</button>
</div>
</div>
</div>
</div>

<div class="modal fade" id="path_modal" tabindex="-1" role="dialog" aria-labelledby="path_modalTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="path_modalTitle">Change Path</h5>
</div>
<div class="modal-body">
<table>
<div id="path_modal_sanitize_error" class="alert alert-danger" role="alert" hidden>File path cannot contain '..' or be emtpy. It must begin and end with '/'. Also, it must only contain the following characters: alphanumericals _ - . / and whitespace</div>
<div id="path_modal_unexpected_error" class="alert alert-danger" role="alert" hidden>An unexpected error occurred. This may be due to a naming/path collision.</div>
<tr>
<td>Old Path: </td>
<td>&nbsp<input type="text" id="path_modal_file_path_old" value="" style="width:370px" disabled></td>
</tr>
<tr>
<td>New Path:</td>
<td>&nbsp<input type="text" id="path_modal_file_path_new" style="width:370px"></td>
</tr>
</table>
</div>
<div class="modal-footer">
<input type="hidden" id="path_modal_file_url_base" value="">
<input type="hidden" id="path_modal_file_name" value="">
<button type="button" class="btn btn-secondary" onclick="clear_path_modal()">Close</button>
<button type="button" id="path_modal_save_changes" class="btn btn-primary" onclick="submit_edit_path_modal_and_reload()">Save changes</button>
</div>
</div>
</div>
</div>

{% comment %}If we want this library to truly be portable, all this CORE2 specific stuff should not be here? {% endcomment %}
{% comment %}Also file_datatable.js references delete_and_refresh which exists in the main app {% endcomment %}
{% load static %}
<script type="text/javascript" src="{% static 'file_datatable/file_datatable.js' %}?v=2"></script>
<script type="text/javascript">
Expand Down Expand Up @@ -41,6 +103,12 @@
document.getElementById('files_count_text').textContent = '('+$('#file_table_{{obj_type}}').DataTable().page.info().recordsTotal+')';
}
});
{% comment %}Also add a click listener for our modal{% endcomment%}
$("#name_modal_file_name_new").keyup(function(event) {
if (event.keyCode === 13) {
$("#name_modal_save_changes").click();
}
});
{% endif %}
$('#file_table_{{obj_type}}').DataTable(config)
} );
Expand Down
26 changes: 19 additions & 7 deletions corere/main/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,32 @@ def rename_submission_files(manuscript, files_dict_list):
files_folder = get_submission_files_path(manuscript, relative=True)
return _rename_files(repo_path, files_folder, files_dict_list)


def _rename_files(repo_path, files_folder, files_dict_list):
repo = git.Repo(repo_path)

# TODO: This doesn't handle name collisions that may happen during rename
for d in files_dict_list:
old_path = files_folder + d.get("old")
new_path = files_folder + d.get("new")

if os.path.exists(repo_path + new_path):
logger.warning(
"Error renaming files, new path already exists. Repo path: "
+ repo_path
+ " . Current file old path: "
+ old_path
+ " . New path: "
+ new_path
+ " ."
)
return False
try:
os.rename(repo_path + old_path, repo_path + new_path)
os.renames(repo_path + old_path, repo_path + new_path)
repo.index.add(repo_path + new_path)
repo.index.remove(repo_path + old_path)
repo.index.commit("File " + repo_path + old_path + " renamed to " + repo_path + new_path)
except OSError as e:
except Exception as e:
logger.error(
"Error renaming files. Likely due to name collision. Repo path: "
"Error renaming files. Cause uncertain. Repo path: "
+ repo_path
+ " . Current file old path: "
+ old_path
Expand All @@ -118,7 +128,9 @@ def _rename_files(repo_path, files_folder, files_dict_list):
+ " . Error "
+ str(e)
)
raise
return False

return True


def delete_manuscript_file(manuscript, file_path):
Expand Down Expand Up @@ -259,7 +271,7 @@ def download_all_manuscript_files(manuscript):

### The repo contains a sub-folder containing all the files. This is half to support downloading zips with a root folder


#TODO: Maybe rename these path endpoints to communicate they are getting the system path not the relative path in the code folder
def get_manuscript_repo_path(manuscript):
return settings.GIT_ROOT + "/" + str(manuscript.id) + "_-_manuscript_-_" + manuscript.slug + "/"

Expand Down
7 changes: 7 additions & 0 deletions corere/main/static/main/output.scss
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@ select.custom-select {
margin-right:0px !important;
}

//Dropzone error svg x
.dz-error-mark svg g g{
fill: #be2626;
fill-opacity: 1;
stroke-opacity: 0;
}

//Add back bootstrap 3 tiny buttons, for our grid. We aren't targeting mobile anyways
// .btn-group-xs > .btn, .btn-xs {
// padding: .25rem .4rem;
Expand Down
100 changes: 42 additions & 58 deletions corere/main/templates/main/file_upload.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,73 +55,57 @@
table.ajax.reload( function(){
document.getElementById('files_count_text').textContent = '('+table.page.info().recordsTotal+')'
});

{% comment %} // if(file.fullPath){
// full_name = "/" + file.fullPath
// } else {
// full_name = "/" + file.name
// }
// encode_full_name = encodeURIComponent(full_name)

// console.log(encode_full_name)

// document.getElementById("filesHeaderDiv").style.visibility = 'visible';

// $("#filestable").append(`<tr>
// <td> <button class="btn btn-secondary btn-sm" type="button" onclick="window.open('../downloadfile/?file_path=`+encode_full_name+`')"><span class="fas fa-file-download"></span></button></td>
// <td> <button class="btn btn-secondary btn-sm" type="button" onclick="delete_and_remove(this, '`+encode_full_name+`')"><span class="far fa-trash-alt"></span></button></td>
// <td class="filespath"><input name="file:`+full_name+`" type="text" style="width:500px" value='`+full_name+`'> </td>
// {% if obj_type == "submission" %}
// <td><img style="height:30px; width:30px" src="../newfilecheck/?file_path=`+encode_full_name+`"/></td>
// {% endif %}
// </tr>`);
{% endcomment %}

this.removeFile(file);
});
this.on("processing", function(file, xhr, data) {
{% comment %} // var deleteallbutton = document.getElementById("deleteallbutton");
// if(deleteallbutton) {
// deleteallbutton.disabled = true;
// }
// var downloadallbutton = document.getElementById("downloadallbutton");
// if(downloadallbutton) {
// downloadallbutton.disabled = true;
// }
// var submitbutton = document.getElementById("submit_fileslist");
// if(submitbutton) {
// submitbutton.disabled = true;
// }

//deleteallbutton.style.display = "none";
{% endcomment %}
});
this.on("queuecomplete", function(file, xhr, data) {
{% comment %} // var deleteallbutton = document.getElementById("deleteallbutton");
// if(deleteallbutton) {
// deleteallbutton.disabled = false;
// deleteallbutton.style.visibility = 'visible';
// }
// var downloadallbutton = document.getElementById("downloadallbutton");
// if(downloadallbutton) {
// downloadallbutton.disabled = false;
// downloadallbutton.style.visibility = 'visible';
// }
// var submitbutton = document.getElementById("submit_fileslist");
// if(submitbutton) {
// submitbutton.disabled = false;
// submitbutton.style.visibility = 'visible';
// }
//deleteallbutton.style.display = "block";
{% endcomment %}
});
this.on("sending", function(file, xhr, data) {
// if file is actually a folder
if(file.fullPath){
data.append("fullPath", file.fullPath);
}
});
}
},

accept: function(file, done) {
{% comment %} // similar regex in submit_edit_name_modal_and_reload {% endcomment %}

var regex1 = /[*?"<>|;#:\\\/]/;
var regex2 = /\.\./;

if(file.name.length === 0 || regex1.test(file.name) || regex2.test(file.name)) {
done('File name cannot be empty, contain .. or any of the following characters: * ? " < > | ; # : \ /');
}

//TODO: Set the correct length we need here
else if (file.name.length > 259) {
done("Filename + path exceeds 260 characters!");
}

{% comment %} console.log(file.name) {% endcomment %}
else if (file.fullPath) {
last_slash = file.fullPath.lastIndexOf('/');
folder_path = file.fullPath.slice(0, last_slash);
console.log(folder_path)
regex_path = /[^a-zA-Z0-9 /_\-\.]/;
//console.log(regex_path.test(folder_path));
if (file.fullPath > 260) {
done("Filename + path exceeds 260 characters!");
}
else if(regex2.test(folder_path)) {
done("File folder cannot contain ..");
}
else if(regex_path.test(folder_path)){
done("File folder can only contain the alphanumerics, _ - . and whitespace");
}
else {
done();
}
}

else {
done();
}
},
};
</script>
{% endif %}
Loading

0 comments on commit 94881de

Please sign in to comment.