From 29a11fab26deb152f46f9bc2cf72f6c2ae1ba18d Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Mon, 28 Jul 2025 11:53:36 -0400 Subject: [PATCH 001/121] fix: remove Google Tag installation & add GTM refs: #314 --- web-app/django/VIM/templates/base.html | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/web-app/django/VIM/templates/base.html b/web-app/django/VIM/templates/base.html index 642ef72d..a6afea15 100644 --- a/web-app/django/VIM/templates/base.html +++ b/web-app/django/VIM/templates/base.html @@ -15,15 +15,14 @@ - - - + {% vite_asset 'src/main.ts' %} {% block ts_files %} @@ -34,6 +33,13 @@ + + +
@@ -142,12 +132,6 @@

Name
{{ instrumentname.name }} - - - -
@@ -155,17 +139,27 @@

Source
{{ instrumentname.source_name }} - - - -
{% endfor %} +
+ + {% else %} + Add Instrument Name + {% endif %} +
From 76df18320b7b569ae0cc5d60a38ad4ec5dbf6439 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 19 Jun 2025 14:05:49 -0400 Subject: [PATCH 023/121] fix: adjust form fields to only allow user input for language, name and source refs: #245 --- web-app/django/VIM/apps/instruments/forms/add_name_form.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web-app/django/VIM/apps/instruments/forms/add_name_form.py b/web-app/django/VIM/apps/instruments/forms/add_name_form.py index f81ca7b0..580f4cd5 100644 --- a/web-app/django/VIM/apps/instruments/forms/add_name_form.py +++ b/web-app/django/VIM/apps/instruments/forms/add_name_form.py @@ -4,11 +4,10 @@ class NameForm(forms.ModelForm): class Meta: model = InstrumentName - fields = ['instrument', 'language', 'name', 'source_name', 'is_alias'] + fields = ['language', 'name', 'source_name'] widgets = { 'message': forms.Textarea(attrs={'rows': 4, 'cols': 40}), } help_texts = { - 'source_name': '', - 'is_alias': '', + 'source_name': '' } \ No newline at end of file From 74368584c4f20f9816a20cff049c590a3e0d69c6 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 19 Jun 2025 14:13:31 -0400 Subject: [PATCH 024/121] feat: define post request for instrument detail page. - with the use of a modal, name adding is no longer on a new page - define form, languages and instrument name in active language contexts refs:#245 --- .../instruments/views/instrument_detail.py | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/web-app/django/VIM/apps/instruments/views/instrument_detail.py b/web-app/django/VIM/apps/instruments/views/instrument_detail.py index 1aeb9ffa..3ec79165 100644 --- a/web-app/django/VIM/apps/instruments/views/instrument_detail.py +++ b/web-app/django/VIM/apps/instruments/views/instrument_detail.py @@ -1,6 +1,9 @@ from django.views.generic import DetailView -from VIM.apps.instruments.models import Instrument, Language - +from VIM.apps.instruments.models import Instrument, Language, InstrumentName +from django.http import HttpResponseRedirect +from django.shortcuts import render +from ..forms.add_name_form import NameForm +from django.shortcuts import get_object_or_404 class InstrumentDetail(DetailView): """ @@ -10,6 +13,7 @@ class InstrumentDetail(DetailView): model = Instrument template_name = "instruments/detail.html" context_object_name = "instrument" + form_class = NameForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -26,4 +30,50 @@ def get_context_data(self, **kwargs): if active_language_en else Language.objects.get(en_label="English") # default in English ) + + context["active_instrument_name"] = (context["instrument_names"].filter( + language=context["active_language"])) + + context["languages"] = Language.objects.all() + + context["form"] = NameForm( + initial={ + "instrument": context["instrument"].wikidata_id + } + ) return context + + def post(self, request, **kwargs): + instrument = get_object_or_404(Instrument, pk=kwargs.get("pk")) + form = NameForm(request.POST) + if form.is_valid(): + language_code = form.cleaned_data["language"] + entry = { + "name": form.cleaned_data["name"], + "source": form.cleaned_data["source_name"], + } + # Save instrument name into database + # If the instrument already has a name in specified language, save as alias + if instrument.instrumentname_set.filter(language=language_code).exists(): + InstrumentName.objects.create( + instrument=instrument, + language=language_code, + name=entry["name"], + source_name=entry["source"], + is_alias= True, + contributor=request.user, + ) + # If the instrument does not have a name in specified language, save as primary name + else: + InstrumentName.objects.create( + instrument=instrument, + language=language_code, + name=entry["name"], + source_name=entry["source"], + is_alias= False, + contributor=request.user, + ) + # Redirect to the instrument detail page + return HttpResponseRedirect("/instrument/" + str(instrument.pk) + "/") + + return render(request, self.template_name, {"form": form}) From 90014fe8e3682364629902f68ac424037060bd09 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 19 Jun 2025 14:14:27 -0400 Subject: [PATCH 025/121] feat: define modal template - modal allows user to enter new instrument name of choice refs: #245 --- .../VIM/templates/instruments/add_name.html | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/add_name.html b/web-app/django/VIM/templates/instruments/add_name.html index 40459944..4d4a7b47 100644 --- a/web-app/django/VIM/templates/instruments/add_name.html +++ b/web-app/django/VIM/templates/instruments/add_name.html @@ -1,4 +1,28 @@ -
- {% csrf_token %} {{ form }} - -
+Modal Structure + From 1f9b1be0fbb8b209a04821bcb4dc50bd535b5cc2 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 19 Jun 2025 14:15:47 -0400 Subject: [PATCH 026/121] initialize pre-existing js for adding an instrument name - from branch img-upload/ add-UMIL-data refs: #245 --- .../src/instruments/InstrumentDetail.ts | 504 +++++++++++++++--- 1 file changed, 429 insertions(+), 75 deletions(-) diff --git a/web-app/frontend/src/instruments/InstrumentDetail.ts b/web-app/frontend/src/instruments/InstrumentDetail.ts index 42a9e9a6..294ba7ae 100644 --- a/web-app/frontend/src/instruments/InstrumentDetail.ts +++ b/web-app/frontend/src/instruments/InstrumentDetail.ts @@ -1,82 +1,436 @@ -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 (this: HTMLElement) { - // Find the edit container using our custom class - const editContainer = this.closest('.edit-container') as HTMLElement; - - const viewField = editContainer.querySelector( - '.view-field', - ) as HTMLElement; - const editField = editContainer.querySelector( - '.edit-field', - ) as HTMLElement; - const cancelBtn = editContainer.querySelector( - '.btn-cancel', - ) as HTMLElement; - const publishBtn = editContainer.querySelector( - '.btn-publish', - ) as HTMLElement; - - viewField.classList.add('d-none'); - editField.classList.remove('d-none'); - cancelBtn.classList.remove('d-none'); - publishBtn.classList.remove('d-none'); - this.style.display = 'none'; - }); +// Get the modal element +var addNameModal = document.getElementById('addNameModal'); + +// Handle modal show event +addNameModal.addEventListener('show.bs.modal', function (event) { + var button = event.relatedTarget; + if (button != undefined) { + var instrumentName = button.getAttribute('data-instrument-name'); + var instrumentWikidataId = button.getAttribute( + 'data-instrument-wikidata-id', + ); + addNameModal.querySelector('#instrumentNameInModal').textContent = + instrumentName; + addNameModal.querySelector('#instrumentWikidataIdInModal').textContent = + instrumentWikidataId; + } + +}); + +// Function to store form data +function storeFormData() { + const storedData = {}; + storedData['instrumentName'] = document.getElementById( + 'instrumentNameInModal', + ).textContent; + storedData['wikidata_id'] = document.getElementById( + 'instrumentWikidataIdInModal', + ).textContent; + storedData['nameRows'] = []; + document.querySelectorAll('.name-row').forEach((row) => { + const rowData = { + language: row.querySelector('.language-input input').value, + name: row.querySelector('.name-input input').value, + source: row.querySelector('.source-input input').value, + description: row.querySelector('.description-input input').value, + alias: row.querySelector('.alias-input input').value, + }; + storedData['nameRows'].push(rowData); }); + localStorage.setItem('addNameFormData', JSON.stringify(storedData)); +} + +// Function to restore form data +function restoreFormData(storedData) { + const form = document.getElementById('addNameForm'); + const parsedData = JSON.parse(storedData); + // Restore main form values + for (const key in parsedData) { + if (form.elements[key]) { + form.elements[key].value = parsedData[key]; + } + } - cancelButtons.forEach((button) => { - button.addEventListener('click', function (this: HTMLElement) { - // Find the edit container using our custom class - const editContainer = this.closest('.edit-container') as HTMLElement; - - const viewField = editContainer.querySelector( - '.view-field', - ) as HTMLElement; - const editField = editContainer.querySelector( - '.edit-field', - ) as HTMLElement; - const editBtn = editContainer.querySelector('.btn-edit') as HTMLElement; - const publishBtn = editContainer.querySelector( - '.btn-publish', - ) as HTMLElement; - - viewField.classList.remove('d-none'); - editField.classList.add('d-none'); - editBtn.style.display = 'inline-block'; - publishBtn.classList.add('d-none'); - this.classList.add('d-none'); + // Restore wikidata_id, publish_to_wikidata + document.getElementById('instrumentNameInModal').textContent = + parsedData['instrumentName']; + document.getElementById('instrumentWikidataIdInModal').textContent = + parsedData['wikidata_id']; + + // Restore dynamically added rows + const nameRowsContainer = document.getElementById('nameRows'); + nameRowsContainer.innerHTML = ''; // Clear existing rows + + if (parsedData['nameRows'] && parsedData['nameRows'].length > 0) { + parsedData['nameRows'].forEach((rowData, index) => { + const newRow = createRow(index + 1); + nameRowsContainer.appendChild(newRow); + newRow.querySelector('.language-input input').value = rowData.language; + newRow.querySelector('.name-input input').value = rowData.name; + newRow.querySelector('.source-input input').value = rowData.source; + newRow.querySelector('.description-input input').value = + rowData.description; + newRow.querySelector('.alias-input input').value = rowData.alias; }); + } + + // Check rows + 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"]'); }); - publishButtons.forEach((button) => { - button.addEventListener('click', function (this: HTMLElement) { - // Find the edit container using our custom class - const editContainer = this.closest('.edit-container') as HTMLElement; - - const viewField = editContainer.querySelector( - '.view-field', - ) as HTMLElement; - const editField = editContainer.querySelector( - '.edit-field', - ) as HTMLInputElement; - const editBtn = editContainer.querySelector('.btn-edit') as HTMLElement; - const cancelBtn = editContainer.querySelector( - '.btn-cancel', - ) as HTMLElement; - - const newValue = editField.value; - // TODO: request to update the value on the server - viewField.textContent = newValue; - viewField.classList.remove('d-none'); - editField.classList.add('d-none'); - editBtn.style.display = 'inline-block'; - this.classList.add('d-none'); - cancelBtn.classList.add('d-none'); - }); +} + +// Reset modal on close +document + .getElementById('addNameModal') + .addEventListener('hide.bs.modal', function () { + localStorage.removeItem('addNameFormData'); + }); + +// 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 = + `You will publish the following:
${publishResults}`; + const confirmationModal = new bootstrap.Modal( + document.getElementById('confirmationModal'), + ); + confirmationModal.show(); + } + }); + +// the number of rows in the modal +let rowIndex = 1; + +// 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 +} + +// 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 }); + +document.addEventListener('DOMContentLoaded', function () { + const storedData = localStorage.getItem('addNameFormData'); + if (storedData) { + // Show the modal + const addNameModal = new bootstrap.Modal( + document.getElementById('addNameModal'), + ); + addNameModal.show(); + // Restore form data + restoreFormData(storedData); + } else { + resetModal(); + } +}); + +// 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 || ''; + + entries.push({ + language: languageCode, + name: nameValue, + source: sourceValue, + description: descriptionValue, + alias: aliasValue, + }); + }); + + // Get the CSRF token + const csrftoken = document.querySelector( + '[name=csrfmiddlewaretoken]', + ).value; + + // Send the request to publish + fetch('/instrument-detail/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrftoken, + }, + 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); + }); + }); \ No newline at end of file From c65304251a67539ff93bedb7ef3b2d7e6df5495c Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Wed, 25 Jun 2025 10:52:11 -0400 Subject: [PATCH 027/121] feat: add new instrument name - add confirmation modal - read in user input using javascript instead of a model form - remove model form refs: #245 --- .../apps/instruments/forms/add_name_form.py | 13 ---- .../instruments/views/instrument_detail.py | 49 +----------- .../VIM/apps/instruments/views/name_form.py | 41 ---------- .../VIM/templates/instruments/add_name.html | 74 +++++++++++++++++-- 4 files changed, 70 insertions(+), 107 deletions(-) delete mode 100644 web-app/django/VIM/apps/instruments/forms/add_name_form.py delete mode 100644 web-app/django/VIM/apps/instruments/views/name_form.py diff --git a/web-app/django/VIM/apps/instruments/forms/add_name_form.py b/web-app/django/VIM/apps/instruments/forms/add_name_form.py deleted file mode 100644 index 580f4cd5..00000000 --- a/web-app/django/VIM/apps/instruments/forms/add_name_form.py +++ /dev/null @@ -1,13 +0,0 @@ -from django import forms -from ..models.instrument_name import InstrumentName - -class NameForm(forms.ModelForm): - class Meta: - model = InstrumentName - fields = ['language', 'name', 'source_name'] - widgets = { - 'message': forms.Textarea(attrs={'rows': 4, 'cols': 40}), - } - help_texts = { - 'source_name': '' - } \ No newline at end of file diff --git a/web-app/django/VIM/apps/instruments/views/instrument_detail.py b/web-app/django/VIM/apps/instruments/views/instrument_detail.py index 3ec79165..267ee5df 100644 --- a/web-app/django/VIM/apps/instruments/views/instrument_detail.py +++ b/web-app/django/VIM/apps/instruments/views/instrument_detail.py @@ -1,9 +1,5 @@ from django.views.generic import DetailView -from VIM.apps.instruments.models import Instrument, Language, InstrumentName -from django.http import HttpResponseRedirect -from django.shortcuts import render -from ..forms.add_name_form import NameForm -from django.shortcuts import get_object_or_404 +from VIM.apps.instruments.models import Instrument, Language class InstrumentDetail(DetailView): """ @@ -13,7 +9,6 @@ class InstrumentDetail(DetailView): model = Instrument template_name = "instruments/detail.html" context_object_name = "instrument" - form_class = NameForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -36,44 +31,4 @@ def get_context_data(self, **kwargs): context["languages"] = Language.objects.all() - context["form"] = NameForm( - initial={ - "instrument": context["instrument"].wikidata_id - } - ) - return context - - def post(self, request, **kwargs): - instrument = get_object_or_404(Instrument, pk=kwargs.get("pk")) - form = NameForm(request.POST) - if form.is_valid(): - language_code = form.cleaned_data["language"] - entry = { - "name": form.cleaned_data["name"], - "source": form.cleaned_data["source_name"], - } - # Save instrument name into database - # If the instrument already has a name in specified language, save as alias - if instrument.instrumentname_set.filter(language=language_code).exists(): - InstrumentName.objects.create( - instrument=instrument, - language=language_code, - name=entry["name"], - source_name=entry["source"], - is_alias= True, - contributor=request.user, - ) - # If the instrument does not have a name in specified language, save as primary name - else: - InstrumentName.objects.create( - instrument=instrument, - language=language_code, - name=entry["name"], - source_name=entry["source"], - is_alias= False, - contributor=request.user, - ) - # Redirect to the instrument detail page - return HttpResponseRedirect("/instrument/" + str(instrument.pk) + "/") - - return render(request, self.template_name, {"form": form}) + return context \ No newline at end of file diff --git a/web-app/django/VIM/apps/instruments/views/name_form.py b/web-app/django/VIM/apps/instruments/views/name_form.py deleted file mode 100644 index 3c1c528d..00000000 --- a/web-app/django/VIM/apps/instruments/views/name_form.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.http import HttpResponseRedirect -from django.shortcuts import render -from django.views import View - -from ..forms.add_name_form import NameForm -from VIM.apps.instruments.models import InstrumentName - - -class MyFormView(View): - form_class = NameForm - initial = {"key": "value"} - template_name = "instruments/add_name.html" - - def get(self, request, *args, **kwargs): - form = self.form_class(initial=self.initial) - return render(request, self.template_name, {"form": form}) - - def post(self, request, *args, **kwargs): - form = self.form_class(request.POST) - if form.is_valid(): - wikidata_id = form.cleaned_data["instrument"] - language_code = form.cleaned_data["language"] - entry = { - "name": form.cleaned_data["name"], - "source": form.cleaned_data["source_name"], - "alias": form.cleaned_data["is_alias"], - } - # Here you would typically save the data to the database - # For example: - InstrumentName.objects.create( - instrument=wikidata_id, - language=language_code, - name=entry["name"], - source_name=entry["source"], - is_alias=entry["alias"], - contributor=request.user, - ) - # Redirect to a success page or the instrument list - return HttpResponseRedirect("/instruments/") - - return render(request, self.template_name, {"form": form}) \ No newline at end of file diff --git a/web-app/django/VIM/templates/instruments/add_name.html b/web-app/django/VIM/templates/instruments/add_name.html index 4d4a7b47..66d6eada 100644 --- a/web-app/django/VIM/templates/instruments/add_name.html +++ b/web-app/django/VIM/templates/instruments/add_name.html @@ -1,4 +1,6 @@ -Modal Structure +{% load static %} + +
+ + + \ No newline at end of file From 0d0fce2633bc72f8233fcb50f6f964c3ce01e3a3 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Wed, 25 Jun 2025 10:54:35 -0400 Subject: [PATCH 028/121] fix: rename verified_instruments view to approved_instruments - aligns with terminology refs: #245 --- .../views/{verified_instruments.py => approved_instruments.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web-app/django/VIM/apps/instruments/views/{verified_instruments.py => approved_instruments.py} (100%) diff --git a/web-app/django/VIM/apps/instruments/views/verified_instruments.py b/web-app/django/VIM/apps/instruments/views/approved_instruments.py similarity index 100% rename from web-app/django/VIM/apps/instruments/views/verified_instruments.py rename to web-app/django/VIM/apps/instruments/views/approved_instruments.py From 1e45b1f6bd0373b5a0b083cb858c392c9beca236 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Wed, 25 Jun 2025 11:05:18 -0400 Subject: [PATCH 029/121] feat: function to add new name entry to UMILdb - set alias/label status based on existing instrument on UMILdb refs: #245 --- .../apps/instruments/views/update_umil_db.py | 123 +++++------------- web-app/django/VIM/urls.py | 6 +- 2 files changed, 32 insertions(+), 97 deletions(-) diff --git a/web-app/django/VIM/apps/instruments/views/update_umil_db.py b/web-app/django/VIM/apps/instruments/views/update_umil_db.py index 08234706..aa02437d 100644 --- a/web-app/django/VIM/apps/instruments/views/update_umil_db.py +++ b/web-app/django/VIM/apps/instruments/views/update_umil_db.py @@ -15,93 +15,17 @@ def add_name(request): This view expects a POST request with the JSON body: { "wikidata_id": "Q12345", - "language": "en", - "entry": - { - "name": "English label", - "source": "Source name", - "alias": "Alias" - }, - } - - Returns: - JsonResponse: JSON response with status and message - """ - if request.method == "POST": - # Parse the JSON request body - data = json.loads(request.body) - wikidata_id = data.get("wikidata_id") - language_code = data.get("language") - entry = data.get("entry") - if not wikidata_id or not language_code or not entry: - return JsonResponse( - { - "status": "error", - "message": "Missing required data", - } - ) - try: - # Fetch the instrument from the database - instrument = Instrument.objects.get(wikidata_id=wikidata_id) - # Fetch the language from the database - language = Language.objects.get(wikidata_code=language_code) - - # Extract data from the entry - name = entry["name"] - source = entry["source"] - alias = entry["alias"] - - # Save entries to the local database - InstrumentName.objects.create( - instrument=instrument, - language=language, - name=name, - source_name=source, - is_verified=False, - is_alias=False - ) - # Save to the InstrumentAlias table if alias is provided - if alias: - InstrumentName.objects.create( - instrument_name=instrument, - alias=alias, - source_name=source, - is_verified=False, - is_alias=True - ) - - return JsonResponse( - { - "status": "success", - "message": "Data saved successfully!", - } - ) - - except Instrument.DoesNotExist: - return JsonResponse({"status": "error", "message": "Instrument not found"}) - except Exception as e: - return JsonResponse({"status": "error", "message": "" + str(e)}) - -@login_required -def add_alias(request): - """ - View to add new instrument alias to UMIL database. - - This view expects a POST request with the JSON body: - { - "wikidata_id": "Q12345", - "language": "en", "entries": [ { - "alias": "Alias" + "language": "en", + "name": "English label", "source": "Source name", - }, { - "alias": "Alias" - "source": "Source name", - }, - ... + "language": "fr", + "name": "French label", + ... + } ], } @@ -112,9 +36,8 @@ def add_alias(request): # Parse the JSON request body data = json.loads(request.body) wikidata_id = data.get("wikidata_id") - language_code = data.get("language") entries = data.get("entries", []) - if not wikidata_id or not language_code or not entries: + if not wikidata_id or not entries: return JsonResponse( { "status": "error", @@ -124,23 +47,36 @@ def add_alias(request): try: # Fetch the instrument from the database instrument = Instrument.objects.get(wikidata_id=wikidata_id) - # Fetch the language from the database - language = Language.objects.get(wikidata_code=language_code) # Process each entry: save to UMIL database for entry in entries: - + + # Extract data from the entry + language_code = entry["language"] + language = Language.objects.get(wikidata_code=language_code) + + name = entry["name"] source = entry["source"] - alias = entry["alias"] - # Save entries to the local database - InstrumentName.objects.create( + # If the instrument already has a name in specified language, save as alias + if instrument.instrumentname_set.filter(language=language).exists(): + InstrumentName.objects.create( + instrument=instrument, + language=language, + name=name, + source_name=source, + is_alias= True, + contributor=request.user, + ) + # If the instrument does not have a name in specified language, save as primary name + else: + InstrumentName.objects.create( instrument=instrument, language=language, - name=alias, + name=name, source_name=source, - is_verified=False, - is_alias=True + is_alias= False, + contributor=request.user, ) return JsonResponse( @@ -154,4 +90,3 @@ def add_alias(request): return JsonResponse({"status": "error", "message": "Instrument not found"}) except Exception as e: return JsonResponse({"status": "error", "message": "" + str(e)}) - \ No newline at end of file diff --git a/web-app/django/VIM/urls.py b/web-app/django/VIM/urls.py index ce6c903a..964cb9f2 100644 --- a/web-app/django/VIM/urls.py +++ b/web-app/django/VIM/urls.py @@ -20,14 +20,14 @@ from django.conf import settings from VIM.apps.instruments.views.instrument_list import InstrumentList from VIM.apps.instruments.views.instrument_detail import InstrumentDetail -from VIM.apps.instruments.views.name_form import MyFormView -from VIM.apps.instruments.views.verified_instruments import InstrumentNameListView +from VIM.apps.instruments.views.approved_instruments import InstrumentNameListView from VIM.apps.instruments.views.wiki_apis import ( wikidata_callback, wikidata_authorize, get_wikidata_access_token, edit_wikidata ) +from VIM.apps.instruments.views.update_umil_db import add_name from django.conf.urls.i18n import i18n_patterns urlpatterns = i18n_patterns( @@ -35,8 +35,8 @@ path("", include("VIM.apps.main.urls", namespace="main")), path("instruments/", InstrumentList.as_view(), name="instrument-list"), path("instrument//", InstrumentDetail.as_view(), name="instrument-detail"), - path("instrument//add-name/", MyFormView.as_view(), name="add-name"), path("instruments/edit-wikidata/", InstrumentNameListView.as_view(), name="edit-wikidata"), + path("add-name/", add_name, name="add-name"), path("oauth/authorize/", wikidata_authorize, name="wikidata_authorize"), path("oauth/callback/", wikidata_callback, name="wikidata_callback"), path( From 47197be55db3dcfc7f83f383c0dd0dea2ad93ede Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Wed, 25 Jun 2025 11:06:40 -0400 Subject: [PATCH 030/121] feat: adjust css for add instrument button - make larger - add hover function refs: #245 --- .../assets/css/instruments/detail.css | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 web-app/frontend/assets/css/instruments/detail.css diff --git a/web-app/frontend/assets/css/instruments/detail.css b/web-app/frontend/assets/css/instruments/detail.css new file mode 100644 index 00000000..7135dfd6 --- /dev/null +++ b/web-app/frontend/assets/css/instruments/detail.css @@ -0,0 +1,103 @@ +.instrument-detail { + display: flex; + flex-wrap: nowrap; + align-items: center; + flex-direction: column; + padding: 50px; +} +.detail-header { + display: flex; + flex-direction: row; + justify-content: flex-start; + flex-wrap: wrap; + width: 100%; +} +.detail-header hr { + width: 100%; +} +.detail-body { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-evenly; + align-items: flex-start; + width: 100%; +} +.detail-image { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: flex-start; +} +.instrument-image { + max-width: 50%; + margin-right: 10px; +} +.detail-image-caption { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: flex-start; +} +.instrument-forms { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + width: 100%; +} +.name-form-item { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; +} +.name-field { + width: 90%; +} +input.edit-field { + width: inherit; +} +th[scope='col'] { + width: 30%; +} +th[scope='row'] { + width: 20%; +} +.button-group { + display: flex; + flex-direction: row; +} +.edit-field, +.btn.cancel, +.btn.publish { + display: none; +} +.btn { + display: inline-block; + padding: 8px 12px; + cursor: pointer; + text-align: center; + text-decoration: none; + outline: black; + border: none; + border-radius: 3px; + margin-right: 5px; +} +.btn.add-name { + background-color: #ffc107; +} + +.btn.add-name:hover { + background-color: #e0a800; +} + +.btn.cancel { + background-color: #dc3545; +} +.btn.publish { + background-color: #28a745; +} From c08c781af6d5363a35028b12e66e15490472b686 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Wed, 25 Jun 2025 11:08:49 -0400 Subject: [PATCH 031/121] feat: javascript function to check if user input already exists on wikidata - isAlias checks for existing label on wikidata, indicates to user if entry will be alias/label - save alias status refs: #245 --- .../src/instruments/InstrumentDetail.ts | 191 ++++++++++++------ 1 file changed, 131 insertions(+), 60 deletions(-) diff --git a/web-app/frontend/src/instruments/InstrumentDetail.ts b/web-app/frontend/src/instruments/InstrumentDetail.ts index 294ba7ae..baebb96d 100644 --- a/web-app/frontend/src/instruments/InstrumentDetail.ts +++ b/web-app/frontend/src/instruments/InstrumentDetail.ts @@ -9,6 +9,7 @@ addNameModal.addEventListener('show.bs.modal', function (event) { var instrumentWikidataId = button.getAttribute( 'data-instrument-wikidata-id', ); + addNameModal.querySelector('#instrumentNameInModal').textContent = instrumentName; addNameModal.querySelector('#instrumentWikidataIdInModal').textContent = @@ -32,8 +33,8 @@ function storeFormData() { language: row.querySelector('.language-input input').value, name: row.querySelector('.name-input input').value, source: row.querySelector('.source-input input').value, - description: row.querySelector('.description-input input').value, - alias: row.querySelector('.alias-input input').value, + // description: row.querySelector('.description-input input').value, + // alias: row.querySelector('.alias-input input').value, }; storedData['nameRows'].push(rowData); }); @@ -68,9 +69,9 @@ function restoreFormData(storedData) { newRow.querySelector('.language-input input').value = rowData.language; newRow.querySelector('.name-input input').value = rowData.name; newRow.querySelector('.source-input input').value = rowData.source; - newRow.querySelector('.description-input input').value = - rowData.description; - newRow.querySelector('.alias-input input').value = rowData.alias; + // newRow.querySelector('.description-input input').value = + // rowData.description; + // newRow.querySelector('.alias-input input').value = rowData.alias; }); } @@ -107,7 +108,7 @@ function isValidLanguage(inputElement) { } // Function to check if a name already exists in Wikidata for the given language -async function checkNameInWikidata(wikidataId, languageCode, languageLabel) { +async function isAlias(wikidataId, languageCode, languageLabel) { const sparqlQuery = ` SELECT ?nameLabel WHERE { wd:${wikidataId} rdfs:label ?nameLabel . @@ -135,6 +136,40 @@ async function checkNameInWikidata(wikidataId, languageCode, languageLabel) { } } +// Function to check if a name already exists in Wikidata for the given language +async function existOnWikidata(wikidataId, languageCode, languageLabel, nameInput) { + console.log(languageCode) + console.log(nameInput) + + const sparqlQuery = ` + SELECT ?nameLabel WHERE { + wd:${wikidataId} (rdfs:label|skos:altLabel) ?nameLabel . + FILTER(LANG(?nameLabel) = "${languageCode}" && CONTAINS(LCASE(?nameLabel),"${nameInput.toLowerCase()}")) + } 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(); + + console.log('Wikidata query result:', data); + + 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'); @@ -156,8 +191,8 @@ function createRow(index) { ${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.
+
+
@@ -171,8 +206,9 @@ function createRow(index) {
+
- +
`; @@ -209,15 +245,12 @@ document let allValid = true; let publishResults = ''; // Collect the results for confirmation - // Iterate over each row and check if the name already exists in Wikidata + // // 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 aliasStatus = row.querySelector('.alias-status'); const languageCode = languageInput.value; const selectedOption = row.querySelector( @@ -232,6 +265,9 @@ document const languageFeedbackInvalid = row.querySelector( '.language-input .invalid-feedback', ); + const nameFeedbackValid = row.querySelector( + '.name-input .valid-feedback', + ); const nameFeedbackInvalid = row.querySelector( '.name-input .invalid-feedback', ); @@ -251,56 +287,88 @@ document continue; } - try { - const result = await checkNameInWikidata( + // 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 { + try { + // Check if name is already an alias or label on Wikidata + const result = await existOnWikidata( wikidataId, languageCode, languageLabel, - ); + nameInput.value, + ) 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}`; + nameInput.classList.add('is-invalid'); + nameFeedbackInvalid.textContent = + `This instrument already has this name on Wikidata in ${languageLabel} (${languageCode}).`; allValid = false; + continue; // Skip to the next row if the name already exists } else { + nameInput.classList.add('is-valid'); + nameInput.classList.remove('is-invalid'); 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'); - } + nameFeedbackValid.textContent = + 'This instrument does not have this name listed on Wikidata yet ! You can add a new name.'; + } + } catch (error) { + displayMessage( + 'There was an error checking Wikidata. Please try again later.', + 'danger', + ); + return; // Stop further processing + } - // 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'); - } + try { + const result = await isAlias( + wikidataId, + languageCode, + languageLabel, + ); - // Add the result to the confirmation message - publishResults += `
${languageLabel} (${languageCode}): ${nameInput.value}; Source: ${sourceInput.value}; Description: ${descriptionInput.value}; Alias: ${aliasInput.value}`; + // If language has a label, input is an Alias + if (result.exists) { + aliasStatus.value = "true"; + } else { + aliasStatus.value = "false"; + } + } catch (error) { + displayMessage( + 'There was an error checking Wikidata. Please try again later.', + 'danger', + ); + return; // Stop further processing } - } catch (error) { - displayMessage( - 'There was an error checking Wikidata. Please try again later.', - 'danger', - ); - return; // Stop further processing } + + // 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'); + } + + console.log('Checking naming:', nameInput.value); + + + + // Add the result to the confirmation message + publishResults += `
Language: ${languageLabel} (${languageCode}) +
Name: ${nameInput.value} +
Source: ${sourceInput.value} +
The entry will be saved as an ${aliasStatus.value === "true" ? 'alias' : 'label'} on Wikidata.`; + } // If all rows are valid, show the confirmation modal @@ -369,23 +437,26 @@ document 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 aliasStatus = row.querySelector('.alias-status'); + // 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 || ''; + const aliasValue = aliasStatus.value; + // const descriptionValue = descriptionInput.value || ''; + // const aliasValue = aliasInput.value || ''; entries.push({ language: languageCode, name: nameValue, source: sourceValue, - description: descriptionValue, alias: aliasValue, + // description: descriptionValue, + // alias: aliasValue, }); }); @@ -395,7 +466,7 @@ document ).value; // Send the request to publish - fetch('/instrument-detail/', { + fetch(`/add-name/`, { method: 'POST', headers: { 'Content-Type': 'application/json', From 88d79b4302b0fbccc737728798478cb213f915a5 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 26 Jun 2025 15:08:49 -0400 Subject: [PATCH 032/121] fix: validation text and publish message formatting refs: #245 --- .../VIM/templates/instruments/add_name.html | 94 +++++++++++-------- .../src/instruments/InstrumentDetail.ts | 3 +- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/add_name.html b/web-app/django/VIM/templates/instruments/add_name.html index 66d6eada..46a03d83 100644 --- a/web-app/django/VIM/templates/instruments/add_name.html +++ b/web-app/django/VIM/templates/instruments/add_name.html @@ -1,37 +1,49 @@ {% load static %} - + {% include "instruments/add_name.html" %} + {% include "instruments/delete_name.html" %} {% include "instruments/includes/jumpToTop.html" %} {% endblock content %} From 56401f7afd6878de72150aa1843e812a6d498a76 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 09:20:13 -0400 Subject: [PATCH 044/121] feat: wikibase connection settings - add wikibase access token - commented out code in wiki_apis for wikimedia api refs: #254 --- docker-compose.yml | 1 + .../VIM/apps/instruments/views/wiki_apis.py | 120 ++++++++++++++++-- web-app/django/VIM/settings.py | 1 + 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8846b309..d603d453 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: - WIKIDATA_REDIRECT_URI=${WIKIDATA_REDIRECT_URI} - WIKIDATA_CLIENT_APP_KEY=${WIKIDATA_CLIENT_APP_KEY} - WIKIDATA_CLIENT_APP_SECRET=${WIKIDATA_CLIENT_APP_SECRET} + - WIKIBASE_ACCESS_TOKEN=${WIKIBASE_ACCESS_TOKEN} command: /virtual-instrument-museum/start-up.sh postgres: diff --git a/web-app/django/VIM/apps/instruments/views/wiki_apis.py b/web-app/django/VIM/apps/instruments/views/wiki_apis.py index b60aa402..6281efc6 100644 --- a/web-app/django/VIM/apps/instruments/views/wiki_apis.py +++ b/web-app/django/VIM/apps/instruments/views/wiki_apis.py @@ -10,11 +10,14 @@ WIKIDATA_REDIRECT_URI = settings.WIKIDATA_REDIRECT_URI WIKIDATA_CLIENT_APP_KEY = settings.WIKIDATA_CLIENT_APP_KEY WIKIDATA_CLIENT_APP_SECRET = settings.WIKIDATA_CLIENT_APP_SECRET +WIKIBASE_ACCESS_TOKEN = settings.WIKIBASE_ACCESS_TOKEN session = requests.Session() def get_wikidata_access_token(request): access_token = request.COOKIES.get("wikidata_access_token") + + # access_token = WIKIBASE_ACCESS_TOKEN return JsonResponse({"access_token": access_token}) @@ -78,6 +81,9 @@ def add_info_to_wikidata_entity(action, access_token, wikidata_id, value, langua Returns: dict: The response from the Wikidata API or an error message if failed. """ + # wikidata_id = wikidata_to_wikibase(wikidata_id) + wikidata_id= "Q4115189" + print(wikidata_id, flush=True) try: # Authorization header with OAuth access token headers = { @@ -90,11 +96,14 @@ def add_info_to_wikidata_entity(action, access_token, wikidata_id, value, langua elif action == "alias": body = {"aliases": [value]} url = f"{WIKIDATA_URL}/entities/items/{wikidata_id}/aliases/{language}" + print(f"DEBUG: Adding alias {value} to {wikidata_id} in {language}", flush=True) else: return {"errors": f"Invalid action: {action}"} # Invalid action - if action == "add_aliases": + if action == "alias": response = session.post(url, headers=headers, json=body) + print(f"DEBUG: url for alias: {url}", flush=True) + print(f"DEBUG: Response status code for alias: {response.status_code}", flush=True) else: response = session.put(url, headers=headers, json=body) response.raise_for_status() @@ -117,16 +126,109 @@ def edit_wikidata(request): """ # retrieve all instrument names that are currently approved AND not yet on Wikidata approved = InstrumentName.objects.filter(is_approved=True).filter(on_wikidata=False) - access_token = get_wikidata_access_token(request) + # access_token = WIKIBASE_ACCESS_TOKEN + access_token = request.COOKIES.get("wikidata_access_token") + + print(approved, flush=True) + print(access_token, flush=True) for instrument_name in approved: - print(f"Processing instrument name: {instrument_name.name} in language {instrument_name.language}") - # # post to wikidata - # if instrument_name.is_alias: - # add_info_to_wikidata_entity("alias", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language) - # else: - # add_info_to_wikidata_entity("label", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language) + print(f"Processing instrument name: {instrument_name.name} in language {instrument_name.language.wikidata_code}") + # post to wikidata + if instrument_name.is_alias: + add_info_to_wikidata_entity("alias", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language.wikidata_code) + else: + add_info_to_wikidata_entity("label", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language.wikidata_code) InstrumentName.objects.filter(is_approved = True).update( on_wikidata=True - ) \ No newline at end of file + ) + return JsonResponse({"status": "success", "message": "Wikidata entries updated successfully."}) + +# def get_csrf_token(access_token): +# response = session.get(f"{WIKIDATA_URL}", params={ +# "action": "query", +# "meta": "tokens", +# "format": "json" +# }, headers={"Authorization": f"Bearer {access_token}"}) +# return response.json()["query"]["tokens"]["csrftoken"] + +# def wikidata_to_wikibase(wikidata_id): +# """ +# Direct mapping for testing - expand as needed +# """ +# # Known mappings: Wikidata ID -> Your Wikibase ID +# known_mappings = { +# "Q6607": "Q179", # Guitar +# } + +# if wikidata_id in known_mappings: +# print(f"DEBUG: Found mapping {wikidata_id} -> {known_mappings[wikidata_id]}", flush=True) +# return known_mappings[wikidata_id] + +# print(f"DEBUG: No mapping found for {wikidata_id}, would create new item", flush=True) +# # For now, return None or create new item +# return None + +# def add_info_to_wikidata_entity(action, access_token, wikidata_id, value, language): +# """ +# Adds information to a Wikidata entity using the MediaWiki API. + +# Args: +# action (str): The action to perform ("label" or "alias"). +# access_token (str): The OAuth access token. +# wikidata_id (str): The Wikidata ID of the entity. +# value (str): The value to add. +# language (str): The language code. + +# Returns: +# dict: The response from the Wikidata API or an error message if failed. +# """ +# wikidata_id = wikidata_to_wikibase(wikidata_id) +# print(wikidata_id, flush=True) +# api_url = f"{WIKIDATA_URL}/w/api.php" + +# # # Get CSRF token +# csrf_token = "4e900338c46ac7f6ac0dcda4c7ad98ce68657217+\\" + +# # Prepare parameters for the MediaWiki API +# if action == "label": +# params = { +# "action": "wbsetlabel", +# "id": wikidata_id, +# "value": value, +# "language": language, +# "token": csrf_token, +# "format": "json", +# } +# elif action == "alias": +# params = { +# "action": "wbsetaliases", +# "id": wikidata_id, +# "add": value, +# "language": language, +# "token": csrf_token, +# "format": "json", +# } +# else: +# return {"errors": f"Invalid action: {action}"} + +# headers = { +# "Authorization": f"Bearer {access_token}", +# "Content-Type": "application/x-www-form-urlencoded", +# } +# print("DEBUG: MediaWiki API response data:", params, flush=True) + +# try: +# response = session.post(api_url, data=params, headers=headers) +# print(f"DEBUG: MediaWiki API response status: {response.status_code}", flush=True) +# response.raise_for_status() +# data = response.json() +# if "error" in data: +# return {"errors": f"Error from MediaWiki API: {data['error'].get('info', 'Unknown error')}"} +# return data + +# except requests.RequestException as e: +# return {"errors": f"HTTP error occurred: {e}"} +# except ValueError as ve: +# return {"errors": f"Value error occurred: {ve}"} \ No newline at end of file diff --git a/web-app/django/VIM/settings.py b/web-app/django/VIM/settings.py index d9d5cd44..aa869dc6 100644 --- a/web-app/django/VIM/settings.py +++ b/web-app/django/VIM/settings.py @@ -183,3 +183,4 @@ WIKIDATA_REDIRECT_URI = os.getenv("WIKIDATA_REDIRECT_URI") WIKIDATA_CLIENT_APP_KEY = os.getenv("WIKIDATA_CLIENT_APP_KEY") WIKIDATA_CLIENT_APP_SECRET = os.getenv("WIKIDATA_CLIENT_APP_SECRET") +WIKIBASE_ACCESS_TOKEN = os.getenv("WIKIBASE_ACCESS_TOKEN") From 5dada8f39360187468e77b9cce4fd3cc93683eb6 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 09:21:26 -0400 Subject: [PATCH 045/121] feat: allow superuser to delete instrument names on upload page refs: #247 --- .../VIM/templates/instruments/name_list.html | 172 +++++++++++++++++- 1 file changed, 166 insertions(+), 6 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/name_list.html b/web-app/django/VIM/templates/instruments/name_list.html index 12e00ec6..7de993e3 100644 --- a/web-app/django/VIM/templates/instruments/name_list.html +++ b/web-app/django/VIM/templates/instruments/name_list.html @@ -8,6 +8,10 @@ Instrument Detail {% endblock title %} +{% block ts_files %} + {% vite_asset 'src/instruments/InstrumentDetail.ts' %} +{% endblock ts_files %} + {% block css_files %} New Instrument Names

Language Name Source + Actions {% for instrument in instrument_names %} {{ instrument.language.en_label }} {{ instrument.name }} {{ instrument.source_name }} + +
+ +
+ {% empty %} - No new names found. + No new names found. {% endfor %} - + {% endif %} + + + + + + + + -{% endblock content %} +{% include "instruments/delete_name.html" %} {% endblock content %} From be6317acf16788e56ee4ea0dfd5a58f21a360d38 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 10:52:15 -0400 Subject: [PATCH 046/121] chore: remove wikidata functionalities - remove upload page - remove upload from nav bar refs: #255 --- docker-compose.yml | 6 - .../instruments/views/approved_instruments.py | 17 -- .../VIM/apps/instruments/views/wiki_apis.py | 234 ------------------ web-app/django/VIM/settings.py | 9 - web-app/django/VIM/templates/base.html | 6 - .../VIM/templates/instruments/name_list.html | 226 ----------------- web-app/django/VIM/urls.py | 16 -- 7 files changed, 514 deletions(-) delete mode 100644 web-app/django/VIM/apps/instruments/views/approved_instruments.py delete mode 100644 web-app/django/VIM/apps/instruments/views/wiki_apis.py delete mode 100644 web-app/django/VIM/templates/instruments/name_list.html diff --git a/docker-compose.yml b/docker-compose.yml index d603d453..4f04f180 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,12 +23,6 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - HOST_NAME=${HOST_NAME} - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} - - WIKIDATA_URL=${WIKIDATA_URL} - - WIKIDATA_OAUTH_URL=${WIKIDATA_OAUTH_URL} - - WIKIDATA_REDIRECT_URI=${WIKIDATA_REDIRECT_URI} - - WIKIDATA_CLIENT_APP_KEY=${WIKIDATA_CLIENT_APP_KEY} - - WIKIDATA_CLIENT_APP_SECRET=${WIKIDATA_CLIENT_APP_SECRET} - - WIKIBASE_ACCESS_TOKEN=${WIKIBASE_ACCESS_TOKEN} command: /virtual-instrument-museum/start-up.sh postgres: diff --git a/web-app/django/VIM/apps/instruments/views/approved_instruments.py b/web-app/django/VIM/apps/instruments/views/approved_instruments.py deleted file mode 100644 index e952fa8a..00000000 --- a/web-app/django/VIM/apps/instruments/views/approved_instruments.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.utils import timezone -from django.views.generic.list import ListView -from django.contrib.auth.mixins import UserPassesTestMixin - -from VIM.apps.instruments.models import InstrumentName - -class InstrumentNameListView(UserPassesTestMixin, ListView): - model = InstrumentName - template_name = "instruments/name_list.html" - paginate_by = 100 # if pagination is desired - context_object_name = "instrument_names" - - def get_queryset(self): - return InstrumentName.objects.filter(is_approved=True, on_wikidata=False) - - def test_func(self): - return self.request.user.is_superuser \ No newline at end of file diff --git a/web-app/django/VIM/apps/instruments/views/wiki_apis.py b/web-app/django/VIM/apps/instruments/views/wiki_apis.py deleted file mode 100644 index 6281efc6..00000000 --- a/web-app/django/VIM/apps/instruments/views/wiki_apis.py +++ /dev/null @@ -1,234 +0,0 @@ -import requests -import urllib.parse -from django.shortcuts import redirect -from django.conf import settings -from django.http import JsonResponse -from VIM.apps.instruments.models import InstrumentName - -WIKIDATA_URL = settings.WIKIDATA_URL -WIKIDATA_OAUTH_URL = settings.WIKIDATA_OAUTH_URL -WIKIDATA_REDIRECT_URI = settings.WIKIDATA_REDIRECT_URI -WIKIDATA_CLIENT_APP_KEY = settings.WIKIDATA_CLIENT_APP_KEY -WIKIDATA_CLIENT_APP_SECRET = settings.WIKIDATA_CLIENT_APP_SECRET -WIKIBASE_ACCESS_TOKEN = settings.WIKIBASE_ACCESS_TOKEN - -session = requests.Session() - -def get_wikidata_access_token(request): - access_token = request.COOKIES.get("wikidata_access_token") - - # access_token = WIKIBASE_ACCESS_TOKEN - return JsonResponse({"access_token": access_token}) - - -def wikidata_callback(request): - # Retrieve the authorization code from the query parameters - authorization_code = request.GET.get("code") - if not authorization_code: - return JsonResponse({"errors": "Authorization code not provided"}, status=400) - # Exchange the authorization code for an access token - base_url = f"{WIKIDATA_OAUTH_URL}/access_token" - data = { - "grant_type": "authorization_code", - "code": authorization_code, - "client_id": WIKIDATA_CLIENT_APP_KEY, - "client_secret": WIKIDATA_CLIENT_APP_SECRET, - "redirect_uri": WIKIDATA_REDIRECT_URI, - } - headers = { - "Content-Type": "application/x-www-form-urlencoded", - } - response = session.post(base_url, data=data, headers=headers) - access_token = response.json().get("access_token") - # Set token in an HTTP-only cookie - response = redirect(request.session.pop("wikidata_next", "/")) - if access_token: - response.set_cookie( - "wikidata_access_token", - access_token, - httponly=True, # Prevent JavaScript access for security - secure=True, # Only send over HTTPS - samesite="Lax", # Prevent cross-site request forgery - ) - return response - -def wikidata_authorize(request): - # Get the previous page (default to "/") - next_url = request.GET.get("next", request.META.get("HTTP_REFERER", "/")) - request.session["wikidata_next"] = next_url - - # Construct the authorization URL - base_url = f"{WIKIDATA_OAUTH_URL}/authorize" - params = { - "response_type": "code", - "client_id": WIKIDATA_CLIENT_APP_KEY, - "redirect_uri": WIKIDATA_REDIRECT_URI, - } - authorization_url = f"{base_url}?{urllib.parse.urlencode(params)}" - return redirect(authorization_url) - -def add_info_to_wikidata_entity(action, access_token, wikidata_id, value, language): - """ - Adds information to a Wikidata entity using OAuth. - - Args: - action (str): The action to perform (e.g., "add_label", "add_description", "add_aliases"). - access_token (str): The OAuth access token. - wikidata_id (str): The Wikidata ID of the entity. - value (str): The value to add. - language (str): The language code. - - Returns: - dict: The response from the Wikidata API or an error message if failed. - """ - # wikidata_id = wikidata_to_wikibase(wikidata_id) - wikidata_id= "Q4115189" - print(wikidata_id, flush=True) - try: - # Authorization header with OAuth access token - headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - } - if action == "label": - body = {"label": value} - url = f"{WIKIDATA_URL}/entities/items/{wikidata_id}/labels/{language}" - elif action == "alias": - body = {"aliases": [value]} - url = f"{WIKIDATA_URL}/entities/items/{wikidata_id}/aliases/{language}" - print(f"DEBUG: Adding alias {value} to {wikidata_id} in {language}", flush=True) - else: - return {"errors": f"Invalid action: {action}"} # Invalid action - - if action == "alias": - response = session.post(url, headers=headers, json=body) - print(f"DEBUG: url for alias: {url}", flush=True) - print(f"DEBUG: Response status code for alias: {response.status_code}", flush=True) - else: - response = session.put(url, headers=headers, json=body) - response.raise_for_status() - data = response.json() - if "errorKey" in data: - return { - "errors": f"Error adding label to Wikidata: {data['messageTranslations']['en']}" - } - return data - - except requests.RequestException as e: - return {"errors": f"HTTP error occurred: {e}"} - except ValueError as ve: - return {"errors": f"Value error occurred: {ve}"} - -def edit_wikidata(request): - """ - This method is intended to be called to edit Wikidata entries for verified instrument names. - It can be triggered by a button in the template. - """ - # retrieve all instrument names that are currently approved AND not yet on Wikidata - approved = InstrumentName.objects.filter(is_approved=True).filter(on_wikidata=False) - # access_token = WIKIBASE_ACCESS_TOKEN - access_token = request.COOKIES.get("wikidata_access_token") - - print(approved, flush=True) - print(access_token, flush=True) - - for instrument_name in approved: - print(f"Processing instrument name: {instrument_name.name} in language {instrument_name.language.wikidata_code}") - # post to wikidata - if instrument_name.is_alias: - add_info_to_wikidata_entity("alias", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language.wikidata_code) - else: - add_info_to_wikidata_entity("label", access_token, instrument_name.instrument.wikidata_id, instrument_name.name, instrument_name.language.wikidata_code) - - InstrumentName.objects.filter(is_approved = True).update( - on_wikidata=True - ) - return JsonResponse({"status": "success", "message": "Wikidata entries updated successfully."}) - -# def get_csrf_token(access_token): -# response = session.get(f"{WIKIDATA_URL}", params={ -# "action": "query", -# "meta": "tokens", -# "format": "json" -# }, headers={"Authorization": f"Bearer {access_token}"}) -# return response.json()["query"]["tokens"]["csrftoken"] - -# def wikidata_to_wikibase(wikidata_id): -# """ -# Direct mapping for testing - expand as needed -# """ -# # Known mappings: Wikidata ID -> Your Wikibase ID -# known_mappings = { -# "Q6607": "Q179", # Guitar -# } - -# if wikidata_id in known_mappings: -# print(f"DEBUG: Found mapping {wikidata_id} -> {known_mappings[wikidata_id]}", flush=True) -# return known_mappings[wikidata_id] - -# print(f"DEBUG: No mapping found for {wikidata_id}, would create new item", flush=True) -# # For now, return None or create new item -# return None - -# def add_info_to_wikidata_entity(action, access_token, wikidata_id, value, language): -# """ -# Adds information to a Wikidata entity using the MediaWiki API. - -# Args: -# action (str): The action to perform ("label" or "alias"). -# access_token (str): The OAuth access token. -# wikidata_id (str): The Wikidata ID of the entity. -# value (str): The value to add. -# language (str): The language code. - -# Returns: -# dict: The response from the Wikidata API or an error message if failed. -# """ -# wikidata_id = wikidata_to_wikibase(wikidata_id) -# print(wikidata_id, flush=True) -# api_url = f"{WIKIDATA_URL}/w/api.php" - -# # # Get CSRF token -# csrf_token = "4e900338c46ac7f6ac0dcda4c7ad98ce68657217+\\" - -# # Prepare parameters for the MediaWiki API -# if action == "label": -# params = { -# "action": "wbsetlabel", -# "id": wikidata_id, -# "value": value, -# "language": language, -# "token": csrf_token, -# "format": "json", -# } -# elif action == "alias": -# params = { -# "action": "wbsetaliases", -# "id": wikidata_id, -# "add": value, -# "language": language, -# "token": csrf_token, -# "format": "json", -# } -# else: -# return {"errors": f"Invalid action: {action}"} - -# headers = { -# "Authorization": f"Bearer {access_token}", -# "Content-Type": "application/x-www-form-urlencoded", -# } -# print("DEBUG: MediaWiki API response data:", params, flush=True) - -# try: -# response = session.post(api_url, data=params, headers=headers) -# print(f"DEBUG: MediaWiki API response status: {response.status_code}", flush=True) -# response.raise_for_status() -# data = response.json() -# if "error" in data: -# return {"errors": f"Error from MediaWiki API: {data['error'].get('info', 'Unknown error')}"} -# return data - -# except requests.RequestException as e: -# return {"errors": f"HTTP error occurred: {e}"} -# except ValueError as ve: -# return {"errors": f"Value error occurred: {ve}"} \ No newline at end of file diff --git a/web-app/django/VIM/settings.py b/web-app/django/VIM/settings.py index aa869dc6..4229007b 100644 --- a/web-app/django/VIM/settings.py +++ b/web-app/django/VIM/settings.py @@ -175,12 +175,3 @@ # SOLR SETTINGS SOLR_URL = "http://solr:8983/solr/virtual-instrument-museum" - -# Wikidata settings - -WIKIDATA_URL = os.getenv("WIKIDATA_URL") -WIKIDATA_OAUTH_URL = os.getenv("WIKIDATA_OAUTH_URL") -WIKIDATA_REDIRECT_URI = os.getenv("WIKIDATA_REDIRECT_URI") -WIKIDATA_CLIENT_APP_KEY = os.getenv("WIKIDATA_CLIENT_APP_KEY") -WIKIDATA_CLIENT_APP_SECRET = os.getenv("WIKIDATA_CLIENT_APP_SECRET") -WIKIBASE_ACCESS_TOKEN = os.getenv("WIKIBASE_ACCESS_TOKEN") diff --git a/web-app/django/VIM/templates/base.html b/web-app/django/VIM/templates/base.html index ce33334e..a6afea15 100644 --- a/web-app/django/VIM/templates/base.html +++ b/web-app/django/VIM/templates/base.html @@ -71,12 +71,6 @@ About - {% if user.is_superuser %} - - {% endif %} -{% extends "base.html" %} - -{% load static %} -{% load django_vite %} - -{% block title %} - Instrument Detail -{% endblock title %} - -{% block ts_files %} - {% vite_asset 'src/instruments/InstrumentDetail.ts' %} -{% endblock ts_files %} - -{% block css_files %} - - -{% endblock css_files %} {% block content %} -
-
-

New Instrument Names

-
-
-
-
- - - - - - - - {% for instrument in instrument_names %} - - - - - - - {% empty %} - - - - {% endfor %} -
LanguageNameSourceActions
{{ instrument.language.en_label }}{{ instrument.name }}{{ instrument.source_name }} -
- -
-
No new names found.
-
- {% csrf_token %} {% if instrument_names %} - - {% endif %} - - - - - - - - - -
-{% include "instruments/delete_name.html" %} {% endblock content %} - diff --git a/web-app/django/VIM/urls.py b/web-app/django/VIM/urls.py index 4150dfb5..6a939f9e 100644 --- a/web-app/django/VIM/urls.py +++ b/web-app/django/VIM/urls.py @@ -20,13 +20,6 @@ from django.conf import settings from VIM.apps.instruments.views.instrument_list import InstrumentList from VIM.apps.instruments.views.instrument_detail import InstrumentDetail -from VIM.apps.instruments.views.approved_instruments import InstrumentNameListView -from VIM.apps.instruments.views.wiki_apis import ( - wikidata_callback, - wikidata_authorize, - get_wikidata_access_token, - edit_wikidata -) from VIM.apps.instruments.views.update_umil_db import (add_name, delete_name) from django.conf.urls.i18n import i18n_patterns @@ -35,17 +28,8 @@ path("", include("VIM.apps.main.urls", namespace="main")), path("instruments/", InstrumentList.as_view(), name="instrument-list"), path("instrument//", InstrumentDetail.as_view(), name="instrument-detail"), - path("instruments/edit-wikidata/", InstrumentNameListView.as_view(), name="edit-wikidata"), path("add-name/", add_name, name="add-name"), path("delete-name/", delete_name, name="delete-name"), - path("oauth/authorize/", wikidata_authorize, name="wikidata_authorize"), - path("oauth/callback/", wikidata_callback, name="wikidata_callback"), - path( - "get_wikidata_access_token/", - get_wikidata_access_token, - name="get_wikidata_access_token", - ), - path("publish/", edit_wikidata, name="publish"), prefix_default_language=False, ) From a1a07734da7802a603436def9e9a757b127a1cc1 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 10:52:34 -0400 Subject: [PATCH 047/121] chore: disable registration button refs: #255 --- web-app/django/VIM/templates/main/registration/register.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-app/django/VIM/templates/main/registration/register.html b/web-app/django/VIM/templates/main/registration/register.html index a5d39acf..20058828 100644 --- a/web-app/django/VIM/templates/main/registration/register.html +++ b/web-app/django/VIM/templates/main/registration/register.html @@ -22,7 +22,7 @@
{{ form.password2 }}
- + {% endblock left_column %} From f9425c27e2f220d823cb5b84b4dff959dfe420f5 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 10:53:41 -0400 Subject: [PATCH 048/121] chore: separate delete name ts into separate file - modify delete alert message refs: #255 --- .../templates/instruments/delete_name.html | 2 +- .../frontend/src/instruments/DeleteName.ts | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 web-app/frontend/src/instruments/DeleteName.ts diff --git a/web-app/django/VIM/templates/instruments/delete_name.html b/web-app/django/VIM/templates/instruments/delete_name.html index e17dc0f7..798b6a17 100644 --- a/web-app/django/VIM/templates/instruments/delete_name.html +++ b/web-app/django/VIM/templates/instruments/delete_name.html @@ -15,7 +15,7 @@ From 4beb32cbccc5ed23631d8834e4c62674efb52c95 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 13:56:21 -0400 Subject: [PATCH 052/121] fix: javascript to typescript and bootstrap modal API refs: #255 --- .../frontend/src/instruments/DeleteName.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/web-app/frontend/src/instruments/DeleteName.ts b/web-app/frontend/src/instruments/DeleteName.ts index 5056470b..f656d7c4 100644 --- a/web-app/frontend/src/instruments/DeleteName.ts +++ b/web-app/frontend/src/instruments/DeleteName.ts @@ -1,10 +1,22 @@ +import { Modal } from 'bootstrap'; + // Get the modal element var deleteNameModal = document.getElementById('deleteNameModal'); -let instrumentNameId = null; // Variable to store the instrument name ID +let instrumentNameId: string | null = null; // Variable to store the instrument name ID + +interface DeleteNameModalEvent extends Event { + relatedTarget?: HTMLElement | null; +} + +interface DeleteNameResponse { + status: string; + message: string; +} // Handle modal show event deleteNameModal.addEventListener('show.bs.modal', function (event) { - var button = event.relatedTarget; + const modalEvent = event as DeleteNameModalEvent; + var button = modalEvent.relatedTarget; if (button != undefined) { var instrumentNameLanguage = button.getAttribute( 'data-instrument-language', @@ -26,9 +38,7 @@ deleteNameModal.addEventListener('show.bs.modal', function (event) { }); document.getElementById('confirmDeleteBtn').addEventListener('click', function () { - const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; - - console.log('Deleting instrument name with ID:', instrumentNameId); + const csrftoken = (document.querySelector('[name=csrfmiddlewaretoken]') as HTMLInputElement).value; // Send the request to publish fetch(`/delete-name/`, { @@ -46,7 +56,7 @@ document.getElementById('confirmDeleteBtn').addEventListener('click', function ( .then((data) => { if (data.status === 'success') { // Close both modals - const deleteNameModal = bootstrap.Modal.getInstance( + const deleteNameModal = Modal.getInstance( document.getElementById('deleteNameModal'), ); From 36c0ac87ee8ffc8e88eb4b9518ac92175e87fc28 Mon Sep 17 00:00:00 2001 From: mailynmailyn Date: Thu, 3 Jul 2025 13:56:40 -0400 Subject: [PATCH 053/121] fix: bootstrap modal API refs: #255 --- .../src/instruments/InstrumentDetail.ts | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/web-app/frontend/src/instruments/InstrumentDetail.ts b/web-app/frontend/src/instruments/InstrumentDetail.ts index d212a5fb..f60924ce 100644 --- a/web-app/frontend/src/instruments/InstrumentDetail.ts +++ b/web-app/frontend/src/instruments/InstrumentDetail.ts @@ -1,3 +1,5 @@ +import { Modal } from 'bootstrap'; + // Get the modal element var addNameModal = document.getElementById('addNameModal'); @@ -197,8 +199,6 @@ async function existOnWikidata( languageLabel: string, nameInput: string ): Promise { - console.log(languageCode) - console.log(nameInput) const sparqlQuery = ` SELECT ?nameLabel WHERE { @@ -216,8 +216,6 @@ async function existOnWikidata( const response = await fetch(queryUrl); const data: ExistSparqlResults = await response.json(); - console.log('Wikidata query result:', data); - if (data.results.bindings.length > 0) { return { exists: true, name: data.results.bindings[0].nameLabel.value }; } else { @@ -280,7 +278,7 @@ function createRow(index: number): HTMLDivElement {
- +
`; @@ -431,10 +429,6 @@ document sourceInput.classList.remove('is-invalid'); } - console.log('Checking naming:', nameInput.value); - - - // Add the result to the confirmation message publishResults += `
Language: ${languageLabel} (${languageCode})
Name: ${nameInput.value} @@ -447,7 +441,7 @@ document if (allValid) { document.getElementById('publishResults').innerHTML = `You will publish the following:
${publishResults}`; - const confirmationModal = new bootstrap.Modal( + const confirmationModal = new Modal( document.getElementById('confirmationModal'), ); confirmationModal.show(); @@ -474,14 +468,11 @@ document.getElementById('addRowBtn').addEventListener('click', function () { updateRemoveButtons(); // Update remove buttons after adding a new row }); -// Add this at the top of your file or before usage if using Bootstrap 5 via CDN or script tag -declare var bootstrap: any; - document.addEventListener('DOMContentLoaded', function () { const storedData = localStorage.getItem('addNameFormData'); if (storedData) { // Show the modal - const addNameModal = new bootstrap.Modal( + const addNameModal = new Modal( document.getElementById('addNameModal'), ); addNameModal.show(); @@ -555,10 +546,10 @@ document .then((data) => { if (data.status === 'success') { // Close both modals - const addNameModal = bootstrap.Modal.getInstance( + const addNameModal = Modal.getInstance( document.getElementById('addNameModal'), ); - const confirmationModal = bootstrap.Modal.getInstance( + const confirmationModal = Modal.getInstance( document.getElementById('confirmationModal'), ); From 650812878063d942b20bc7f791274f14d2820fc8 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 3 Jul 2025 15:24:09 -0400 Subject: [PATCH 054/121] ci: make djlint log filename only --- .github/workflows/frontend_format.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend_format.yml b/.github/workflows/frontend_format.yml index 36cc57a8..bfffe070 100644 --- a/.github/workflows/frontend_format.yml +++ b/.github/workflows/frontend_format.yml @@ -20,7 +20,9 @@ jobs: run: poetry install --no-root - name: Run djlint via Poetry - run: poetry run djlint . --check --extension html --exclude "migrations/*" + run: | + cd web-app/django/VIM/templates/ + poetry run djlint . --check --extension html --exclude "migrations/*" 2>&1 | grep -E "^[a-zA-Z0-9/_\.-]+\.html$" | sort | uniq - name: Set up Node.js uses: actions/setup-node@v4 From c16e78dabbd3af00d0175880c67a1364eba26245 Mon Sep 17 00:00:00 2001 From: Yinan Zhou Date: Thu, 3 Jul 2025 15:28:25 -0400 Subject: [PATCH 055/121] chore: reformat frontend files --- .../VIM/templates/instruments/add_name.html | 68 ++++----- .../templates/instruments/delete_name.html | 16 +-- .../VIM/templates/instruments/detail.html | 1 + .../frontend/src/instruments/DeleteName.ts | 73 +++++----- .../src/instruments/InstrumentDetail.ts | 136 ++++++++++-------- 5 files changed, 144 insertions(+), 150 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/add_name.html b/web-app/django/VIM/templates/instruments/add_name.html index 26fb623b..6bd9b13d 100644 --- a/web-app/django/VIM/templates/instruments/add_name.html +++ b/web-app/django/VIM/templates/instruments/add_name.html @@ -1,49 +1,37 @@ {% load static %} -