Skip to content

Commit

Permalink
feat: add wikidata oauth module
Browse files Browse the repository at this point in the history
  • Loading branch information
kunfang98927 committed Jan 30, 2025
1 parent bac2cc0 commit b5aa583
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,56 @@ document.getElementById('addRowBtn').addEventListener('click', function () {
updateRemoveButtons(); // Update remove buttons after adding a new row
});

document.addEventListener('DOMContentLoaded', function () {
const publishCheckbox = document.getElementById('publishToWikidataCheckbox');
const accountSelection = document.getElementById('accountSelection');
const accountOption = document.getElementById('accountOption');
const authorizeButtonRow = document.getElementById('authorizeButtonRow');
const authorizeBtn = document.getElementById('authorizeBtn');

// Show account selection when 'publish to Wikidata' checkbox is checked
publishCheckbox.addEventListener('change', function () {
accountSelection.style.display = publishCheckbox.checked ? 'block' : 'none';
if (!publishCheckbox.checked) {
authorizeButtonRow.style.display = 'none'; // Hide authorize button if unchecked
}
});

// Show authorize button if the user selects 'Your own Wikidata account'
accountOption.addEventListener('change', function () {
authorizeButtonRow.style.display =
accountOption.value === 'user_account' ? 'block' : 'none';
});

// Handle the OAuth authorization button click
authorizeBtn.addEventListener('click', function () {
// Redirect to your OAuth authorization endpoint
window.location.href = '/oauth/authorize';
});
});

// Reset the modal when hidden
document
.getElementById('addNameModal')
.addEventListener('hide.bs.modal', resetModal);

// Function to get CSRF token from cookies
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Check if this cookie string begins with the name
if (cookie.substring(0, name.length + 1) === name + '=') {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}

// Function to handle confirm publish action
document
.getElementById('confirmPublishBtn')
Expand All @@ -307,12 +352,6 @@ document
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,
Expand All @@ -322,23 +361,24 @@ document
});
});

// Check if the user wants to publish to Wikidata
// Determine if publishing to Wikidata is enabled
const publishToWikidata = document.getElementById(
'publishToWikidataCheckbox',
).checked;
const accountOption = document.getElementById('accountOption').value;

// Publish data to our database and then to Wikidata
// Send the request to publish
fetch('/publish_name/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')
.value,
'X-CSRFToken': getCookie('csrftoken'),
},
body: JSON.stringify({
wikidata_id: wikidataId,
entries: entries,
publish_to_wikidata: publishToWikidata,
account_option: accountOption,
}),
})
.then((response) => response.json())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addNameModalLabel">
Add New Name for
<span id="instrumentNameInModal"></span>
Add New Name for <span id="instrumentNameInModal"></span>
(wikidata ID:<span id="instrumentWikidataIdInModal"></span>)
</h5>
<button type="button"
Expand Down Expand Up @@ -51,6 +50,27 @@ <h5 class="modal-title" id="addNameModalLabel">
</div>
</div>
</div>
<!-- Account selection options (hidden by default) -->
<div class="row mt-3" id="accountSelection" style="display: none;">
<div class="col">
<label for="accountOption" class="form-label">
Choose an account for publishing:
</label>
<select class="form-select" id="accountOption" name="account_option" required>
<option value="">Select the Wikidata account to use</option>
<option value="user_account">Your own Wikidata account</option>
<option value="public_account">UMIL's public Wikidata account</option>
</select>
</div>
</div>
<!-- Authorize button for OAuth (hidden by default) -->
<div class="row mt-3" id="authorizeButtonRow" style="display: none;">
<div class="col text-end">
<button type="button" class="btn btn-secondary" id="authorizeBtn">
Authorize
</button>
</div>
</div>
<!-- Row for 'Publish' button -->
<div class="row mt-3">
<div class="col text-end">
Expand Down
173 changes: 95 additions & 78 deletions web-app/django/VIM/apps/instruments/views/publish_name.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
""" Django view to handle publishing to Wikidata. """
""" Django view to handle publishing to Wikidata using OAuth. """

import json
from django.views.decorators.csrf import csrf_protect
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
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,
)
from VIM.apps.instruments.views.wiki_apis import add_info_to_wikidata_entity


@csrf_protect
@login_required
def publish_name(request):
"""
View to publish new instrument names to Wikidata.
View to publish new instrument names to Wikidata using OAuth.
This view expects a POST request with the following JSON body like this:
This view expects a POST request with the JSON body:
{
"wikidata_id": "Q12345",
"entries": [
Expand All @@ -32,125 +31,133 @@ def publish_name(request):
...
}
],
"publish_to_wikidata": true
"publish_to_wikidata": true,
"account_option": "public_account" or "user_account"
}
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")
entries = data.get("entries", [])
publish_to_wikidata = data.get("publish_to_wikidata", False)
account_option = data.get(
"account_option", "public_account"
) # Default to public account

if not wikidata_id or not entries:
return JsonResponse(
{
"status": "error",
"message": "Missing required data",
"message": "[publish_name] 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
# Determine which access token to use based on account_option
if publish_to_wikidata:
csrf_token, lgusername, error_message = get_csrf_token(
username, password
)

# Check if there was an error
if error_message:
if account_option == "public_account":
# Use UMIL's public access token (set in settings or environment)
access_token = request.session.get("wikidata_access_token")
print("[publish_name] access_token", access_token)
if not access_token:
# Redirect to the authorization flow and return to `publish_name` after completion
redirect_url = (
f"{reverse('wikidata_authorize')}?next={request.path}"
)
print("[publish_name] Redirecting to", redirect_url)
return redirect(redirect_url)
print("[publish_name] access_token", access_token)
elif account_option == "user_account":
# Use the user's OAuth access token (stored in session)
access_token = request.session.get("user_access_token")
if not access_token:
return JsonResponse(
{
"status": "error",
"message": "[publish_name] User not authenticated for Wikidata.",
}
)
else:
return JsonResponse(
{
"status": "error",
"message": f"Failed to get CSRF token: {error_message}",
"message": "[publish_name] Invalid account option.",
}
)

# Process each entry: save locally, and conditionally publish to Wikidata
for entry in entries:
language_code = entry["language"]
name = entry["name"]
source = entry["source"]
description = entry["description"]
alias = entry["alias"]
description = entry.get("description", "")
alias = entry.get("alias", "")

# Get the language object
language = Language.objects.get(wikidata_code=language_code)

# Optionally publish to Wikidata
# Publish each entry to Wikidata if requested
if publish_to_wikidata:
# Publish the new label to Wikidata
# Add the label
response_label = add_info_to_wikidata_entity(
"wbsetlabel",
csrf_token,
wikidata_id,
name,
language_code,
lgusername,
"wbsetlabel", access_token, wikidata_id, name, language_code
)
if "error" in response_label:
return JsonResponse(
{
"status": "error",
"message": f"Failed to publish label: {response_label}",
"message": f"[publish_name] 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}",
}
# Add the description if provided
if description:
response_desc = add_info_to_wikidata_entity(
"wbsetdescription",
access_token,
wikidata_id,
description,
language_code,
)

# 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}",
}
if "error" in response_desc:
return JsonResponse(
{
"status": "error",
"message": f"[publish_name] Failed to publish description: {response_desc}",
}
)

# Add the alias if provided
if alias:
response_alias = add_info_to_wikidata_entity(
"wbsetaliases",
access_token,
wikidata_id,
alias,
language_code,
)

# Save to the local database
# for "name" in "lang" with "description"
if "error" in response_alias:
return JsonResponse(
{
"status": "error",
"message": f"[publish_name] Failed to publish alias: {response_alias}",
}
)

# Save entries to the local database
language = Language.objects.get(wikidata_code=language_code)
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,
Expand All @@ -161,9 +168,19 @@ def publish_name(request):
return JsonResponse(
{
"status": "success",
"message": "Data saved successfully!",
"message": "Data saved and published successfully!",
}
)

except Instrument.DoesNotExist:
return JsonResponse({"status": "error", "message": "Instrument not found"})
return JsonResponse({"status": "error", "message": "Invalid request method"})
return JsonResponse(
{"status": "error", "message": "[publish_name] Instrument not found"}
)
except Exception as e:
return JsonResponse(
{"status": "error", "message": "[publish_name] " + str(e)}
)

return JsonResponse(
{"status": "error", "message": "[publish_name] Invalid request method"}
)
Loading

0 comments on commit b5aa583

Please sign in to comment.