Skip to content
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

Tag type creation and crud functions #7928

Merged
merged 10 commits into from
Jul 3, 2023
39 changes: 39 additions & 0 deletions openlibrary/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime, timedelta
import logging
import web
import json
import requests
from typing import Any
from collections import defaultdict
Expand Down Expand Up @@ -1116,6 +1117,42 @@ def get_default_cover(self):
return Image(web.ctx.site, "b", cover_id)


# TODO: expand on tag model
class Tag(Thing):
"""Class to represent /type/tag objects in OL."""

@classmethod
JaydenTeoh marked this conversation as resolved.
Show resolved Hide resolved
def get_tag(cls, tag_name, tag_type):
"""Returns a Tag object for a given tag name and tag type."""
q = {'type': '/type/tag', 'name': tag_name, 'tag_type': tag_type}
match = list(web.ctx.site.things(q))
return match[0] if match else None

@classmethod
def create_tag(cls, tag_name, tag_description, tag_type, tag_plugins):
"""Creates a new Tag object."""
key = web.ctx.site.new_key('/type/tag')
web.ctx.path = key
web.ctx.site.save(
{
'key': key,
'name': tag_name,
'tag_description': tag_description,
'tag_type': tag_type,
'tag_plugins': json.loads(tag_plugins or "[]"),
'type': dict(key='/type/tag'),
},
comment='New Tag',
)
return key

def url(self, suffix="", **params):
return self.get_url(suffix, **params)

def get_url_suffix(self):
return self.name or "unnamed"


@dataclass
class LoggedBooksData:
"""
Expand Down Expand Up @@ -1164,6 +1201,7 @@ def register_models():
client.register_thing_class('/type/user', User)
client.register_thing_class('/type/list', List)
client.register_thing_class('/type/usergroup', UserGroup)
client.register_thing_class('/type/tag', Tag)


def register_types():
Expand All @@ -1174,6 +1212,7 @@ def register_types():
types.register_type('^/books/[^/]*$', '/type/edition')
types.register_type('^/works/[^/]*$', '/type/work')
types.register_type('^/languages/[^/]*$', '/type/language')
types.register_type('^/tags/[^/]*$', '/type/tag')

types.register_type('^/usergroup/[^/]*$', '/type/usergroup')
types.register_type('^/permission/[^/]*$', '/type/permission')
Expand Down
2 changes: 2 additions & 0 deletions openlibrary/core/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ def get_schema():
schema.add_table_group('work', '/type/work', datatypes)
schema.add_table_group('publisher', '/type/publisher', datatypes)
schema.add_table_group('subject', '/type/subject', datatypes)
schema.add_table_group('tag', '/type/tag', datatypes)

schema.add_seq('/type/edition', '/books/OL%dM')
schema.add_seq('/type/author', '/authors/OL%dA')

schema.add_seq('/type/work', '/works/OL%dW')
schema.add_seq('/type/publisher', '/publishers/OL%dP')
schema.add_seq('/type/tag', '/tags/OL%dT')

_sql = schema.sql

Expand Down
60 changes: 60 additions & 0 deletions openlibrary/plugins/openlibrary/types/tag.type
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "Tag",
"key": "/type/tag",
"kind": "regular",
"created": {
"type": "/type/datetime",
"value": "2023-04-05T22:27:36.162339"
},
"last_modified": {
"type": "/type/datetime",
"value": "2023-04-05T22:37:06.504291"
},
"latest_revision": 3,
"type": {
"key": "/type/type"
},
"properties": [
{
"expected_type": {
"key": "/type/string",
},
"name": "name",
"type": {
"key": "/type/property"
},
"unique": true
},
{
"expected_type": {
"key": "/type/string",
},
"name": "tag_description",
"type": {
"key": "/type/property"
},
"unique": true
},
{
"expected_type": {
"key": "/type/string",
},
"name": "tag_plugins",
"type": {
"key": "/type/property"
},
"unique": true
},
{
"expected_type": {
"key": "/type/string",
},
"name": "tag_type",
"type": {
"key": "/type/property"
},
"unique": true
},
],
"revision": 3
}
148 changes: 148 additions & 0 deletions openlibrary/plugins/upstream/addtag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Handlers for adding and editing tags."""

import web
import json

from typing import NoReturn

from infogami.core.db import ValidationException
from infogami.infobase import common
from infogami.utils.view import add_flash_message, public
from infogami.infobase.client import ClientException
from infogami.utils import delegate

from openlibrary.plugins.openlibrary.processors import urlsafe
from openlibrary.i18n import gettext as _
import logging

from openlibrary.plugins.upstream import spamcheck, utils
from openlibrary.plugins.upstream.models import Tag
from openlibrary.plugins.upstream.addbook import get_recaptcha, safe_seeother, trim_doc
from openlibrary.plugins.upstream.utils import render_template

logger = logging.getLogger("openlibrary.tag")


class addtag(delegate.page):
path = '/tag/add'

def GET(self):
"""Main user interface for adding a tag to Open Library."""

if not self.has_permission():
raise common.PermissionDenied(message='Permission denied to add tags')

return render_template('tag/add', recaptcha=get_recaptcha())

def has_permission(self) -> bool:
"""
Can a tag be added?
"""
return web.ctx.user and (
web.ctx.user.is_usergroup_member('/usergroup/super-librarians')
)

def POST(self):
i = web.input(
tag_name="",
tag_type="",
tag_description="",
tag_plugins="",
)

if spamcheck.is_spam(i, allow_privileged_edits=True):
return render_template(
"message.html", "Oops", 'Something went wrong. Please try again later.'
)

if not web.ctx.site.get_user():
recap = get_recaptcha()
if recap and not recap.validate():
return render_template(
'message.html',
'Recaptcha solution was incorrect',
'Please <a href="javascript:history.back()">go back</a> and try again.',
)

i = utils.unflatten(i)
match = self.find_match(i) # returns None or Tag (if match found)

return self.tag_match(match) if match else 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.get_tag(i.tag_name, i.tag_type)

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)
raise safe_seeother(tag.key + "/edit")

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_tag(i.tag_name, i.tag_description, i.tag_type, i.tag_plugins)
raise safe_seeother(key)


class tag_edit(delegate.page):
path = r"(/tags/OL\d+T)/edit"

def GET(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()

return render_template('type/tag/edit', tag)

def POST(self, key):
tag = web.ctx.site.get(key)
if tag is None:
raise web.notfound()

i = web.input(_comment=None)
formdata = self.process_input(i)
try:
if not formdata:
raise web.badrequest()
elif "_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)
except (ClientException, ValidationException) as e:
add_flash_message('error', str(e))
return render_template("type/tag/edit", tag)

def process_input(self, i):
i = utils.unflatten(i)
if i.tag_plugins:
i.tag_plugins = json.loads(i.tag_plugins)
tag = trim_doc(i)
return tag


def setup():
"""Do required setup."""
pass
3 changes: 2 additions & 1 deletion openlibrary/plugins/upstream/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from openlibrary import accounts

from openlibrary.plugins.upstream import addbook, covers, models, utils
from openlibrary.plugins.upstream import addbook, addtag, covers, models, utils
from openlibrary.plugins.upstream import spamcheck
from openlibrary.plugins.upstream import merge_authors
from openlibrary.plugins.upstream import edits
Expand Down Expand Up @@ -384,6 +384,7 @@ def setup():
models.setup()
utils.setup()
addbook.setup()
addtag.setup()
covers.setup()
merge_authors.setup()
# merge_works.setup() # ILE code
Expand Down
7 changes: 7 additions & 0 deletions openlibrary/plugins/upstream/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,12 @@ def get_seed(self, seed):
return models.Seed(self.get_list(), seed)


class Tag(models.Tag):
"""Class to represent /type/tag objects in Open Library."""

pass


def setup():
models.register_models()

Expand All @@ -1018,6 +1024,7 @@ def setup():
client.register_thing_class('/type/place', SubjectPlace)
client.register_thing_class('/type/person', SubjectPerson)
client.register_thing_class('/type/user', User)
client.register_thing_class('/type/tag', Tag)

client.register_changeset_class(None, Changeset) # set the default class
client.register_changeset_class('merge-authors', MergeAuthors)
Expand Down