diff --git a/app/models.py b/app/models.py index 38c440204f013428e215c6fb4eca1f82d0386b6e..d16bcfae81445cab6aad45e4a7d95d36035905e0 100644 --- a/app/models.py +++ b/app/models.py @@ -1331,6 +1331,8 @@ class Corpus(HashidMixin, db.Model): @db.event.listens_for(Job, 'after_delete') @db.event.listens_for(JobInput, 'after_delete') @db.event.listens_for(JobResult, 'after_delete') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_delete') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_delete') def ressource_after_delete(mapper, connection, ressource): jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}] room = f'users.{ressource.user_hashid}' @@ -1344,6 +1346,8 @@ def ressource_after_delete(mapper, connection, ressource): @db.event.listens_for(Job, 'after_insert') @db.event.listens_for(JobInput, 'after_insert') @db.event.listens_for(JobResult, 'after_insert') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_insert') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_insert') def ressource_after_insert_handler(mapper, connection, ressource): value = ressource.to_json_serializeable() for attr in mapper.relationships: @@ -1360,6 +1364,8 @@ def ressource_after_insert_handler(mapper, connection, ressource): @db.event.listens_for(Job, 'after_update') @db.event.listens_for(JobInput, 'after_update') @db.event.listens_for(JobResult, 'after_update') +@db.event.listens_for(SpaCyNLPPipelineModel, 'after_update') +@db.event.listens_for(TesseractOCRPipelineModel, 'after_update') def ressource_after_update_handler(mapper, connection, ressource): jsonpatch = [] for attr in db.inspect(ressource).attrs: diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js index f3959a79a43bae43ebe7a6e05421801e5354e5ee..bf637ccac47101860702b81b686a36bb36dc552a 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js @@ -18,39 +18,33 @@ class SpaCyNLPPipelineModelList extends ResourceList { }); app.getUser(this.userId).then((user) => { this.add(Object.values(user.spacy_nlp_pipeline_models)); - for (let uncheckedCheckbox of this.listjs.list.querySelectorAll('input[data-checked="True"]')) { - uncheckedCheckbox.setAttribute('checked', ''); - } - if (user.role.name !== ('Administrator' || 'Contributor')) { - for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) { - switchElement.setAttribute('disabled', ''); - } - } this.isInitialized = true; }); } get item() { - return ` - <tr class="list-item clickable hoverable"> - <td><b><span class="title"></span> <span class="version"></span></b><br><i><span class="description"></span></i></td> - <td><a class="publisher-url"><span class="publisher"></span></a> (<span class="publishing-year"></span>)<br><a class="publishing-url"><span class="publishing-url-2"></span></a></td> - <td> - <div class="list-action-trigger switch center-align" data-list-action="share-request"> - <span class="share"></span> - <label> - <input type="checkbox" class="is_public"> - <span class="lever"></span> - public - </label> - </div> - </td> - <td class="right-align"> - <a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a> - <a class="list-action-trigger btn-floating service-color darken waves-effect waves-light service-2" data-list-action="view"><i class="material-icons">send</i></a> - </td> - </tr> - `.trim(); + return (values) => { + return ` + <tr class="list-item clickable hoverable"> + <td><b><span class="title"></span> <span class="version"></span></b><br><i><span class="description"></span></i></td> + <td><a class="publisher-url"><span class="publisher"></span></a> (<span class="publishing-year"></span>)<br><a class="publishing-url publishing-url-2"></a></td> + <td> + <div class="list-action-trigger switch center-align" data-list-action="share-request"> + <span class="share"></span> + <label> + <input class="is-public" ${values['is-public'] ? 'checked' : ''} type="checkbox"> + <span class="lever"></span> + public + </label> + </div> + </td> + <td class="right-align"> + <a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a> + <a class="list-action-trigger btn-floating service-color darken waves-effect waves-light service-2" data-list-action="view"><i class="material-icons">send</i></a> + </td> + </tr> + `.trim(); + }; } get valueNames() { @@ -65,8 +59,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { 'publishing-year', 'title', 'title-2', - 'version', - {name: 'is_public', attr: 'data-checked'} + 'version' ]; } @@ -96,6 +89,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { } mapResourceToValue(spaCyNLPPipelineModel) { + console.log(spaCyNLPPipelineModel); return { 'id': spaCyNLPPipelineModel.id, 'creation-date': spaCyNLPPipelineModel.creation_date, @@ -108,7 +102,7 @@ class SpaCyNLPPipelineModelList extends ResourceList { 'title': spaCyNLPPipelineModel.title, 'title-2': spaCyNLPPipelineModel.title, 'version': spaCyNLPPipelineModel.version, - 'is_public': spaCyNLPPipelineModel.is_public ? 'True' : 'False' + 'is-public': spaCyNLPPipelineModel.is_public }; } @@ -117,14 +111,15 @@ class SpaCyNLPPipelineModelList extends ResourceList { } onChange(event) { - let actionSwitchElement = event.target.closest('.list-action-trigger'); - let action = actionSwitchElement.dataset.listAction; - let spaCyNLPPipelineModelElement = event.target.closest('tr'); - let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; - switch (action) { + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + if (listActionElement === null) {return;} + let listAction = listActionElement.dataset.listAction; + switch (listAction) { case 'share-request': { - let is_public = actionSwitchElement.querySelector('input').checked; - Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, is_public); + Utils.spaCyNLPPipelineModelToggleIsPublicRequest(this.userId, itemId); break; } default: { @@ -134,24 +129,23 @@ class SpaCyNLPPipelineModelList extends ResourceList { } onClick(event) { - if (event.target.closest('.action-switch')) { - let userRole = app.data.users[this.userId].role.name; - if (userRole !== ('Administrator' || 'Contributor')) { - app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error'); - } - return; + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + // ignore switch clicks, handle them by the onChange method instead + if (listActionElement.classList.contains('switch')) { + event.preventDefault(); + this.onChange(event); } - let actionButtonElement = event.target.closest('.list-action-trigger'); - let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.listAction; - let spaCyNLPPipelineModelElement = event.target.closest('tr'); - let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; - switch (action) { + let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; + switch (listAction) { case 'delete-request': { - Utils.deleteSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId); + Utils.deleteSpaCyNLPPipelineModelRequest(this.userId, itemId); break; } case 'view': { - window.location.href = `/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`; + window.location.href = `/contributions/spacy-nlp-pipeline-models/${itemId}`; break; } default: { @@ -159,4 +153,41 @@ class SpaCyNLPPipelineModelList extends ResourceList { } } } + + onPatch(patch) { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)`); + let filteredPatch = patch.filter(operation => re.test(operation.path)); + for (let operation of filteredPatch) { + switch(operation.op) { + case 'add': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) {this.add(operation.value);} + break; + } + case 'remove': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) { + let [match, itemId] = operation.path.match(re); + this.remove(itemId); + } + break; + } + case 'replace': { + let re = new RegExp(`^/users/${this.userId}/spacy_nlp_pipeline_models/([A-Za-z0-9]*)/(is_public)$`); + if (re.test(operation.path)) { + let [match, itemId, valueName] = operation.path.match(re); + if (valueName === 'is_public') { + this.listjs.list.querySelector(`.list-item[data-id="${itemId}"] .is-public`).checked = operation.value; + valueName = 'is-public'; + } + this.replace(itemId, valueName, operation.value); + } + break; + } + default: { + break; + } + } + } + } } diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js index c3bc6447ce7c3dc80bd4e0578503f0b533741f3b..ad319041e82a3992436aae2ae2f5c64407940f35 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js @@ -31,26 +31,28 @@ class TesseractOCRPipelineModelList extends ResourceList { } get item() { - return ` - <tr class="list-item clickable hoverable"> - <td><b><span class="title"></span> <span class="version"></span></b><br><i><span class="description"></span></i></td> - <td><a class="publisher-url"><span class="publisher"></span></a> (<span class="publishing-year"></span>)<br><a class="publishing-url"><span class="publishing-url-2"></span></a></td> - <td> - <div class="list-action-trigger switch center-align" data-list-action="share-request"> - <span class="share"></span> - <label> - <input type="checkbox" class="is_public"> - <span class="lever"></span> - public - </label> - </div> - </td> - <td class="right-align"> - <a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a> - <a class="list-action-trigger btn-floating service-color darken waves-effect waves-light service-2" data-list-action="view"><i class="material-icons">send</i></a> - </td> - </tr> - `.trim(); + return (values) => { + return ` + <tr class="list-item clickable hoverable"> + <td><b><span class="title"></span> <span class="version"></span></b><br><i><span class="description"></span></i></td> + <td><a class="publisher-url"><span class="publisher"></span></a> (<span class="publishing-year"></span>)<br><a class="publishing-url"><span class="publishing-url-2"></span></a></td> + <td> + <div class="list-action-trigger switch center-align" data-list-action="share-request"> + <span class="share"></span> + <label> + <input ${values['is-public'] ? 'checked' : ''} class="is-public" type="checkbox"> + <span class="lever"></span> + public + </label> + </div> + </td> + <td class="right-align"> + <a class="list-action-trigger btn-floating red waves-effect waves-light" data-list-action="delete-request"><i class="material-icons">delete</i></a> + <a class="list-action-trigger btn-floating service-color darken waves-effect waves-light service-2" data-list-action="view"><i class="material-icons">send</i></a> + </td> + </tr> + `.trim(); + }; } get valueNames() { @@ -108,7 +110,7 @@ class TesseractOCRPipelineModelList extends ResourceList { 'title': tesseractOCRPipelineModel.title, 'title-2': tesseractOCRPipelineModel.title, 'version': tesseractOCRPipelineModel.version, - 'is_public': tesseractOCRPipelineModel.is_public ? 'True' : 'False' + 'is-public': tesseractOCRPipelineModel.is_public }; } @@ -117,14 +119,15 @@ class TesseractOCRPipelineModelList extends ResourceList { } onChange(event) { - let actionSwitchElement = event.target.closest('.list-action-trigger'); - let action = actionSwitchElement.dataset.listAction; - let tesseractOCRPipelineModelElement = event.target.closest('tr'); - let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; - switch (action) { + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + if (listActionElement === null) {return;} + let listAction = listActionElement.dataset.listAction; + switch (listAction) { case 'share-request': { - let is_public = actionSwitchElement.querySelector('input').checked; - Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, is_public); + Utils.tesseractOCRPipelineModelToggleIsPublicRequest(this.userId, itemId); break; } default: { @@ -134,24 +137,23 @@ class TesseractOCRPipelineModelList extends ResourceList { } onClick(event) { - if (event.target.closest('.action-switch')) { - let userRole = app.data.users[this.userId].role.name; - if (userRole !== ('Administrator' || 'Contributor')) { - app.flash('You need the "Contributor" or "Administrator" role to perform this action.', 'error'); - } - return; + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + // ignore switch clicks, handle them by the onChange method instead + if (listActionElement.classList.contains('switch')) { + event.preventDefault(); + this.onChange(event); } - let actionButtonElement = event.target.closest('.list-action-trigger'); - let action = actionButtonElement === null ? 'view' : actionButtonElement.dataset.listAction; - let tesseractOCRPipelineModelElement = event.target.closest('tr'); - let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; - switch (action) { + let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; + switch (listAction) { case 'delete-request': { - Utils.deleteTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId); + Utils.deleteTesseractOCRPipelineModelRequest(this.userId, itemId); break; } case 'view': { - window.location.href = `/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`; + window.location.href = `/contributions/tesseract-ocr-pipeline-models/${itemId}`; break; } default: { @@ -159,4 +161,41 @@ class TesseractOCRPipelineModelList extends ResourceList { } } } + + onPatch(patch) { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)`); + let filteredPatch = patch.filter(operation => re.test(operation.path)); + for (let operation of filteredPatch) { + switch(operation.op) { + case 'add': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) {this.add(operation.value);} + break; + } + case 'remove': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)$`); + if (re.test(operation.path)) { + let [match, itemId] = operation.path.match(re); + this.remove(itemId); + } + break; + } + case 'replace': { + let re = new RegExp(`^/users/${this.userId}/tesseract_ocr_pipeline_models/([A-Za-z0-9]*)/(is_public)$`); + if (re.test(operation.path)) { + let [match, itemId, valueName] = operation.path.match(re); + if (valueName === 'is_public') { + this.listjs.list.querySelector(`.list-item[data-id="${itemId}"] .is-public`).checked = operation.value; + valueName = 'is-public'; + } + this.replace(itemId, valueName, operation.value); + } + break; + } + default: { + break; + } + } + } + } } diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index d00b613a3b18b1e088885408051ec38e45d5f66e..2e847477cfbb7660b97b65d416ad08d2a996ce3f 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -563,7 +563,7 @@ class Utils { }); } - static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, is_public) { + static tesseractOCRPipelineModelToggleIsPublicRequest(userId, tesseractOCRPipelineModelId, is_public) { return new Promise((resolve, reject) => { let tesseractOCRPipelineModel; try { @@ -572,28 +572,24 @@ class Utils { tesseractOCRPipelineModel = {}; } - let msg = ''; - if (is_public) { - msg = `Model "${tesseractOCRPipelineModel?.title}" is now public`; - } else { - msg = `Model "${tesseractOCRPipelineModel?.title}" is now private`; - } fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - app.flash(msg); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); + .then( + (response) => { + if (response.status === 403) { + app.flash('Forbidden', 'error'); + reject(response); + } + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); }); } - static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, is_public) { + static spaCyNLPPipelineModelToggleIsPublicRequest(userId, spaCyNLPPipelineModelId) { return new Promise((resolve, reject) => { let spaCyNLPPipelineModel; try { @@ -602,24 +598,20 @@ class Utils { spaCyNLPPipelineModel = {}; } - let msg = ''; - if (is_public) { - msg = `Model "${spaCyNLPPipelineModel?.title}" is now public`; - } else { - msg = `Model "${spaCyNLPPipelineModel?.title}" is now private`; - } fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - app.flash(msg); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); + .then( + (response) => { + if (response.status === 403) { + app.flash('Forbidden', 'error'); + reject(response); + } + resolve(response); + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); }); } }