-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subject tag changes #10138
Open
jimchamp
wants to merge
24
commits into
internetarchive:master
Choose a base branch
from
jimchamp:subject-tag-changes
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Subject tag changes #10138
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
7cbc6d6
Update flow for subject types
jimchamp e01b699
Create handler for adding typed tags
jimchamp 5f5f078
Remove tag query from `get_subjects()`
jimchamp 91e2f60
Update validation and tag creation functions
jimchamp a8b787e
Require `body` for subject tags
jimchamp 6bd18d3
Add disambiguations section to subject page
jimchamp 94b6ce1
Add tag search endpoint
jimchamp 66ccc15
Fix bug preventing creation of collection tags
jimchamp 00b8ece
Address `TODO` items
jimchamp 1c17c70
Remove inline JS
jimchamp 4678c5b
Update type hints
jimchamp 12fca88
Updates the order of Python imports
jimchamp be7be99
Fix `missing-i18n` test failure
jimchamp 0d81650
Remove redundant subject form inputs
jimchamp cef31a2
Disambiguation updates
jimchamp 69404a7
Return to subject page after tag creation or edit
jimchamp 95f67e5
Update subject page
jimchamp 8224657
Update tag validations
jimchamp 7d697be
Add `body` field to all add and edit tag forms
jimchamp 1b8ad73
Add `tag_type` to tag view
jimchamp 2b2e593
Consolidate tag edit/add forms
jimchamp 860a95e
Fix up for `ruff`
jimchamp 5ba6a4b
Require `tag_description`
jimchamp 0ed861a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -560,4 +560,3 @@ jQuery(function () { | |
.then(module => module.initGoBackLinks(backLinks)) | ||
} | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,73 @@ | ||
"""Handlers for adding and editing tags.""" | ||
|
||
from typing import NoReturn | ||
|
||
import web | ||
|
||
from infogami.core.db import ValidationException | ||
from infogami.infobase.client import ClientException | ||
from infogami.utils import delegate | ||
from infogami.utils.view import add_flash_message, public | ||
from openlibrary.accounts import get_current_user | ||
from openlibrary.plugins.upstream import spamcheck, utils | ||
from openlibrary.plugins.upstream import spamcheck | ||
from openlibrary.plugins.upstream.addbook import safe_seeother, trim_doc | ||
from openlibrary.plugins.upstream.models import Tag | ||
from openlibrary.plugins.upstream.utils import render_template | ||
|
||
|
||
@public | ||
def get_tag_types(): | ||
return ["subject", "work", "collection"] | ||
return TAG_TYPES | ||
|
||
|
||
@public | ||
def get_subject_tag_types(): | ||
return SUBJECT_SUB_TYPES | ||
|
||
|
||
SUBJECT_SUB_TYPES = ["subject", "person", "place", "time"] | ||
TAG_TYPES = SUBJECT_SUB_TYPES + ["collection"] | ||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This are being used as display strings. |
||
|
||
|
||
def validate_tag(tag): | ||
return tag.get('name', '') and tag.get('tag_type', '') | ||
return tag.get('name', '') and tag.get('tag_description', '') and tag.get('tag_type', '') in get_tag_types() and tag.get('body') | ||
|
||
|
||
def validate_subject_tag(tag): | ||
return validate_tag(tag) and tag.get('tag_type', '') in get_subject_tag_types() | ||
|
||
|
||
def create_tag(tag: dict): | ||
if not validate_tag(tag): | ||
raise ValueError("Invalid data for tag creation") | ||
|
||
d = { | ||
"type": {"key": "/type/tag"}, | ||
**tag, | ||
} | ||
|
||
tag = Tag.create(trim_doc(d)) | ||
return tag | ||
|
||
|
||
def create_subject_tag(tag: dict): | ||
if not validate_subject_tag(tag): | ||
raise ValueError("Invalid data for subject tag creation") | ||
|
||
d = { | ||
"type": {"key": "/type/tag"}, | ||
**tag, | ||
} | ||
|
||
tag = Tag.create(trim_doc(d)) | ||
return tag | ||
|
||
|
||
def find_match(name: str, tag_type: str) -> str: | ||
""" | ||
Tries to find an existing tag that matches the data provided by the user. | ||
Returns the key of the matching tag, or an empty string if no such tag exists. | ||
""" | ||
matches = Tag.find(name, tag_type=tag_type) | ||
return matches[0] if matches else '' | ||
|
||
|
||
class addtag(delegate.page): | ||
|
@@ -30,14 +76,15 @@ class addtag(delegate.page): | |
def GET(self): | ||
"""Main user interface for adding a tag to Open Library.""" | ||
if not (patron := get_current_user()): | ||
# NOTE : will lose any query params on login redirect | ||
raise web.seeother(f'/account/login?redirect={self.path}') | ||
|
||
if not self.has_permission(patron): | ||
raise web.unauthorized(message='Permission denied to add tags') | ||
|
||
i = web.input(name=None, type=None, sub_type=None) | ||
|
||
return render_template('tag/add', i.name, i.type, subject_type=i.sub_type) | ||
return render_template('type/tag/form', i) | ||
|
||
def has_permission(self, user) -> bool: | ||
""" | ||
|
@@ -52,7 +99,7 @@ def POST(self): | |
name="", | ||
tag_type="", | ||
tag_description="", | ||
tag_plugins="", | ||
body="", | ||
) | ||
|
||
if spamcheck.is_spam(i, allow_privileged_edits=True): | ||
|
@@ -66,42 +113,24 @@ def POST(self): | |
if not self.has_permission(patron): | ||
raise web.unauthorized(message='Permission denied to add tags') | ||
|
||
i = utils.unflatten(i) | ||
|
||
if not validate_tag(i): | ||
if not self.validate_input(i): | ||
raise web.badrequest() | ||
|
||
match = self.find_match(i) # returns None or Tag (if match found) | ||
|
||
if match: | ||
# tag match | ||
return self.tag_match(match) | ||
else: | ||
# no match | ||
return self.no_match(i) | ||
|
||
def find_match(self, i: web.utils.Storage): | ||
""" | ||
Tries to find an existing tag that matches the data provided by the user. | ||
""" | ||
return Tag.find(i.name, i.tag_type) | ||
if match := find_match(i.name, i.tag_type): | ||
# A tag with the same name and type already exists | ||
add_flash_message( | ||
'error', | ||
f'A matching tag with the same name and type already exists: <a href="{match}">{match}</a>' | ||
) | ||
return render_template('type/tag/form', i) | ||
|
||
def tag_match(self, match: list) -> NoReturn: | ||
""" | ||
Action for when an existing tag has been found. | ||
Redirect user to the found tag's edit page to add any missing details. | ||
""" | ||
tag = web.ctx.site.get(match[0]) | ||
raise safe_seeother(tag.key + "/edit") | ||
tag = create_tag(i) | ||
raise safe_seeother(tag.key) | ||
|
||
def no_match(self, i: web.utils.Storage) -> NoReturn: | ||
""" | ||
Action to take when no tags are found. | ||
Creates a new Tag. | ||
Redirects the user to the tag's home page | ||
""" | ||
key = Tag.create(i.name, i.tag_description, i.tag_type, i.tag_plugins) | ||
raise safe_seeother(key) | ||
def validate_input(self, i): | ||
if i.tag_type in get_subject_tag_types(): | ||
return validate_subject_tag(i) | ||
return validate_tag(i) | ||
|
||
|
||
class tag_edit(delegate.page): | ||
|
@@ -114,41 +143,69 @@ def GET(self, key): | |
web.ctx.fullpath, | ||
"Permission denied to edit " + key + ".", | ||
) | ||
|
||
i = web.input(redir=None) | ||
tag = web.ctx.site.get(key) | ||
if tag is None: | ||
raise web.notfound() | ||
|
||
return render_template('type/tag/edit', tag) | ||
return render_template("type/tag/form", tag, redirect=i.redir) | ||
|
||
def POST(self, key): | ||
if not web.ctx.site.can_write(key): | ||
return render_template( | ||
"permission_denied", | ||
web.ctx.fullpath, | ||
"Permission denied to edit " + key + ".", | ||
) | ||
tag = web.ctx.site.get(key) | ||
if tag is None: | ||
raise web.notfound() | ||
|
||
i = web.input(_comment=None) | ||
formdata = self.process_input(i) | ||
i = web.input(_comment=None, redir=None) | ||
formdata = trim_doc(i) | ||
if not formdata or not self.validate(formdata, formdata.tag_type): | ||
raise web.badrequest() | ||
if tag.tag_type != formdata.tag_type: | ||
match = find_match(formdata.name, formdata.tag_type) | ||
if match: | ||
# A tag with the same name and type already exists | ||
add_flash_message( | ||
'error', | ||
f'A matching tag with the same name and type already exists: <a href="{match}">{match}</a>' | ||
) | ||
return render_template("type/tag/form", formdata, redirect=i.redir) | ||
|
||
try: | ||
if not formdata or not validate_tag(formdata): | ||
raise web.badrequest() | ||
elif "_delete" in i: | ||
if "_delete" in i: | ||
tag = web.ctx.site.new( | ||
key, {"key": key, "type": {"key": "/type/delete"}} | ||
) | ||
tag._save(comment=i._comment) | ||
raise safe_seeother(key) | ||
else: | ||
tag.update(formdata) | ||
tag._save(comment=i._comment) | ||
raise safe_seeother(key) | ||
tag.update(formdata) | ||
tag._save(comment=i._comment) | ||
raise safe_seeother(i.redir if i.redir else key) | ||
except (ClientException, ValidationException) as e: | ||
add_flash_message('error', str(e)) | ||
return render_template("type/tag/edit", tag) | ||
return render_template("type/tag/form", tag, redirect=i.redir) | ||
|
||
def validate(self, data, tag_type): | ||
if tag_type in SUBJECT_SUB_TYPES: | ||
return validate_subject_tag(data) | ||
else: | ||
return validate_tag(data) | ||
|
||
|
||
class tag_search(delegate.page): | ||
path = "/tags/-/([^/]+):([^/]+)" | ||
|
||
def process_input(self, i): | ||
i = utils.unflatten(i) | ||
tag = trim_doc(i) | ||
return tag | ||
def GET(self, type, name): | ||
# TODO : Search is case sensitive | ||
# TODO : Handle spaces and special characters | ||
matches = Tag.find(name, tag_type=type) | ||
if matches: | ||
return web.seeother(matches[0]) | ||
return render_template("notfound", f"/tags/-/{type}:{name}", create=False) | ||
|
||
|
||
def setup(): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.