Skip to content

Commit

Permalink
feature add ids to works
Browse files Browse the repository at this point in the history
fixes #3430 and #1797

Not fully ready to merge, for at least two reasons:

Displays the ids on the work/edition page though #3430 currently
suggests this is optional, so may need removed until a design is
settled.
  • Loading branch information
davidscotson committed Oct 9, 2023
1 parent 0857026 commit 68dc489
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 1 deletion.
43 changes: 43 additions & 0 deletions openlibrary/plugins/openlibrary/js/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ export function initIdentifierValidation() {
});
}

export function initWorkIdentifierValidation() {
$('#workidentifiers').repeat({
vars: {prefix: 'work--'},
validate: function(data) {return validateWorkIdentifiers(data)},
});
}

export function initClassificationValidation() {
const dataConfig = JSON.parse(document.querySelector('#classifications').dataset.config);
$('#classifications').repeat({
Expand All @@ -248,6 +255,42 @@ export function initClassificationValidation() {
});
}

/**
* Called by initWorkIdentifierValidation(), along with tests in
* tests/unit/js/editEditionsPage.test.js, to validate the addition of new
* identifiers (ISBN, LCCN) to an edition.
* @param {Object} data data from the input form
* @returns {boolean} true if identifier passes validation
*/
export function validateWorkIdentifiers(data) {
const dataConfig = JSON.parse(document.querySelector('#workidentifiers').dataset.config);

if (data.name === '' || data.name === '---') {
return error('#workid-errors', 'workselect-id', dataConfig['Please select an identifier.'])
}
const label = $('#workselect-id').find(`option[value='${data.name}']`).html();
if (data.value === '') {
return error('#workid-errors', 'workid-value', dataConfig['You need to give a value to ID.'].replace(/ID/, label));
}

let validId = true;
if (data.name === 'lccn') {
validId = validateLccn(data, dataConfig, label);
}

// checking for duplicate identifier entry on all identifier types
// expects parsed ids so placed after validate
const entries = document.querySelectorAll(`.${data.name}`);
if (isIdDupe(entries, data.value) === true) {
return error('#workid-errors', 'workid-value', dataConfig['That ID already exists for this work.'].replace(/ID/, label));
}

if (validId === false) return false;

$('#workid-errors').hide();
return true;
}

export function initLanguageMultiInputAutocomplete() {
$(function() {
getJqueryElements('.multi-input-autocomplete--language').forEach(jqueryElement => {
Expand Down
4 changes: 4 additions & 0 deletions openlibrary/plugins/openlibrary/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ jQuery(function () {
const addRowButton = document.getElementById('add_row_button');
const roles = document.querySelector('#roles');
const identifiers = document.querySelector('#identifiers');
const workIdentifiers = document.querySelector('#workidentifiers');
const classifications = document.querySelector('#classifications');
const excerpts = document.getElementById('excerpts');
const links = document.getElementById('links');
Expand Down Expand Up @@ -162,6 +163,9 @@ jQuery(function () {
if (identifiers) {
module.initIdentifierValidation();
}
if (workIdentifiers) {
module.initWorkIdentifierValidation();
}
if (classifications) {
module.initClassificationValidation();
}
Expand Down
3 changes: 3 additions & 0 deletions openlibrary/plugins/upstream/addbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,9 @@ def save(self, formdata: web.Storage) -> None:
edition_data.works = [{'key': self.work.key}]

if self.work is not None:
identifiers = work_data.pop('identifiers', [])
self.work.set_identifiers(identifiers)

self.work.update(work_data)
saveutil.save(self.work)

Expand Down
63 changes: 62 additions & 1 deletion openlibrary/plugins/upstream/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
from openlibrary.core.models import Image
from openlibrary.core import lending

from openlibrary.plugins.upstream.utils import MultiDict, parse_toc, get_edition_config
from openlibrary.plugins.upstream.utils import (
MultiDict,
parse_toc,
get_edition_config,
get_work_config,
)
from openlibrary.plugins.upstream import account
from openlibrary.plugins.upstream import borrow
from openlibrary.plugins.worksearch.code import works_by_author
Expand Down Expand Up @@ -561,6 +566,62 @@ def get_covers(self, use_solr=True):
else:
return []

def get_identifiers(self):
"""Returns (name, value) pairs of all available identifiers."""
return self._process_identifiers(
get_work_config().identifiers, self.identifiers
)

def set_identifiers(self, identifiers):
"""Updates the work from identifiers specified as (name, value) pairs."""

d = {}
for id in identifiers:
# ignore bad values
if 'name' not in id or 'value' not in id:
continue
name, value = id['name'], id['value']
if name == 'lccn':
value = normalize_lccn(value)
# `None` in this field causes errors. See #7999.
if value is not None:
d.setdefault(name, []).append(value)

self.identifiers = {}

for name, value in d.items():
self.identifiers[name] = value

def _process_identifiers(self, config_, values):
id_map = {}
for id in config_:
id_map[id.name] = id
id.setdefault("label", id.name)
id.setdefault("url_format", None)

d = MultiDict()

def process(name, value):
if value:
if not isinstance(value, list):
value = [value]

id = id_map.get(name) or web.storage(
name=name, label=name, url_format=None
)
for v in value:
d[id.name] = web.storage(
name=id.name,
label=id.label,
value=v,
url=id.get('url') and id.url.replace('@@@', v.replace(' ', '')),
)

for name in values:
process(name, values[name])

return d

def get_covers_from_solr(self):
try:
w = self._solr_data
Expand Down
21 changes: 21 additions & 0 deletions openlibrary/plugins/upstream/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,27 @@ def _get_author_config():
return Storage(identifiers=identifiers)


@public
def get_work_config() -> Storage:
return _get_work_config()


@web.memoize
def _get_work_config():
"""Returns the work config.
The results are cached on the first invocation. Any changes to /config/work page require restarting the app.
This is is cached because fetching and creating the Thing object was taking about 20ms of time for each book request.
"""
thing = web.ctx.site.get('/config/work')
if hasattr(thing, "identifiers"):
identifiers = [Storage(t.dict()) for t in thing.identifiers if 'name' in t]
else:
identifiers = {}
return Storage(identifiers=identifiers)


@public
def get_edition_config() -> Storage:
return _get_edition_config()
Expand Down
69 changes: 69 additions & 0 deletions openlibrary/templates/books/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

$ this_title = work.title + ': ' + work.subtitle if work.get('subtitle', None) else work.title

$ work_config = get_work_config()

$var title: $this_title
$putctx("robots", "noindex,nofollow")

Expand Down Expand Up @@ -105,6 +107,73 @@ <h3 class="editFormBookAuthors">
</div>
</div>
</fieldset>
$ config = ({
$ 'Please select an identifier.': _('Please select an identifier.'),
$ 'You need to give a value to ID.': _('You need to give a value to ID.'),
$ 'ID ids cannot contain whitespace.': _('ID ids cannot contain whitespace.'),
$ 'That ID already exists for this work.': _('That ID already exists for this work.'),
$ 'Invalid ID format': _('Invalid ID format')
$ })
<fieldset class="major" id="workidentifiers" data-config="$dumps(config)">
<legend>$_("ID Numbers")</legend>
<div class="formBack">

<div id="id-errors" class="note" style="display: none"></div>
<div class="formElement">
<div class="label">
<label for="workselect-id">$_("Do you know any identifiers for this work?")</label>
<span class="tip">$_("Like, VIAF?")</span>
</div>
<div class="input">
<table class="identifiers">
<tr id="workidentifiers-form">
<td align="right">
<select name="name" id="workselect-id">
$ id_labels = dict((d.name, d.label) for d in work_config.identifiers)
$ id_dict = dict((id.name, id) for id in work_config.identifiers)

<option value="">$_('Select one of many...')</option>
$for id in work_config.identifiers:
<option value="$id.name">$id.label</option>

</select>
</td>
<td>
<input type="text" name="value" id="workid-value"/>
</td>
<td>
<button type="button" name="add" class="repeat-add larger">$_("Add")</button>
</td>
</tr>
<tbody id="workidentifiers-display">
<tr id="workidentifiers-template" style="display: none;" class="repeat-item">
<td align="right"><strong>{{\$("#workselect-id").find("option[value='" + name + "']").html()}}</strong></td>
<td>{{value}}
<input type="hidden" name="{{prefix}}identifiers--{{index}}--name" value="{{name}}"/>
<input type="hidden" name="{{prefix}}identifiers--{{index}}--value" value="{{value}}" class="{{name}}"/>
</td>
<td><a href="javascript:;" class="repeat-remove red plain" title="Remove this identifier">[x]</a></td>
</tr>
<tr>
<td align="right">Open Library</td>
<td>$work.key.split("/")[-1]</td>
<td></td>
</tr>
$for i, id in enumerate(work.get_identifiers().values()):
<tr id="workidentifiers--$i" class="repeat-item">
<td align="right"><strong>$id_labels.get(id.name, id.name)</strong></td>
<td>$id.value
<input type="hidden" name="work--identifiers--${i}--name" value="$id.name"/>
<input type="hidden" name="work--identifiers--${i}--value" value="$id.value" class="$id.name"/>
</td>
<td><a href="javascript:;" class="repeat-remove red plain" title="Remove this identifier">[x]</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset class="major">
<legend>$_("Add Excerpts")</legend>
<div class="formBack" id="excerpts">
Expand Down
14 changes: 14 additions & 0 deletions openlibrary/templates/type/edition/view.html
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,20 @@ <h4>$:_('Links <span class="gray small sansserif">outside Open Library</span>')<
<li><a href="$link.url">$link.title</a></li>
</ul>
</div>

<div class="section">
<h3 class="list-header collapse">$_("Work ID Numbers")</h3>
<dl class="meta">
$:display_identifiers("Open Library", [storage(url=None, value=work.key.split("/")[-1])])
$for name, values in work.get_identifiers().multi_items():
$ identifier_label = values[0].label
$:display_identifiers(identifier_label, values)

$:display_goodreads("Open Library", [storage(url=None, value=work.key.split("/")[-1])])
$for name, values in work.get_identifiers().multi_items():
$:display_goodreads(values[0].label, values)
</dl>
</div>
</div>

$if show_observations:
Expand Down

0 comments on commit 68dc489

Please sign in to comment.