From aa13e25cca3dc5186679e83462f3eb78a1944500 Mon Sep 17 00:00:00 2001 From: Kun Fang Date: Thu, 29 Aug 2024 15:44:12 -0400 Subject: [PATCH 01/12] feat: initialize image upload UI - remove "instrument-detail" page - add two buttons on each instrument card: "Upload new images" & "View on wikidata" - initialize image upload modal UI Refs: #150 --- .../static/instruments/css/index.css | 77 +++++++++ .../static/instruments/js/ImageUpload.js | 69 ++++++++ .../static/instruments/js/InstrumentDetail.js | 41 ----- .../templates/instruments/detail.html | 157 ------------------ .../instruments/includes/masonryView.html | 23 ++- .../instruments/includes/stdView.html | 23 ++- .../instruments/includes/uploadImgModal.html | 42 +++++ .../templates/instruments/index.html | 3 + .../instruments/views/instrument_detail.py | 29 ---- .../apps/instruments/views/instrument_list.py | 4 +- web-app/django/VIM/urls.py | 2 - 11 files changed, 230 insertions(+), 240 deletions(-) create mode 100644 web-app/django/VIM/apps/instruments/static/instruments/js/ImageUpload.js delete mode 100644 web-app/django/VIM/apps/instruments/static/instruments/js/InstrumentDetail.js delete mode 100644 web-app/django/VIM/apps/instruments/templates/instruments/detail.html create mode 100644 web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html delete mode 100644 web-app/django/VIM/apps/instruments/views/instrument_detail.py diff --git a/web-app/django/VIM/apps/instruments/static/instruments/css/index.css b/web-app/django/VIM/apps/instruments/static/instruments/css/index.css index cbd6a2f..155bee5 100644 --- a/web-app/django/VIM/apps/instruments/static/instruments/css/index.css +++ b/web-app/django/VIM/apps/instruments/static/instruments/css/index.css @@ -91,6 +91,83 @@ hr { background-color: #faf1e4; } +.instrument-img-container:hover .instrument-img { + opacity: 0.3; +} + +.instrument-img { + opacity: 1; + display: block; + width: 100%; + height: auto; + transition: 0.5s ease; + backface-visibility: hidden; +} + +.instrument-img-container:hover .middle-button-group { + opacity: 1; +} + +.middle-button-group { + transition: 0.5s ease; + opacity: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + text-align: center; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: space-around; + width: max-content; +} + +.middle-button-group .btn { + background-color: #435334; + border: 1px solid #435334; + color: white; + font-size: 14px; + margin-bottom: 6px; +} + +.middle-button-group .btn:hover { + background-color: #9eb384; + border: 1px solid #9eb384; + color: white; +} + +.modal-btn { + background-color: #435334; + border: 1px solid #435334; +} + +.modal-btn:hover { + background-color: #9eb384; + border: 1px solid #9eb384; +} + +#instrumentNameInModal { + font-weight: bold; + color: #435334; +} + +#previewImages { + display: flex; + flex-wrap: wrap; +} + +#previewImages .col-3 { + margin-bottom: 15px; +} + +#previewImages img { + width: 100%; + height: auto; + border-radius: 5px; +} + .card-title { color: #435334; } diff --git a/web-app/django/VIM/apps/instruments/static/instruments/js/ImageUpload.js b/web-app/django/VIM/apps/instruments/static/instruments/js/ImageUpload.js new file mode 100644 index 0000000..87b9b52 --- /dev/null +++ b/web-app/django/VIM/apps/instruments/static/instruments/js/ImageUpload.js @@ -0,0 +1,69 @@ +function displaySelectedImage(event, elementId) { + const selectedImage = document.getElementById(elementId); + const fileInput = event.target; + + if (fileInput.files && fileInput.files[0]) { + const reader = new FileReader(); + + reader.onload = function (e) { + selectedImage.src = e.target.result; + }; + + reader.readAsDataURL(fileInput.files[0]); + } +} + +// Get the modal element +var uploadImagesModal = document.getElementById('uploadImagesModal'); + +uploadImagesModal.addEventListener('show.bs.modal', function (event) { + var button = event.relatedTarget; + var instrumentName = button.getAttribute('data-instrument-name'); + var instrumentWikidataId = button.getAttribute('data-instrument-wikidata-id'); + var instrumentNameInModal = uploadImagesModal.querySelector( + '#instrumentNameInModal' + ); + instrumentNameInModal.textContent = instrumentName; + + var instrumentWikidataIdInModal = uploadImagesModal.querySelector( + '#instrumentWikidataId' + ); + instrumentWikidataIdInModal.textContent = instrumentWikidataId; +}); + +document + .getElementById('imageFiles') + .addEventListener('change', function (event) { + var previewContainer = document.getElementById('previewImages'); + previewContainer.innerHTML = ''; // Clear existing previews + + var files = event.target.files; + + for (var i = 0; i < files.length; i++) { + var file = files[i]; + + // Ensure that the file is an image + if (file.type.startsWith('image/')) { + var reader = new FileReader(); + + reader.onload = (function (file) { + return function (e) { + var colDiv = document.createElement('div'); + colDiv.className = 'col-3'; + + var img = document.createElement('img'); + img.src = e.target.result; + img.className = 'img-thumbnail'; + img.alt = file.name; + img.style.maxHeight = '150px'; + img.style.objectFit = 'cover'; + + colDiv.appendChild(img); + previewContainer.appendChild(colDiv); + }; + })(file); + + reader.readAsDataURL(file); + } + } + }); diff --git a/web-app/django/VIM/apps/instruments/static/instruments/js/InstrumentDetail.js b/web-app/django/VIM/apps/instruments/static/instruments/js/InstrumentDetail.js deleted file mode 100644 index c868191..0000000 --- a/web-app/django/VIM/apps/instruments/static/instruments/js/InstrumentDetail.js +++ /dev/null @@ -1,41 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - const editButtons = document.querySelectorAll('.btn.edit'); - const cancelButtons = document.querySelectorAll('.btn.cancel'); - const publishButtons = document.querySelectorAll('.btn.publish'); - - editButtons.forEach((button) => { - button.addEventListener('click', function () { - const parentTd = this.closest('td'); - parentTd.querySelector('.view-field').style.display = 'none'; - parentTd.querySelector('.edit-field').style.display = 'inline-block'; - parentTd.querySelector('.btn.cancel').style.display = 'inline-block'; - parentTd.querySelector('.btn.publish').style.display = 'inline-block'; - this.style.display = 'none'; - }); - }); - - cancelButtons.forEach((button) => { - button.addEventListener('click', function () { - const parentTd = this.closest('td'); - parentTd.querySelector('.view-field').style.display = 'inline'; - parentTd.querySelector('.edit-field').style.display = 'none'; - parentTd.querySelector('.btn.edit').style.display = 'inline-block'; - parentTd.querySelector('.btn.publish').style.display = 'none'; - this.style.display = 'none'; - }); - }); - - publishButtons.forEach((button) => { - button.addEventListener('click', function () { - const parentTd = this.closest('td'); - const newValue = parentTd.querySelector('.edit-field').value; - // TODO: request to update the value on the server - parentTd.querySelector('.view-field').textContent = newValue; - parentTd.querySelector('.view-field').style.display = 'inline'; - parentTd.querySelector('.edit-field').style.display = 'none'; - parentTd.querySelector('.btn.edit').style.display = 'inline-block'; - this.style.display = 'none'; - parentTd.querySelector('.btn.cancel').style.display = 'none'; - }); - }); -}); diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/detail.html b/web-app/django/VIM/apps/instruments/templates/instruments/detail.html deleted file mode 100644 index 2fe1dc7..0000000 --- a/web-app/django/VIM/apps/instruments/templates/instruments/detail.html +++ /dev/null @@ -1,157 +0,0 @@ -{% extends "base.html" %} - -{% load static %} - -{% block title %} - Instrument Detail -{% endblock title %} - -{% block css_files %} - - -{% endblock css_files %} - -{% block content %} -
-
-

- {% for instrumentname in instrument_names %} - {% if instrumentname.language.en_label == active_language.en_label %} - {{ instrumentname.name|title }} - {% endif %} - {% endfor %} -

-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Wikidata ID - -
Hornbostel-Sachs Classification -
-
- {{ instrument.hornbostel_sachs_class }} -
-
-
MIMO Classification - -
Instrument Names in Different Languages - - - - - - - - - - {% for instrumentname in instrument_names %} - - - - - - {% endfor %} - -
- Language - - Name - - Source -
-
-
- {{ instrumentname.language.en_label }} - -
-
- - - -
-
-
-
-
- {{ instrumentname.name }} - -
-
- - - -
-
-
-
-
- {{ instrumentname.source_name }} - -
-
- - - -
-
-
-
Image -
- -
-
-
-
-
-{% endblock content %} - -{% block scripts %} - -{% endblock scripts %} diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html index d920518..0149446 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html @@ -4,11 +4,24 @@ id="masonry-view"> {% for instrument in instruments %}
- +
- instrument thumbnail +

{% for instrumentname in instrument.instrumentname_set.all %} @@ -17,7 +30,7 @@

- +
{% endfor %} diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html index a645148..ec08ba5 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html @@ -5,12 +5,25 @@ style="display:none"> {% for instrument in instruments %}
- +
{% endfor %} diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html new file mode 100644 index 0000000..caab7aa --- /dev/null +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html @@ -0,0 +1,42 @@ +{% load static %} + + + diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/index.html b/web-app/django/VIM/apps/instruments/templates/instruments/index.html index 42bc80a..b83aefa 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/index.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/index.html @@ -103,6 +103,8 @@

+ {% include "instruments/includes/uploadImgModal.html" %} + + View on Wikidata +
+

{% for instrumentname in instrument.instrumentname_set.all %} @@ -17,7 +30,7 @@

- + {% endfor %} diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html index a645148..ec08ba5 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html @@ -5,12 +5,25 @@ style="display:none"> {% for instrument in instruments %} {% endfor %} diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html new file mode 100644 index 0000000..caab7aa --- /dev/null +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/uploadImgModal.html @@ -0,0 +1,42 @@ +{% load static %} + + + diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/index.html b/web-app/django/VIM/apps/instruments/templates/instruments/index.html index 42bc80a..b83aefa 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/index.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/index.html @@ -103,6 +103,8 @@

+ {% include "instruments/includes/uploadImgModal.html" %} +
diff --git a/web-app/django/VIM/apps/main/templates/main/index.html b/web-app/django/VIM/apps/main/templates/main/index.html index 845d441..45a37a6 100644 --- a/web-app/django/VIM/apps/main/templates/main/index.html +++ b/web-app/django/VIM/apps/main/templates/main/index.html @@ -41,12 +41,12 @@

From 364ff92ca1402d172ee19dd79ea0e6a822234b84 Mon Sep 17 00:00:00 2001 From: Kun Fang Date: Fri, 11 Oct 2024 10:40:40 -0400 Subject: [PATCH 06/12] feat: add new instrument name - add new instrument name, source, description, and alias by a modal; - optionally publish to wikidata; Refs: #163 --- .../static/instruments/js/AddName.js | 373 ++++++++++++++++++ .../instruments/includes/addNameModal.html | 105 +++++ .../instruments/includes/masonryView.html | 26 +- .../instruments/includes/stdView.html | 26 +- .../templates/instruments/index.html | 2 + .../apps/instruments/views/instrument_list.py | 157 +++++++- .../VIM/apps/instruments/views/wiki_apis.py | 127 ++++++ web-app/django/VIM/urls.py | 6 +- 8 files changed, 803 insertions(+), 19 deletions(-) create mode 100644 web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js create mode 100644 web-app/django/VIM/apps/instruments/templates/instruments/includes/addNameModal.html create mode 100644 web-app/django/VIM/apps/instruments/views/wiki_apis.py diff --git a/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js b/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js new file mode 100644 index 0000000..41938b0 --- /dev/null +++ b/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js @@ -0,0 +1,373 @@ +// Get the modal element +var addNameModal = document.getElementById('addNameModal'); + +addNameModal.addEventListener('show.bs.modal', function (event) { + var button = event.relatedTarget; + var instrumentName = button.getAttribute('data-instrument-name'); + var instrumentWikidataId = button.getAttribute('data-instrument-wikidata-id'); + var instrumentNameInModal = addNameModal.querySelector( + '#instrumentNameInModal' + ); + instrumentNameInModal.textContent = instrumentName; + + var instrumentWikidataIdInModal = addNameModal.querySelector( + '#instrumentWikidataIdInModal' + ); + instrumentWikidataIdInModal.textContent = instrumentWikidataId; +}); + +// the number of rows in the modal +let rowIndex = 1; + +// Function to validate that the user has selected a valid language from the datalist +function isValidLanguage(inputElement) { + const datalistId = inputElement.getAttribute('list'); + const datalist = document.getElementById(datalistId); + const options = datalist.querySelectorAll('option'); + + // Check if the input value matches any option value in the datalist + for (let option of options) { + if (option.value === inputElement.value) { + return true; // Valid language selected + } + } + return false; // Invalid language input +} + +// Function to check if a name already exists in Wikidata for the given language +async function checkNameInWikidata(wikidataId, languageCode, languageLabel) { + const sparqlQuery = ` + SELECT ?nameLabel WHERE { + wd:${wikidataId} rdfs:label ?nameLabel . + FILTER(LANG(?nameLabel) = "${languageCode}") + } LIMIT 1 + `; + + const endpointUrl = 'https://query.wikidata.org/sparql'; + const queryUrl = `${endpointUrl}?query=${encodeURIComponent( + sparqlQuery + )}&format=json`; + + try { + const response = await fetch(queryUrl); + const data = await response.json(); + + if (data.results.bindings.length > 0) { + return { exists: true, name: data.results.bindings[0].nameLabel.value }; + } else { + return { exists: false }; + } + } catch (error) { + console.error('Error querying Wikidata:', error); + throw new Error('Wikidata query failed'); + } +} + +// Reusable function to create a new row +function createRow(index) { + const row = document.createElement('div'); + row.classList.add('row', 'mb-1', 'name-row'); + + // Create datalist options dynamically using the global languages variable + let datalistOptions = languages + .map( + (language) => ` + + ` + ) + .join(''); + + row.innerHTML = ` +
+ + + + ${datalistOptions} + +
This instrument does not have a name in this language yet. You can add a new name.
+
This instrument already has a name in this language.
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ `; + + // Add event listener for remove button + row.querySelector('.remove-row-btn').addEventListener('click', function () { + row.remove(); + updateRemoveButtons(); // Ensure correct behavior when rows are removed + }); + + return row; +} + +// Function to update remove button visibility based on the number of rows +function updateRemoveButtons() { + const rows = document.querySelectorAll('.name-row'); + rows.forEach((row, index) => { + const removeButton = row.querySelector('.remove-row-btn'); + // Show the remove button only if there are more than one row + if (rows.length > 1) { + removeButton.style.display = 'inline-block'; + } else { + removeButton.style.display = 'none'; // Hide the button if only one row remains + } + }); +} + +// Function to validate and check all rows on form submission +document + .getElementById('addNameForm') + .addEventListener('submit', async function (event) { + event.preventDefault(); // Prevent form submission + + const nameRows = document.querySelectorAll('.name-row'); + let allValid = true; + let publishResults = ''; // Collect the results for confirmation + + // Iterate over each row and check if the name already exists in Wikidata + for (let row of nameRows) { + const languageInput = row.querySelector('input[list]'); + const nameInput = row.querySelector('.name-input input[type="text"]'); + const sourceInput = row.querySelector('.source-input input[type="text"]'); + const descriptionInput = row.querySelector( + '.description-input input[type="text"]' + ); + const aliasInput = row.querySelector('.alias-input input[type="text"]'); + + const languageCode = languageInput.value; + const selectedOption = row.querySelector( + `option[value="${languageCode}"]` + ); + const languageLabel = selectedOption ? selectedOption.textContent : ''; + + // get feedback elements for valid and invalid inputs respectively for language and name + const languageFeedbackValid = row.querySelector( + '.language-input .valid-feedback' + ); + const languageFeedbackInvalid = row.querySelector( + '.language-input .invalid-feedback' + ); + const nameFeedbackInvalid = row.querySelector( + '.name-input .invalid-feedback' + ); + const sourceFeedbackInvalid = row.querySelector( + '.source-input .invalid-feedback' + ); + + const wikidataId = document + .getElementById('instrumentWikidataIdInModal') + .textContent.trim(); + + if (!isValidLanguage(languageInput)) { + languageInput.classList.add('is-invalid'); + languageFeedbackInvalid.textContent = + 'Please select a valid language from the list.'; + allValid = false; + continue; + } + + try { + const result = await checkNameInWikidata( + wikidataId, + languageCode, + languageLabel + ); + if (result.exists) { + languageInput.classList.add('is-invalid'); + languageInput.classList.remove('is-valid'); + languageFeedbackInvalid.textContent = `This instrument already has a name in ${languageLabel} (${languageCode}): ${result.name}`; + allValid = false; + } else { + languageInput.classList.add('is-valid'); + languageInput.classList.remove('is-invalid'); + languageFeedbackValid.textContent = `This instrument does not have a name in ${languageLabel} (${languageCode}) yet. You can add a new name.`; + + // check if name is empty + if (nameInput.value.trim() === '') { + nameInput.classList.add('is-invalid'); + nameInput.classList.remove('is-valid'); + nameFeedbackInvalid.textContent = + 'Please enter a name for this instrument in the selected language.'; + allValid = false; + } else { + nameInput.classList.add('is-valid'); + nameInput.classList.remove('is-invalid'); + } + + // check if source is empty + if (sourceInput.value.trim() === '') { + sourceInput.classList.add('is-invalid'); + sourceInput.classList.remove('is-valid'); + sourceFeedbackInvalid.textContent = + 'Please enter the source of this name.'; + allValid = false; + } else { + sourceInput.classList.add('is-valid'); + sourceInput.classList.remove('is-invalid'); + } + + // Add the result to the confirmation message + publishResults += `
${languageLabel} (${languageCode}): ${nameInput.value}; Source: ${sourceInput.value}; Description: ${descriptionInput.value}; Alias: ${aliasInput.value}`; + } + } catch (error) { + displayMessage( + 'There was an error checking Wikidata. Please try again later.', + 'danger' + ); + return; // Stop further processing + } + } + + // If all rows are valid, show the confirmation modal + if (allValid) { + document.getElementById( + 'publishResults' + ).innerHTML = `Your final publish results will be:
${publishResults}`; + const confirmationModal = new bootstrap.Modal( + document.getElementById('confirmationModal') + ); + confirmationModal.show(); + } + }); + +// Function to reset the modal and ensure only one row is present +function resetModal() { + const nameRows = document.getElementById('nameRows'); + nameRows.innerHTML = ''; // Clear all rows + nameRows.appendChild(createRow(1)); // Add initial row + updateRemoveButtons(); // Ensure remove buttons are updated on reset + rowIndex = 1; // Reset row index +} + +// Fetch languages when the modal is loaded +document.addEventListener('DOMContentLoaded', async () => { + resetModal(); +}); + +// Add a new row when the 'Add another row' button is clicked +document.getElementById('addRowBtn').addEventListener('click', function () { + rowIndex++; + const nameRows = document.getElementById('nameRows'); + nameRows.appendChild(createRow(rowIndex)); + updateRemoveButtons(); // Update remove buttons after adding a new row +}); + +// Reset the modal when hidden +document + .getElementById('addNameModal') + .addEventListener('hide.bs.modal', resetModal); + +// Function to handle confirm publish action +document + .getElementById('confirmPublishBtn') + .addEventListener('click', function () { + const wikidataId = document + .getElementById('instrumentWikidataIdInModal') + .textContent.trim(); + const entries = []; + + // Collect the data to publish + const nameRows = document.querySelectorAll('.name-row'); + nameRows.forEach((row) => { + const languageInput = row.querySelector('input[list]'); + const nameInput = row.querySelector('.name-input input[type="text"]'); + const sourceInput = row.querySelector('.source-input input[type="text"]'); + const descriptionInput = row.querySelector( + '.description-input input[type="text"]' + ); + const aliasInput = row.querySelector('.alias-input input[type="text"]'); + + const languageCode = languageInput.value; + const nameValue = nameInput.value; + const sourceValue = sourceInput.value; + const descriptionValue = descriptionInput.value; + const aliasValue = aliasInput.value; + + if ( + languageCode && + nameValue && + sourceValue && + descriptionValue && + aliasValue + ) { + entries.push({ + language: languageCode, + name: nameValue, + source: sourceValue, + description: descriptionValue, + alias: aliasValue, + }); + } + }); + + // Check if the user wants to publish to Wikidata + const publishToWikidata = document.getElementById( + 'publishToWikidataCheckbox' + ).checked; + + // Publish data to our database and then to Wikidata + fetch('/publish_name/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]') + .value, + }, + body: JSON.stringify({ + wikidata_id: wikidataId, + entries: entries, + publish_to_wikidata: publishToWikidata, + }), + }) + .then((response) => response.json()) + .then((data) => { + if (data.status === 'success') { + alert('Data published successfully!'); + // Close both modals + const addNameModal = bootstrap.Modal.getInstance( + document.getElementById('addNameModal') + ); + const confirmationModal = bootstrap.Modal.getInstance( + document.getElementById('confirmationModal') + ); + + if (addNameModal) { + addNameModal.hide(); // Close the 'Add Name' modal + } + + if (confirmationModal) { + confirmationModal.hide(); // Close the 'Confirmation' modal + } + } else { + alert('Error: ' + data.message); + } + }) + .catch((error) => { + alert('An error occurred while publishing the data: ' + error.message); + }); + }); diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/addNameModal.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/addNameModal.html new file mode 100644 index 0000000..f266d3a --- /dev/null +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/addNameModal.html @@ -0,0 +1,105 @@ +{% load static %} + + + + + + diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html index 0149446..28041e2 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html @@ -9,14 +9,24 @@
instrument thumbnail
- + {% if user.is_authenticated %} + + + {% endif %} View on Wikidata diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html index ec08ba5..a528eb7 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html @@ -11,14 +11,24 @@
instrument thumbnail
- + {% if user.is_authenticated %} + + + {% endif %} View on Wikidata diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/index.html b/web-app/django/VIM/apps/instruments/templates/instruments/index.html index 69b56c7..159af10 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/index.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/index.html @@ -110,6 +110,7 @@

+ {% include "instruments/includes/addNameModal.html" %} {% include "instruments/includes/uploadImgModal.html" %} + + {% endif %} + View on Wikidata +
+
diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html index 28041e2..f0e93c6 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/masonryView.html @@ -6,37 +6,11 @@
-
- instrument thumbnail -
- {% if user.is_authenticated %} - - - {% endif %} - View on Wikidata -
-
+ {% include "instruments/includes/instrumentContainer.html" %} +

- {% for instrumentname in instrument.instrumentname_set.all %} - {{ instrumentname.name|title }} - {% endfor %} + {{ instrument.instrumentname_set.all.first.name|title }}

diff --git a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html index a528eb7..c9c4825 100644 --- a/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html +++ b/web-app/django/VIM/apps/instruments/templates/instruments/includes/stdView.html @@ -8,38 +8,12 @@
-
- instrument thumbnail -
- {% if user.is_authenticated %} - - - {% endif %} - View on Wikidata -
-
+ {% include "instruments/includes/instrumentContainer.html" %} +

- {% for instrumentname in instrument.instrumentname_set.all %} - {{ instrumentname.name|title }} - {% endfor %} + {{ instrument.instrumentname_set.all.first.name|title }}

diff --git a/web-app/django/VIM/apps/instruments/views/instrument_list.py b/web-app/django/VIM/apps/instruments/views/instrument_list.py index 9ffd947..9507fa5 100644 --- a/web-app/django/VIM/apps/instruments/views/instrument_list.py +++ b/web-app/django/VIM/apps/instruments/views/instrument_list.py @@ -1,17 +1,10 @@ """ Django views for the instrument list page. """ -import json from django.db.models import Prefetch from django.db.models.query import QuerySet -from django.views.decorators.csrf import csrf_exempt from django.views.generic import ListView -from django.http import JsonResponse import requests from VIM.apps.instruments.models import Instrument, Language, InstrumentName -from VIM.apps.instruments.views.wiki_apis import ( - get_csrf_token, - add_info_to_wikidata_entity, -) class InstrumentList(ListView): @@ -111,149 +104,3 @@ def get_queryset(self) -> QuerySet[Instrument]: return Instrument.objects.select_related("thumbnail").prefetch_related( instrumentname_prefetch_manager ) - - -# Django view to handle publishing to Wikidata -@csrf_exempt -def publish_name(request): - """ - View to publish new instrument names to Wikidata. - - This view expects a POST request with the following JSON body like this: - { - "wikidata_id": "Q12345", - "entries": [ - { - "language": "en", - "name": "English label", - "source": "Source name", - "description": "Description", - "alias": "Alias" - }, - { - "language": "fr", - "name": "French label", - ... - } - ], - "publish_to_wikidata": true - } - - The view will publish the provided entries to the Wikidata entity with the given ID. - - Returns: - JsonResponse: JSON response with status and message - """ - - if request.method == "POST": - # Parse the JSON request body - data = json.loads(request.body) - username = "YOUR_USERNAME" # Replace with actual username - password = "YOUR_PASSWORD" # Replace with actual credentials - wikidata_id = data.get("wikidata_id") - entries = data.get("entries") - publish_to_wikidata = data.get("publish_to_wikidata", False) - - if not wikidata_id or not entries: - return JsonResponse({"status": "error", "message": "Missing required data"}) - - try: - # Fetch the instrument from the database - instrument = Instrument.objects.get(wikidata_id=wikidata_id) - - # Process each entry: save locally, and conditionally publish to Wikidata - if publish_to_wikidata: - csrf_token, lgusername, error_message = get_csrf_token( - username, password - ) - - # Check if there was an error - if error_message: - return JsonResponse( - { - "status": "error", - "message": f"Failed to get CSRF token: {error_message}", - } - ) - for entry in entries: - language_code = entry["language"] - name = entry["name"] - source = entry["source"] - description = entry["description"] - alias = entry["alias"] - - # Get the language object - language = Language.objects.get(wikidata_code=language_code) - - # Optionally publish to Wikidata - if publish_to_wikidata: - # Publish the new label to Wikidata - response_label = add_info_to_wikidata_entity( - "wbsetlabel", - csrf_token, - wikidata_id, - name, - language_code, - lgusername, - ) - if "error" in response_label: - return JsonResponse( - { - "status": "error", - "message": f"Failed to publish label: {response_label}", - } - ) - - # Publish the new description to Wikidata - response_desc = add_info_to_wikidata_entity( - "wbsetdescription", - csrf_token, - wikidata_id, - description, - language_code, - lgusername, - ) - if "error" in response_desc: - return JsonResponse( - { - "status": "error", - "message": f"Failed to publish description: {response_desc}", - } - ) - - # Publish the new alias to Wikidata - response_alias = add_info_to_wikidata_entity( - "wbsetaliases", - csrf_token, - wikidata_id, - alias, - language_code, - lgusername, - ) - if "error" in response_alias: - return JsonResponse( - { - "status": "error", - "message": f"Failed to publish alias: {response_alias}", - } - ) - - # Save to the local database - InstrumentName.objects.create( - instrument=instrument, - language=language, - name=name, - source_name=source, - description=description, - alias=alias, - ) - - return JsonResponse( - { - "status": "success", - "message": "Data saved successfully!", - } - ) - except Instrument.DoesNotExist: - return JsonResponse({"status": "error", "message": "Instrument not found"}) - return JsonResponse({"status": "error", "message": "Invalid request method"}) diff --git a/web-app/django/VIM/apps/instruments/views/publish_name.py b/web-app/django/VIM/apps/instruments/views/publish_name.py new file mode 100644 index 0000000..0994fde --- /dev/null +++ b/web-app/django/VIM/apps/instruments/views/publish_name.py @@ -0,0 +1,169 @@ +""" Django view to handle publishing to Wikidata. """ + +import json +from django.views.decorators.csrf import csrf_protect +from django.http import JsonResponse +from VIM.apps.instruments.models import Instrument, Language, InstrumentName +from VIM.apps.instruments.views.wiki_apis import ( + get_csrf_token, + add_info_to_wikidata_entity, +) + + +@csrf_protect +def publish_name(request): + """ + View to publish new instrument names to Wikidata. + + This view expects a POST request with the following JSON body like this: + { + "wikidata_id": "Q12345", + "entries": [ + { + "language": "en", + "name": "English label", + "source": "Source name", + "description": "Description", + "alias": "Alias" + }, + { + "language": "fr", + "name": "French label", + ... + } + ], + "publish_to_wikidata": true + } + + The view will publish the provided entries to the Wikidata entity with the given ID. + + Returns: + JsonResponse: JSON response with status and message + """ + + if request.method == "POST": + # Parse the JSON request body + data = json.loads(request.body) + username = "YOUR_USERNAME" # Replace with actual username + password = "YOUR_PASSWORD" # Replace with actual credentials + wikidata_id = data.get("wikidata_id") + entries = data.get("entries") + publish_to_wikidata = data.get("publish_to_wikidata", False) + + if not wikidata_id or not entries: + return JsonResponse( + { + "status": "error", + "message": "Missing required data", + } + ) + + try: + # Fetch the instrument from the database + instrument = Instrument.objects.get(wikidata_id=wikidata_id) + + # Process each entry: save locally, and conditionally publish to Wikidata + if publish_to_wikidata: + csrf_token, lgusername, error_message = get_csrf_token( + username, password + ) + + # Check if there was an error + if error_message: + return JsonResponse( + { + "status": "error", + "message": f"Failed to get CSRF token: {error_message}", + } + ) + for entry in entries: + language_code = entry["language"] + name = entry["name"] + source = entry["source"] + description = entry["description"] + alias = entry["alias"] + + # Get the language object + language = Language.objects.get(wikidata_code=language_code) + + # Optionally publish to Wikidata + if publish_to_wikidata: + # Publish the new label to Wikidata + response_label = add_info_to_wikidata_entity( + "wbsetlabel", + csrf_token, + wikidata_id, + name, + language_code, + lgusername, + ) + if "error" in response_label: + return JsonResponse( + { + "status": "error", + "message": f"Failed to publish label: {response_label}", + } + ) + + # Publish the new description to Wikidata + response_desc = add_info_to_wikidata_entity( + "wbsetdescription", + csrf_token, + wikidata_id, + description, + language_code, + lgusername, + ) + if "error" in response_desc: + return JsonResponse( + { + "status": "error", + "message": f"Failed to publish description: {response_desc}", + } + ) + + # Publish the new alias to Wikidata + response_alias = add_info_to_wikidata_entity( + "wbsetaliases", + csrf_token, + wikidata_id, + alias, + language_code, + lgusername, + ) + if "error" in response_alias: + return JsonResponse( + { + "status": "error", + "message": f"Failed to publish alias: {response_alias}", + } + ) + + # Save to the local database + # for "name" in "lang" with "description" + InstrumentName.objects.create( + instrument=instrument, + language=language, + name=name, + source_name=source, + description=description, + ) + # if alias is provided, save to the local database + if alias: + # for "alias" in language "lang" + InstrumentName.objects.create( + instrument=instrument, + language=language, + name=alias, + source_name=source, + ) + + return JsonResponse( + { + "status": "success", + "message": "Data saved successfully!", + } + ) + except Instrument.DoesNotExist: + return JsonResponse({"status": "error", "message": "Instrument not found"}) + return JsonResponse({"status": "error", "message": "Invalid request method"}) diff --git a/web-app/django/VIM/urls.py b/web-app/django/VIM/urls.py index ed3b98e..88d36f7 100644 --- a/web-app/django/VIM/urls.py +++ b/web-app/django/VIM/urls.py @@ -19,10 +19,8 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.i18n import i18n_patterns -from VIM.apps.instruments.views.instrument_list import ( - InstrumentList, - publish_name, -) +from VIM.apps.instruments.views.instrument_list import InstrumentList +from VIM.apps.instruments.views.publish_name import publish_name urlpatterns = i18n_patterns( path("admin/", admin.site.urls), From bac2cc0d9f890665315aa1c386f1a8489c734cf1 Mon Sep 17 00:00:00 2001 From: Kun Fang Date: Mon, 21 Oct 2024 12:41:13 -0400 Subject: [PATCH 12/12] fix: refactor code for "add instrument name" Ref: #163 --- .../static/instruments/js/AddName.js | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js b/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js index 66a6f96..075b288 100644 --- a/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js +++ b/web-app/django/VIM/apps/instruments/static/instruments/js/AddName.js @@ -304,24 +304,22 @@ document const languageCode = languageInput.value; const nameValue = nameInput.value; const sourceValue = sourceInput.value; - const descriptionValue = descriptionInput.value; - const aliasValue = aliasInput.value; - - if ( - languageCode && - nameValue && - sourceValue && - descriptionValue && - aliasValue - ) { - entries.push({ - language: languageCode, - name: nameValue, - source: sourceValue, - description: descriptionValue, - alias: aliasValue, - }); - } + const descriptionValue = descriptionInput.value || ''; + const aliasValue = aliasInput.value || ''; + + console.log('languageCode: ', languageCode); + console.log('nameValue: ', nameValue); + console.log('sourceValue: ', sourceValue); + console.log('descriptionValue: ', descriptionValue); + console.log('aliasValue: ', aliasValue); + + entries.push({ + language: languageCode, + name: nameValue, + source: sourceValue, + description: descriptionValue, + alias: aliasValue, + }); }); // Check if the user wants to publish to Wikidata