diff options
author | bnewbold <bnewbold@archive.org> | 2020-08-03 18:00:41 +0000 |
---|---|---|
committer | bnewbold <bnewbold@archive.org> | 2020-08-03 18:00:41 +0000 |
commit | 4702bee24dde8bae64df76ad411a6d8329cc9bdf (patch) | |
tree | 221ec7bd8d77bddb2dec344c19253cca65156911 /python/fatcat_web | |
parent | 5037642d7775c638d035c2faed8094537dfaf94d (diff) | |
parent | 31f59b4b0ba7ff95b685c8826a7d019fb142f65c (diff) | |
download | fatcat-4702bee24dde8bae64df76ad411a6d8329cc9bdf.tar.gz fatcat-4702bee24dde8bae64df76ad411a6d8329cc9bdf.zip |
Merge branch 'bnewbold-editing' into 'master'
editing improvements
See merge request webgroup/fatcat!73
Diffstat (limited to 'python/fatcat_web')
20 files changed, 825 insertions, 106 deletions
diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index ffce35f3..8e3b03b0 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -1,8 +1,10 @@ +from typing import Optional + from flask import render_template, abort, redirect, session, flash from flask_login import login_required -from fatcat_openapi_client import Editgroup +from fatcat_openapi_client import * from fatcat_openapi_client.rest import ApiException from fatcat_tools.transforms import * from fatcat_web import app, api, auth_api @@ -13,6 +15,104 @@ from fatcat_web.entity_helpers import * ### Helper Methods ########################################################## +def generic_entity_create_from_toml(user_api, entity_type: str, editgroup_id: str, toml_str: str) -> EntityEdit: + if entity_type == 'container': + entity = entity_from_toml(toml_str, ContainerEntity) + edit = user_api.create_container(editgroup_id, entity) + elif entity_type == 'creator': + entity = entity_from_toml(toml_str, CreatorEntity) + edit = user_api.create_creator(editgroup_id, entity) + elif entity_type == 'file': + entity = entity_from_toml(toml_str, FileEntity) + edit = user_api.create_file(editgroup_id, entity) + elif entity_type == 'fileset': + entity = entity_from_toml(toml_str, FilesetEntity) + edit = user_api.create_fileset(editgroup_id, entity) + elif entity_type == 'webcapture': + entity = entity_from_toml(toml_str, WebcaptureEntity) + edit = user_api.create_webcapture(editgroup_id, entity) + elif entity_type == 'release': + entity = entity_from_toml(toml_str, ReleaseEntity) + edit = user_api.create_release(editgroup_id, entity) + elif entity_type == 'work': + entity = entity_from_toml(toml_str, WorkEntity) + edit = user_api.create_work(editgroup_id, entity) + else: + raise NotImplementedError + return edit + +def generic_entity_delete_edit(user_api, entity_type: str, editgroup_id: str, edit_id: str) -> None: + try: + if entity_type == 'container': + user_api.delete_container_edit(editgroup_id, edit_id) + elif entity_type == 'creator': + user_api.delete_creator_edit(editgroup_id, edit_id) + elif entity_type == 'file': + user_api.delete_file_edit(editgroup_id, edit_id) + elif entity_type == 'fileset': + user_api.delete_fileset_edit(editgroup_id, edit_id) + elif entity_type == 'webcapture': + user_api.delete_webcapture_edit(editgroup_id, edit_id) + elif entity_type == 'release': + user_api.delete_release_edit(editgroup_id, edit_id) + elif entity_type == 'work': + user_api.delete_work_edit(editgroup_id, edit_id) + else: + raise NotImplementedError + except ApiException as ae: + if ae.status == 404: + pass + else: + raise ae + +def generic_entity_delete_entity(user_api, entity_type: str, editgroup_id: str, entity_ident: str) -> None: + try: + if entity_type == 'container': + edit = user_api.delete_container(editgroup_id, entity_ident) + elif entity_type == 'creator': + edit = user_api.delete_creator(editgroup_id, entity_ident) + elif entity_type == 'file': + edit = user_api.delete_file(editgroup_id, entity_ident) + elif entity_type == 'fileset': + edit = user_api.delete_fileset(editgroup_id, entity_ident) + elif entity_type == 'webcapture': + edit = user_api.delete_webcapture(editgroup_id, entity_ident) + elif entity_type == 'release': + edit = user_api.delete_release(editgroup_id, entity_ident) + elif entity_type == 'work': + edit = user_api.delete_work(editgroup_id, entity_ident) + else: + raise NotImplementedError + except ApiException as ae: + raise ae + return edit + +def generic_entity_update_from_toml(user_api, entity_type: str, editgroup_id: str, existing_ident, toml_str: str) -> EntityEdit: + if entity_type == 'container': + entity = entity_from_toml(toml_str, ContainerEntity) + edit = user_api.update_container(editgroup_id, existing_ident, entity) + elif entity_type == 'creator': + entity = entity_from_toml(toml_str, CreatorEntity) + edit = user_api.update_creator(editgroup_id, existing_ident, entity) + elif entity_type == 'file': + entity = entity_from_toml(toml_str, FileEntity) + edit = user_api.update_file(editgroup_id, existing_ident, entity) + elif entity_type == 'fileset': + entity = entity_from_toml(toml_str, FilesetEntity) + edit = user_api.update_fileset(editgroup_id, existing_ident, entity) + elif entity_type == 'webcapture': + entity = entity_from_toml(toml_str, WebcaptureEntity) + edit = user_api.update_webcapture(editgroup_id, existing_ident, entity) + elif entity_type == 'release': + entity = entity_from_toml(toml_str, ReleaseEntity) + edit = user_api.update_release(editgroup_id, existing_ident, entity) + elif entity_type == 'work': + entity = entity_from_toml(toml_str, WorkEntity) + edit = user_api.update_work(editgroup_id, existing_ident, entity) + else: + raise NotImplementedError + return edit + def form_editgroup_get_or_create(api, edit_form): """ This function expects a submitted, validated edit form @@ -75,7 +175,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template try: editgroup = api.get_editgroup(editgroup_id) except ApiException as ae: - abort(ae.status) + raise ae # check that editgroup is edit-able if editgroup.changelog_index != None: @@ -138,14 +238,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template # a "update pointer" edit existing.revision = None try: - if entity_type == 'container': - user_api.delete_container_edit(editgroup.editgroup_id, existing_edit.edit_id) - elif entity_type == 'file': - user_api.delete_file_edit(editgroup.editgroup_id, existing_edit.edit_id) - elif entity_type == 'release': - user_api.delete_release_edit(editgroup.editgroup_id, existing_edit.edit_id) - else: - raise NotImplementedError + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) except ApiException as ae: if ae.status == 404: pass @@ -194,7 +287,13 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template existing_ident=existing_ident, editgroup=editgroup, potential_editgroups=potential_editgroups), status -def generic_edit_delete(editgroup_id, entity_type, edit_id): +def generic_entity_toml_edit(editgroup_id, entity_type, existing_ident, edit_template): + """ + Similar to generic_entity_edit(), but for TOML editing mode. + + Handles both creation and update/edit paths. + """ + # fetch editgroup (if set) or 404 editgroup = None if editgroup_id: @@ -205,23 +304,185 @@ def generic_edit_delete(editgroup_id, entity_type, edit_id): # check that editgroup is edit-able if editgroup.changelog_index != None: - abort(400, "Editgroup already merged") + flash("Editgroup already merged") + abort(400) + + # fetch entity (if set) or 404 + existing = None + existing_edit = None + if editgroup and existing_ident: + existing, existing_edit = generic_get_editgroup_entity(editgroup, entity_type, existing_ident) + elif existing_ident: + existing = generic_get_entity(entity_type, existing_ident) + + # parse form (if submitted) + status = 200 + form = EntityTomlForm() + + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if not editgroup: + editgroup = form_editgroup_get_or_create(user_api, form) + + if editgroup: + + if not existing_ident: # it's a create + try: + edit = generic_entity_create_from_toml(user_api, entity_type, editgroup.editgroup_id, form.toml.data) + except ValueError as ve: + form.toml.errors = [ve] + status = 400 + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: # it's an update + # TODO: some danger of wiping database state here is + # "updated edit" causes, eg, a 4xx error. Better to allow + # this in the API itself. For now, form validation *should* + # catch most errors, and if not editor can hit back and try + # again. This means, need to allow failure of deletion. + if existing_edit: + # need to clear revision on object or this becomes just + # a "update pointer" edit + existing.revision = None + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) + try: + edit = generic_entity_update_from_toml(user_api, entity_type, editgroup.editgroup_id, existing.ident, form.toml.data) + except ValueError as ve: + form.toml.errors = [ve] + status = 400 + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: + status = 400 + elif form.errors: + status = 400 + app.log.info("form errors (did not validate): {}".format(form.errors)) + + else: # form is not submitted + if existing: + form = EntityTomlForm.from_entity(existing) + + editor_editgroups = api.get_editor_editgroups(session['editor']['editor_id'], limit=20) + potential_editgroups = [e for e in editor_editgroups if e.changelog_index == None and e.submitted == None] + + if not form.is_submitted(): + # default to most recent not submitted, fallback to "create new" + form.editgroup_id.data = "" + if potential_editgroups: + form.editgroup_id.data = potential_editgroups[0].editgroup_id + + return render_template(edit_template, form=form, entity_type=entity_type, + existing_ident=existing_ident, editgroup=editgroup, + potential_editgroups=potential_editgroups), status + +def generic_entity_delete(editgroup_id: Optional[str], entity_type: str, existing_ident: str): + """ + Similar to generic_entity_edit(), but for deleting entities. This is a bit + simpler! + + Handles both creation and update/edit paths. + """ + + # fetch editgroup (if set) or 404 + editgroup = None + if editgroup_id: + try: + editgroup = api.get_editgroup(editgroup_id) + except ApiException as ae: + raise ae + + # check that editgroup is edit-able + if editgroup.changelog_index != None: + flash("Editgroup already merged") + abort(400) + + # fetch entity (if set) or 404 + existing = None + existing_edit = None + if editgroup and existing_ident: + existing, existing_edit = generic_get_editgroup_entity(editgroup, entity_type, existing_ident) + elif existing_ident: + existing = generic_get_entity(entity_type, existing_ident) + + # parse form (if submitted) + status = 200 + form = EntityEditForm() + + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if not editgroup: + editgroup = form_editgroup_get_or_create(user_api, form) + + if editgroup: + # TODO: some danger of wiping database state here is + # "updated edit" causes, eg, a 4xx error. Better to allow + # this in the API itself. For now, form validation *should* + # catch most errors, and if not editor can hit back and try + # again. This means, need to allow failure of deletion. + if existing_edit: + # need to clear revision on object or this becomes just + # a "update pointer" edit + existing.revision = None + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id) + try: + edit = generic_entity_delete_entity(user_api, entity_type, editgroup.editgroup_id, existing.ident) + except ApiException as ae: + app.log.warning(ae) + raise ae + if status == 200: + return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident)) + else: + status = 400 + elif form.errors: + status = 400 + app.log.info("form errors (did not validate): {}".format(form.errors)) + + else: # form is not submitted + if existing: + form = EntityTomlForm.from_entity(existing) + + editor_editgroups = api.get_editor_editgroups(session['editor']['editor_id'], limit=20) + potential_editgroups = [e for e in editor_editgroups if e.changelog_index == None and e.submitted == None] + + if not form.is_submitted(): + # default to most recent not submitted, fallback to "create new" + form.editgroup_id.data = "" + if potential_editgroups: + form.editgroup_id.data = potential_editgroups[0].editgroup_id + + return render_template("entity_delete.html", form=form, entity_type=entity_type, + existing_ident=existing_ident, editgroup=editgroup, + potential_editgroups=potential_editgroups), status + +def generic_edit_delete(editgroup_id, entity_type, edit_id): + # fetch editgroup (if set) or 404 + editgroup = None + if editgroup_id: + try: + editgroup = api.get_editgroup(editgroup_id) + except ApiException as ae: + abort(ae.status) + + # check that editgroup is edit-able + if editgroup.changelog_index != None: + flash("Editgroup already merged") + abort(400) # API on behalf of user user_api = auth_api(session['api_token']) # do the deletion - try: - if entity_type == 'container': - user_api.delete_container_edit(editgroup.editgroup_id, edit_id) - elif entity_type == 'file': - user_api.delete_file_edit(editgroup.editgroup_id, edit_id) - elif entity_type == 'release': - user_api.delete_release_edit(editgroup.editgroup_id, edit_id) - else: - raise NotImplementedError - except ApiException as ae: - raise ae + generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, edit_id) return redirect("/editgroup/{}".format(editgroup_id)) @@ -234,19 +495,43 @@ def container_create_view(): @app.route('/container/<ident>/edit', methods=['GET', 'POST']) @login_required -def container_edit(ident): +def container_edit_view(ident): return generic_entity_edit(None, 'container', ident, 'container_edit.html') +@app.route('/container/<ident>/delete', methods=['GET', 'POST']) +@login_required +def container_delete_view(ident): + return generic_entity_delete(None, 'container', ident) + @app.route('/editgroup/<editgroup_id>/container/<ident>/edit', methods=['GET', 'POST']) @login_required -def container_editgroup_edit(editgroup_id, ident): +def container_editgroup_edit_view(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'container', ident, 'container_edit.html') +@app.route('/editgroup/<editgroup_id>/container/<ident>/delete', methods=['GET', 'POST']) +@login_required +def container_editgroup_delete_view(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'container', ident) + @app.route('/editgroup/<editgroup_id>/container/edit/<edit_id>/delete', methods=['POST']) @login_required def container_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'container', edit_id) +@app.route('/creator/<ident>/delete', methods=['GET', 'POST']) +@login_required +def creator_delete_view(ident): + return generic_entity_delete(None, 'creator', ident) + +@app.route('/editgroup/<editgroup_id>/creator/edit/<edit_id>/delete', methods=['POST']) +def creator_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'creator', edit_id) + +@app.route('/editgroup/<editgroup_id>/creator/<ident>/delete', methods=['GET', 'POST']) +@login_required +def creator_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'creator', ident) + @app.route('/file/create', methods=['GET', 'POST']) @login_required def file_create_view(): @@ -254,19 +539,57 @@ def file_create_view(): @app.route('/file/<ident>/edit', methods=['GET', 'POST']) @login_required -def file_edit(ident): +def file_edit_view(ident): return generic_entity_edit(None, 'file', ident, 'file_edit.html') +@app.route('/file/<ident>/delete', methods=['GET', 'POST']) +@login_required +def file_delete_view(ident): + return generic_entity_delete(None, 'file', ident) + @app.route('/editgroup/<editgroup_id>/file/<ident>/edit', methods=['GET', 'POST']) @login_required -def file_editgroup_edit(editgroup_id, ident): +def file_editgroup_edit_view(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'file', ident, 'file_edit.html') +@app.route('/editgroup/<editgroup_id>/file/<ident>/delete', methods=['GET', 'POST']) +@login_required +def file_editgroup_delete_view(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'file', ident) + @app.route('/editgroup/<editgroup_id>/file/edit/<edit_id>/delete', methods=['POST']) @login_required def file_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'file', edit_id) +@app.route('/fileset/<ident>/delete', methods=['GET', 'POST']) +@login_required +def fileset_delete_view(ident): + return generic_entity_delete(None, 'fileset', ident) + +@app.route('/editgroup/<editgroup_id>/fileset/edit/<edit_id>/delete', methods=['POST']) +def fileset_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'fileset', edit_id) + +@app.route('/editgroup/<editgroup_id>/fileset/<ident>/delete', methods=['GET', 'POST']) +@login_required +def fileset_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'fileset', ident) + +@app.route('/webcapture/<ident>/delete', methods=['GET', 'POST']) +@login_required +def webcapture_delete_view(ident): + return generic_entity_delete(None, 'webcapture', ident) + +@app.route('/editgroup/<editgroup_id>/webcapture/edit/<edit_id>/delete', methods=['POST']) +def webcapture_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'webcapture', edit_id) + +@app.route('/editgroup/<editgroup_id>/webcapture/<ident>/delete', methods=['GET', 'POST']) +@login_required +def webcapture_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'webcapture', ident) + @app.route('/release/create', methods=['GET', 'POST']) @login_required def release_create_view(): @@ -274,82 +597,208 @@ def release_create_view(): @app.route('/release/<ident>/edit', methods=['GET', 'POST']) @login_required -def release_edit(ident): +def release_edit_view(ident): return generic_entity_edit(None, 'release', ident, 'release_edit.html') +@app.route('/release/<ident>/delete', methods=['GET', 'POST']) +@login_required +def release_delete_view(ident): + return generic_entity_delete(None, 'release', ident) + @app.route('/editgroup/<editgroup_id>/release/<ident>/edit', methods=['GET', 'POST']) @login_required def release_editgroup_edit(editgroup_id, ident): return generic_entity_edit(editgroup_id, 'release', ident, 'release_edit.html') +@app.route('/editgroup/<editgroup_id>/release/<ident>/delete', methods=['GET', 'POST']) +@login_required +def release_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'release', ident) + @app.route('/editgroup/<editgroup_id>/release/edit/<edit_id>/delete', methods=['POST']) @login_required def release_edit_delete(editgroup_id, edit_id): return generic_edit_delete(editgroup_id, 'release', edit_id) +@app.route('/work/<ident>/delete', methods=['GET', 'POST']) +@login_required +def work_delete_view(ident): + return generic_entity_delete(None, 'work', ident) -### Not-Implemented Views ################################################### +@app.route('/editgroup/<editgroup_id>/work/edit/<edit_id>/delete', methods=['POST']) +def work_edit_delete(editgroup_id, edit_id): + return generic_edit_delete(editgroup_id, 'work', edit_id) + +@app.route('/editgroup/<editgroup_id>/work/<ident>/delete', methods=['GET', 'POST']) +@login_required +def work_editgroup_delete(editgroup_id, ident): + return generic_entity_delete(editgroup_id, 'work', ident) + +### TOML Views ############################################################## + +@app.route('/container/create/toml', methods=['GET', 'POST']) +@login_required +def container_create_toml_view(): + return generic_entity_toml_edit(None, 'container', None, 'entity_create_toml.html') + +@app.route('/container/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def container_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'container', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/container/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def container_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'container', ident, 'entity_edit_toml.html') + +@app.route('/creator/create/toml', methods=['GET', 'POST']) +@login_required +def creator_create_toml_view(): + return generic_entity_toml_edit(None, 'creator', None, 'entity_create_toml.html') + +@app.route('/creator/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def creator_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'creator', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/creator/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def creator_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'creator', ident, 'entity_edit_toml.html') + +@app.route('/file/create/toml', methods=['GET', 'POST']) +@login_required +def file_create_toml_view(): + return generic_entity_toml_edit(None, 'file', None, 'entity_create_toml.html') + +@app.route('/file/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def file_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'file', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/file/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def file_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'file', ident, 'entity_edit_toml.html') + +@app.route('/fileset/create/toml', methods=['GET', 'POST']) +@login_required +def fileset_create_toml_view(): + return generic_entity_toml_edit(None, 'fileset', None, 'entity_create_toml.html') + +@app.route('/fileset/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def fileset_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'fileset', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/fileset/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def fileset_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'fileset', ident, 'entity_edit_toml.html') + +@app.route('/webcapture/create/toml', methods=['GET', 'POST']) +@login_required +def webcapture_create_toml_view(): + return generic_entity_toml_edit(None, 'webcapture', None, 'entity_create_toml.html') + +@app.route('/webcapture/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def webcapture_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'webcapture', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/webcapture/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def webcapture_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'webcapture', ident, 'entity_edit_toml.html') + +@app.route('/release/create/toml', methods=['GET', 'POST']) +@login_required +def release_create_toml_view(): + return generic_entity_toml_edit(None, 'release', None, 'entity_create_toml.html') + +@app.route('/release/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def release_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'release', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/release/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def release_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'release', ident, 'entity_edit_toml.html') + +@app.route('/work/create/toml', methods=['GET', 'POST']) +@login_required +def work_create_toml_view(): + return generic_entity_toml_edit(None, 'work', None, 'entity_create_toml.html') + +@app.route('/work/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def work_edit_toml_view(ident): + return generic_entity_toml_edit(None, 'work', ident, 'entity_edit_toml.html') + +@app.route('/editgroup/<editgroup_id>/work/<ident>/edit/toml', methods=['GET', 'POST']) +@login_required +def work_editgroup_edit_toml(editgroup_id, ident): + return generic_entity_toml_edit(editgroup_id, 'work', ident, 'entity_edit_toml.html') + +### TOML-Only Editing Redirects ################################################ @app.route('/creator/create', methods=['GET']) +@login_required def creator_create_view(): - return abort(404) + return redirect('/creator/create/toml') @app.route('/creator/<ident>/edit', methods=['GET']) -def creator_edit(ident): - return render_template('entity_edit.html'), 404 +@login_required +def creator_edit_view(ident): + return redirect(f'/creator/{ident}/edit/toml') @app.route('/editgroup/<editgroup_id>/creator/<ident>/edit', methods=['GET', 'POST']) +@login_required def creator_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup/<editgroup_id>/creator/edit/<edit_id>/delete', methods=['POST']) -def creator_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/creator/{ident}/edit/toml') @app.route('/fileset/create', methods=['GET']) +@login_required def fileset_create_view(): - return abort(404) + return redirect('/fileset/create/toml') @app.route('/fileset/<ident>/edit', methods=['GET']) -def fileset_edit(ident): - return render_template('entity_edit.html'), 404 +@login_required +def fileset_edit_view(ident): + return redirect(f'/fileset/{ident}/edit/toml') @app.route('/editgroup/<editgroup_id>/fileset/<ident>/edit', methods=['GET', 'POST']) +@login_required def fileset_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup/<editgroup_id>/fileset/edit/<edit_id>/delete', methods=['POST']) -def fileset_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/fileset/{ident}/edit/toml') @app.route('/webcapture/create', methods=['GET']) +@login_required def webcapture_create_view(): - return abort(404) + return redirect('/webcapture/create/toml') @app.route('/webcapture/<ident>/edit', methods=['GET']) -def webcapture_edit(ident): - return render_template('entity_edit.html'), 404 +@login_required +def webcapture_edit_view(ident): + return redirect(f'/webcapture/{ident}/edit/toml') @app.route('/editgroup/<editgroup_id>/webcapture/<ident>/edit', methods=['GET', 'POST']) +@login_required def webcapture_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup/<editgroup_id>/webcapture/edit/<edit_id>/delete', methods=['POST']) -def webcapture_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/webcapture/{ident}/edit/toml') @app.route('/work/create', methods=['GET']) +@login_required def work_create_view(): - return abort(404) + return redirect('/work/create/toml') @app.route('/work/<ident>/edit', methods=['GET']) -def work_edit(ident): - return render_template('entity_edit.html'), 404 +@login_required +def work_edit_view(ident): + return redirect(f'/work/{ident}/edit/toml') @app.route('/editgroup/<editgroup_id>/work/<ident>/edit', methods=['GET', 'POST']) +@login_required def work_editgroup_edit(editgroup_id, ident): - return abort(404) - -@app.route('/editgroup/<editgroup_id>/work/edit/<edit_id>/delete', methods=['POST']) -def work_edit_delete(editgroup_id, edit_id): - return abort(404) + return redirect(f'/editgroup/{editgroup_id}/work/{ident}/edit/toml') diff --git a/python/fatcat_web/entity_helpers.py b/python/fatcat_web/entity_helpers.py index c9a57290..7156b9be 100644 --- a/python/fatcat_web/entity_helpers.py +++ b/python/fatcat_web/entity_helpers.py @@ -1,5 +1,6 @@ from flask import abort +from fatcat_openapi_client import * from fatcat_openapi_client.rest import ApiException, ApiValueError from fatcat_tools.transforms import * from fatcat_web import api @@ -148,6 +149,26 @@ def generic_get_entity_revision(entity_type, revision_id): except ApiValueError: abort(400) +def generic_deleted_entity(entity_type, ident): + if entity_type == 'container': + entity = ContainerEntity() + elif entity_type == 'creator': + entity = CreatorEntity() + elif entity_type == 'file': + entity = FileEntity() + elif entity_type == 'fileset': + entity = FilesetEntity() + elif entity_type == 'webcapture': + entity = WebcaptureEntity() + elif entity_type == 'release': + entity = ReleaseEntity() + elif entity_type == 'work': + entity = WorkEntity() + else: + raise NotImplementedError + entity.ident = ident + return entity + def generic_get_editgroup_entity(editgroup, entity_type, ident): if entity_type == 'container': edits = editgroup.edits.containers @@ -166,14 +187,18 @@ def generic_get_editgroup_entity(editgroup, entity_type, ident): else: raise NotImplementedError revision_id = None + edit = None for e in edits: if e.ident == ident: revision_id = e.revision edit = e break - if not revision_id: + if not edit: # couldn't find relevant edit in this editgroup abort(404) + if not revision_id: + # deletion, presumably + return generic_deleted_entity(entity_type, ident), edit try: entity = generic_get_entity_revision(entity_type, revision_id) diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 15585bf6..ff885fcb 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -4,10 +4,14 @@ Note: in thoery could use, eg, https://github.com/christabor/swagger_wtforms, but can't find one that is actually maintained. """ +import datetime + +import toml from flask_wtf import FlaskForm from wtforms import SelectField, DateField, StringField, IntegerField, \ - HiddenField, FormField, FieldList, validators + HiddenField, FormField, FieldList, validators, ValidationError, TextAreaField +from fatcat_tools import entity_to_toml from fatcat_openapi_client import ContainerEntity, FileEntity, \ ReleaseEntity, ReleaseContrib, FileUrl, ReleaseExtIds @@ -29,6 +33,16 @@ release_stage_options = [ ('published', 'Published'), ('updated', 'Updated'), ] +withdrawn_status_options = [ + ('', 'Not Withdrawn (blank)'), + ('retracted', 'Retracted'), + ('withdrawn', 'Withdrawn'), + ('concern', 'Concern Noted'), + ('spam', 'Spam'), + ('legal', 'Legal Taketown'), + ('safety', 'Public Safety'), + ('national-security', 'National Security'), +] role_type_options = [ ('author', 'Author'), ('editor', 'Editor'), @@ -62,11 +76,25 @@ class ReleaseContribForm(FlaskForm): default='author') RELEASE_SIMPLE_ATTRS = ['title', 'original_title', 'work_id', 'container_id', - 'release_type', 'release_stage', 'release_date', 'volume', 'issue', - 'pages', 'publisher', 'language', 'license_slug'] + 'release_type', 'release_stage', 'withdrawn_status', 'release_date', + 'release_year', 'volume', 'issue', 'pages', 'publisher', 'language', + 'license_slug'] RELEASE_EXTID_ATTRS = ['doi', 'wikidata_qid', 'isbn13', 'pmid', 'pmcid'] +def valid_year(form, field): + if field.data > datetime.date.today().year + 5: + raise ValidationError( + f"Year is too far in the future: {field.data}") + if field.data < 10: + raise ValidationError( + f"Year is too far in the past: {field.data}") + +def valid_2char_ascii(form, field): + if len(field.data) != 2 or len(field.data.encode('utf-8')) != 2 or not field.data.isalpha() or field.data != field.data.lower(): + raise ValidationError( + f"Must be 2-character ISO format, lower case: {field.data}") + class ReleaseEntityForm(EntityEditForm): """ TODO: @@ -75,7 +103,7 @@ class ReleaseEntityForm(EntityEditForm): """ title = StringField('Title', [validators.DataRequired()]) - original_title = StringField('Original Title') + original_title = StringField('Title in Original Language (if different)') work_id = StringField('Work FCID', [validators.Optional(True), validators.Length(min=26, max=26)]) @@ -87,9 +115,14 @@ class ReleaseEntityForm(EntityEditForm): choices=release_type_options, default='') release_stage = SelectField(choices=release_stage_options) + withdrawn_status = SelectField("Withdrawn Status", + [validators.Optional(True)], + choices=withdrawn_status_options, + default='') release_date = DateField('Release Date', [validators.Optional(True)]) - #release_year + release_year = IntegerField('Release Year', + [validators.Optional(True), valid_year]) doi = StringField('DOI', [validators.Regexp(r'^10\..*\/.*', message="DOI must be valid"), validators.Optional(True)]) @@ -104,7 +137,8 @@ class ReleaseEntityForm(EntityEditForm): issue = StringField('Issue') pages = StringField('Pages') publisher = StringField('Publisher (optional)') - language = StringField('Language (code)') + language = StringField('Language (code)', + [validators.Optional(True), valid_2char_ascii]) license_slug = StringField('License (slug)') contribs = FieldList(FormField(ReleaseContribForm)) #refs @@ -150,11 +184,13 @@ class ReleaseEntityForm(EntityEditForm): a = None setattr(re, simple_attr, a) for extid_attr in RELEASE_EXTID_ATTRS: - a = getattr(self, simple_attr).data + a = getattr(self, extid_attr).data # special case blank strings if a == '': a = None - setattr(re.ext_ids, simple_attr, a) + setattr(re.ext_ids, extid_attr, a) + if self.release_date.data: + re.release_year = self.release_date.data.year # bunch of complexity here to preserve old contrib metadata (eg, # affiliation and extra) not included in current forms # TODO: this may be broken; either way needs tests @@ -204,8 +240,9 @@ class ContainerEntityForm(EntityEditForm): issnl = StringField("ISSN-L (linking)") issne = StringField("ISSN (electronic)") issnp = StringField("ISSN (print)") - original_name = StringField("Original Name (native language)") - country = StringField("Country of Publication (ISO code)") + original_name = StringField("Name in Original Language (if different)") + country = StringField("Country of Publication (ISO code)", + [validators.Optional(True), valid_2char_ascii]) wikidata_qid = StringField('Wikidata QID') urls = FieldList( StringField("Container URLs", @@ -413,3 +450,31 @@ class SavePaperNowForm(FlaskForm): ingest_request['link_source'] = 'arxiv' ingest_request['link_source_id'] = release.ext_ids.arxiv return ingest_request + +def valid_toml(form, field): + try: + toml.loads(field.data) + except toml.TomlDecodeError as tpe: + raise ValidationError(tpe) + +class EntityTomlForm(EntityEditForm): + + toml = TextAreaField( + "TOML", + [validators.DataRequired(), + valid_toml, + ], + ) + + @staticmethod + def from_entity(entity): + """ + Initializes form with TOML version of existing entity + """ + etf = EntityTomlForm() + if entity.state == 'active': + pop_fields = ['ident', 'state', 'revision', 'redirect'] + else: + pop_fields = ['ident', 'state'] + etf.toml.data = entity_to_toml(entity, pop_fields=pop_fields) + return etf diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py index 74805c69..da2bb6cf 100644 --- a/python/fatcat_web/routes.py +++ b/python/fatcat_web/routes.py @@ -227,7 +227,7 @@ def generic_editgroup_entity_view(editgroup_id, entity_type, ident, view_templat entity, edit = generic_get_editgroup_entity(editgroup, entity_type, ident) - if entity.state == "deleted": + if entity.revision is None or entity.state == "deleted": return render_template('deleted_entity.html', entity=entity, entity_type=entity_type, editgroup=editgroup) diff --git a/python/fatcat_web/templates/base.html b/python/fatcat_web/templates/base.html index 18c66077..2c18ec44 100644 --- a/python/fatcat_web/templates/base.html +++ b/python/fatcat_web/templates/base.html @@ -25,6 +25,10 @@ @media only screen and (max-width: 479px) { .mobile-hide{ display: none !important; } } + + .field textarea#toml { + font-family: monospace; + } </style> {% block extra_head %}{% endblock %} </head> diff --git a/python/fatcat_web/templates/container_create.html b/python/fatcat_web/templates/container_create.html index 5786d05d..be8c5671 100644 --- a/python/fatcat_web/templates/container_create.html +++ b/python/fatcat_web/templates/container_create.html @@ -9,13 +9,15 @@ a journal (eg, "New England Journal of Medicine"), conference proceedings, a book series, or a blog. Not all publications are in a container. <form class="ui form" id="create_container_form" method="POST" action="/container/create"> + <p>Experienced users can also use the <a href="/container/create/toml">TOML + creation form</a> to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %} <br><br> <input class="ui primary submit button" type="submit" value="Create Container!"> <p> - <i>New entity will be part of the current editgroup, which needs to be + <i>New container entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog.</i> </form> diff --git a/python/fatcat_web/templates/container_edit.html b/python/fatcat_web/templates/container_edit.html index 5188ce0d..99f77d53 100644 --- a/python/fatcat_web/templates/container_edit.html +++ b/python/fatcat_web/templates/container_edit.html @@ -7,6 +7,14 @@ <h1 class="ui header">Edit Container Entity</h1> <form class="ui form" id="edit_container_form" method="POST" action="{% if editgroup %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/container/{{ existing_ident }}/edit"> + + <p>Experienced users can also use the <a href="{% if editgroup + %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/container/{{ + existing_ident }}/edit/toml">TOML editing form</a> to access all metadata + fields in a raw format. + {% if not editgroup %} + You can also <a href="/container/{{ existing_ident }}/delete">delete this entity</a>. + {% endif %} {% endblock %} <p>See <a href="https://guide.fatcat.wiki/entity_container.html">the catalog diff --git a/python/fatcat_web/templates/deleted_entity.html b/python/fatcat_web/templates/deleted_entity.html index eefc87cf..4c6b14b6 100644 --- a/python/fatcat_web/templates/deleted_entity.html +++ b/python/fatcat_web/templates/deleted_entity.html @@ -30,7 +30,7 @@ <b>Entity Type:</b> <code>{{ entity_type }}</code> </div> -{{ entity_macros.fatcat_bits(entity, entity_type, "") }} +{{ entity_macros.fatcat_bits(entity, entity_type, "", editgroup=editgroup) }} </div> </div> diff --git a/python/fatcat_web/templates/edit_macros.html b/python/fatcat_web/templates/edit_macros.html index a207e51e..d4839373 100644 --- a/python/fatcat_web/templates/edit_macros.html +++ b/python/fatcat_web/templates/edit_macros.html @@ -17,6 +17,23 @@ </div> {%- endmacro %} +{% macro form_toml_field(field, div_classes="") -%} +<h3 class="ui dividing header">Raw Metadata (TOML)</h3> +<div class="field {{ div_classes }} {% if field.errors %}error{% endif %}"> + <p><a href="https://toml.io/en/">TOML</a> is a markup language, similar to + YAML or Wikitext. The schema here is the same as the Fatcat API JSON schema, but + with a syntax that is easier to read and edit by hand. All nested metadata + fields are available here; refer to the fatcat guide for metadata + documentation and style guide, or TOML documentation for syntax notes (eg, + what those double brackets mean, and how to represent lists of authors or + references). + <br> + <br> + {{ field(rows=24) }} + {{ form_field_errors(field) }} +</div> +{%- endmacro %} + {% macro form_field_inline(field, div_classes="") -%} <div class="ui grid"> <div class="three wide column middle aligned right aligned" {# style="padding-right: 0.5rem;" #}> @@ -38,7 +55,7 @@ {% macro editgroup_dropdown(form, editgroup=None, potential_editgroups=None) -%} {% if editgroup %} <p>You are updating an existing un-merged editgroup: <a href="/editgroup/{{ editgroup.editgroup_id}}">{{ editgroup.editgroup_id }}</a>. - <p><b>Description:</b> {{ editgroup.description }} + <p><b>Description:</b> {{ editgroup.description or "" }} {% else %} {% if not potential_editgroups %} <p>You have no un-submitted editgroups in progress; a new one will be diff --git a/python/fatcat_web/templates/editgroup_view.html b/python/fatcat_web/templates/editgroup_view.html index e8146d19..a36dc3e5 100644 --- a/python/fatcat_web/templates/editgroup_view.html +++ b/python/fatcat_web/templates/editgroup_view.html @@ -25,7 +25,7 @@ updated {% endif %} <a href="/editgroup/{{ editgroup.editgroup_id }}/{{ entity_type }}/{{ edit.ident }}">[view edit]</a> - {% if auth_to.edit and not editgroup.changelog_index and not editgroup.submitted and entity_type in ('release', 'file', 'container') %} + {% if auth_to.edit and not editgroup.changelog_index and not editgroup.submitted %} <a href="/editgroup/{{ editgroup.editgroup_id }}/{{ entity_type }}/{{ edit.ident }}/edit" style="color: green;">[re-edit]</a> <form id="submit_edit_delete" method="POST" action="/editgroup/{{ editgroup.editgroup_id }}/{{ entity_type }}/edit/{{ edit.edit_id }}/delete" style="display:inline;"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> diff --git a/python/fatcat_web/templates/entity_create_toml.html b/python/fatcat_web/templates/entity_create_toml.html new file mode 100644 index 00000000..ec5bc4a2 --- /dev/null +++ b/python/fatcat_web/templates/entity_create_toml.html @@ -0,0 +1,20 @@ +{% extends "entity_edit_toml.html" %} + +{% block edit_form_prefix %} +<div class="ui segment"> +<h1 class="ui header">Create New {{ entity_type }} Entity (TOML mode)</h1> + +<form class="ui form" id="create_entity_form" method="POST" action="/{{ entity_type }}/create/toml"> +{% endblock %} + +{% block edit_form_suffix %} + <br><br> + <input class="ui primary submit button" type="submit" value="Create {{ entity_type }}!"> + <p> + <i>New {{ entity_type }} entity will be part of the current editgroup, which + needs to be submited and approved before the entity will formally be included + in the catalog.</i> +</form> +</div> +{% endblock %} + diff --git a/python/fatcat_web/templates/entity_delete.html b/python/fatcat_web/templates/entity_delete.html new file mode 100644 index 00000000..b2e13af4 --- /dev/null +++ b/python/fatcat_web/templates/entity_delete.html @@ -0,0 +1,49 @@ +{% import "edit_macros.html" as edit_macros %} +{% extends "base.html" %} + +{% block body %} +{% block edit_form_prefix %} + <div class="ui segment"> + <h1 class="ui header">Delete Entity</h1> + + <form class="ui form" id="delete_form" method="POST" action="{% if editgroup and editgroup.editgroup_id %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/{{ entity_type }}/{{ existing_ident }}/delete"> +{% endblock %} + + <p>See <a href="https://guide.fatcat.wiki/entity_release.html">the catalog + style guide</a> for schema notes, and <a + href="https://guide.fatcat.wiki/editing_quickstart.html">the editing + tutorial</a> if this is your first time making an edit. + + {{ form.hidden_tag() }} + + <h3 class="ui dividing header">Editgroup Metadata</h3> + {{ edit_macros.editgroup_dropdown(form, editgroup, potential_editgroups) }} + + <h3 class="ui dividing header">Submit</h3> + {{ edit_macros.form_field_basic(form.edit_description) }} + This description will be attached to the individual edit, not to the + editgroup as a whole. + +{% block edit_form_suffix %} + <br><br> + <input class="ui primary submit button" type="submit" value="Update Release!"> + <p> + <i>Deletion will be part of the current editgroup, which needs to be submited and + approved before the change is included in the catalog.</i> +</form> +</div> +{% endblock %} +{% endblock %} + +{% block postscript %} +<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script> +<script> +<!-- Form code --> +$(document).ready(function() { + + // these javascript dropdowns hide the original <input>, which breaks browser + // form focusing (eg, for required fields) :( + $('.ui.dropdown') .dropdown(); +}); +</script> +{% endblock %} diff --git a/python/fatcat_web/templates/entity_edit.html b/python/fatcat_web/templates/entity_edit.html deleted file mode 100644 index 97f7bf46..00000000 --- a/python/fatcat_web/templates/entity_edit.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% block body %} - -<h1>Not Implemented</h1> - -<p>Entity editing via the web interface isn't implemented yet. Sorry! - -{% endblock %} diff --git a/python/fatcat_web/templates/entity_edit_toml.html b/python/fatcat_web/templates/entity_edit_toml.html new file mode 100644 index 00000000..64768d6e --- /dev/null +++ b/python/fatcat_web/templates/entity_edit_toml.html @@ -0,0 +1,55 @@ +{% import "edit_macros.html" as edit_macros %} +{% extends "base.html" %} + +{% block body %} +{% block edit_form_prefix %} + <div class="ui segment"> + <h1 class="ui header">Edit Entity (TOML mode)</h1> + + <form class="ui form" id="edit_toml_form" method="POST" action="{% if editgroup and editgroup.editgroup_id %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/{{ entity_type }}/{{ existing_ident }}/edit/toml"> + + {% if not editgroup %} + <p>You can also <a href="/{{ entity_type }}/{{ existing_ident }}/delete">delete this entity</a>. + {% endif %} +{% endblock %} + + <p>See <a href="https://guide.fatcat.wiki/entity_release.html">the catalog + style guide</a> for schema notes, and <a + href="https://guide.fatcat.wiki/editing_quickstart.html">the editing + tutorial</a> if this is your first time making an edit. + + {{ form.hidden_tag() }} + + <h3 class="ui dividing header">Editgroup Metadata</h3> + {{ edit_macros.editgroup_dropdown(form, editgroup, potential_editgroups) }} + + {{ edit_macros.form_toml_field(form.toml, "required") }} + + <h3 class="ui dividing header">Submit</h3> + {{ edit_macros.form_field_basic(form.edit_description) }} + This description will be attached to the individual edit, not to the + editgroup as a whole. + +{% block edit_form_suffix %} + <br><br> + <input class="ui primary submit button" type="submit" value="Update Release!"> + <p> + <i>Edit will be part of the current editgroup, which needs to be submited and + approved before the change is included in the catalog.</i> +</form> +</div> +{% endblock %} +{% endblock %} + +{% block postscript %} +<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script> +<script> +<!-- Form code --> +$(document).ready(function() { + + // these javascript dropdowns hide the original <input>, which breaks browser + // form focusing (eg, for required fields) :( + $('.ui.dropdown') .dropdown(); +}); +</script> +{% endblock %} diff --git a/python/fatcat_web/templates/entity_macros.html b/python/fatcat_web/templates/entity_macros.html index 718c071c..0ce646bf 100644 --- a/python/fatcat_web/templates/entity_macros.html +++ b/python/fatcat_web/templates/entity_macros.html @@ -33,7 +33,7 @@ {% if entity.state %} State is "{{ entity.state }}". {% endif %} - {% if entity.state != "deleted" %} + {% if entity.revision %} Revision: <br><small><code><a href="/{{ entity_type }}/rev/{{ entity.revision }}">{{ entity.revision }}</a></code></small> {% endif %} @@ -43,11 +43,13 @@ {%- else -%} https://api.{{ config.FATCAT_DOMAIN }} {%- endif -%} - /v0/{{ entity_type }} - {%- if entity.ident -%} - /{{ entity.ident }} + /v0 + {%- if editgroup and entity.ident -%} + /editgroup/{{ editgroup.editgroup_id }}{# /{{ entity_type }}/{{ entity.ident }} #} + {%- elif entity.ident -%} + /{{ entity_type }}/{{ entity.ident }} {%- elif entity.revision -%} - /rev/{{ entity.revision }} + /{{ entity_type }}/rev/{{ entity.revision }} {% endif %} {% if expand %}?expand={{ expand}}{% endif %}"> As JSON object via API diff --git a/python/fatcat_web/templates/file_create.html b/python/fatcat_web/templates/file_create.html index a7c99b96..affcfb6e 100644 --- a/python/fatcat_web/templates/file_create.html +++ b/python/fatcat_web/templates/file_create.html @@ -5,13 +5,15 @@ <h1 class="ui header">Create New File Entity</h1> <form class="ui form" id="create_file_form" method="POST" action="/file/create"> + <p>Experienced users can also use the <a href="/file/create/toml">TOML + creation form</a> to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %} <br><br> <input class="ui primary submit button" type="submit" value="Create File!"> <p> - <i>New entity will be part of the current editgroup, which needs to be + <i>New file entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog.</i> </form> diff --git a/python/fatcat_web/templates/file_edit.html b/python/fatcat_web/templates/file_edit.html index e8a421b3..745b0c41 100644 --- a/python/fatcat_web/templates/file_edit.html +++ b/python/fatcat_web/templates/file_edit.html @@ -7,6 +7,14 @@ <h1 class="ui header">Edit File Entity</h1> <form class="ui form" id="edit_file_form" method="POST" action="{% if editgroup %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/file/{{ existing_ident }}/edit"> + + <p>Experienced users can also use the <a href="{% if editgroup + %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/file/{{ + existing_ident }}/edit/toml">TOML editing form</a> to access all metadata + fields in a raw format. + {% if not editgroup %} + You can also <a href="/file/{{ existing_ident }}/delete">delete this entity</a>. + {% endif %} {% endblock %} <p>See <a href="https://guide.fatcat.wiki/entity_file.html">the catalog diff --git a/python/fatcat_web/templates/home.html b/python/fatcat_web/templates/home.html index 4557e212..de32d6a4 100644 --- a/python/fatcat_web/templates/home.html +++ b/python/fatcat_web/templates/home.html @@ -171,11 +171,10 @@ <tr><td><b>Creator</b> <br>authors, editors, translators + <td><a href="/creator/create">Create</a> {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - <td> <td><a href="/creator/iimvc523xbhqlav6j3sbthuehu">Author</a> {% else %} - <td><!-- <a href="/creator/create">Create</a> --> <td><a href="/creator/iimvc523xbhqlav6j3sbthuehu">Author</a> (prod) <br><a href="/creator/aaaaaaaaaaaaaircaaaaaaaaai">Dummy</a> <br><a href="/creator/aaaaaaaaaaaaaircaaaaaaaaam">Realistic</a> @@ -206,11 +205,10 @@ </form> <tr><td><b>File Set</b> <br>datasets, suplementary materials + <td><a href="/fileset/create">Create</a> {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - <td> <td><a href="/fileset/ho376wmdanckpp66iwfs7g22ne">Dataset</a> {% else %} - <td> <td><a href="/fileset/ho376wmdanckpp66iwfs7g22ne">Dataset</a> (prod) <br><a href="/fileset/aaaaaaaaaaaaaztgaaaaaaaaai">Dummy</a> <br><a href="/fileset/aaaaaaaaaaaaaztgaaaaaaaaam">Realistic</a> @@ -218,12 +216,11 @@ <td> <tr><td><b>Web Capture</b> <br>HTML and interactive articles, blog posts + <td><a href="/webcapture/create">Create</a> {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - <td> <td><a href="/webcapture/z7uaeatyvfgwdpuxtrdu4okqii">D-Lib</a> <br><a href="/webcapture/5l2pubtfefbmdnqws2izccqlpm">Blog Post</a> {% else %} - <td> <td><a href="/webcapture/z7uaeatyvfgwdpuxtrdu4okqii">D-Lib</a> (prod) <br><a href="/webcapture/aaaaaaaaaaaaa53xaaaaaaaaai">Dummy</a> <br><a href="/webcapture/aaaaaaaaaaaaa53xaaaaaaaaam">Realistic</a> @@ -231,11 +228,10 @@ <td> <tr><td><b>Work</b> <br>for grouping Releases + <td><a href="/work/create">Create</a> {% if config.FATCAT_DOMAIN == 'fatcat.wiki' %} - <td> <td><a href="/work/ftl6xv267vb6xfech3khri3nwa">Paper</a> {% else %} - <td> <td><a href="/work/ftl6xv267vb6xfech3khri3nwa">Paper</a> (prod) <br><a href="/work/aaaaaaaaaaaaavkvaaaaaaaaai">Dummy</a> <br><a href="/work/aaaaaaaaaaaaavkvaaaaaaaaam">Realistic</a> diff --git a/python/fatcat_web/templates/release_create.html b/python/fatcat_web/templates/release_create.html index 5ec2efe5..4f5dabd7 100644 --- a/python/fatcat_web/templates/release_create.html +++ b/python/fatcat_web/templates/release_create.html @@ -5,13 +5,15 @@ <h1 class="ui header">Create New Release Entity</h1> <form class="ui form" id="create_release_form" method="POST" action="/release/create"> + <p>Experienced users can also use the <a href="/release/create/toml">TOML + creation form</a> to access all metadata fields in a raw format. {% endblock %} {% block edit_form_suffix %} <br><br> <input class="ui primary submit button" type="submit" value="Create Release!"> <p> - <i>New entity will be part of the current editgroup, which needs to be + <i>New release entity will be part of the current editgroup, which needs to be submited and approved before the entity will formally be included in the catalog.</i> </form> diff --git a/python/fatcat_web/templates/release_edit.html b/python/fatcat_web/templates/release_edit.html index a4a7e56f..c26c9850 100644 --- a/python/fatcat_web/templates/release_edit.html +++ b/python/fatcat_web/templates/release_edit.html @@ -7,6 +7,14 @@ <h1 class="ui header">Edit Release Entity</h1> <form class="ui form" id="edit_release_form" method="POST" action="{% if editgroup %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/release/{{ existing_ident }}/edit"> + + <p>Experienced users can also use the <a href="{% if editgroup + %}/editgroup/{{ editgroup.editgroup_id }}{% endif %}/release/{{ + existing_ident }}/edit/toml">TOML editing form</a> to access all metadata + fields in a raw format. + {% if not editgroup %} + You can also <a href="/release/{{ existing_ident }}/delete">delete this entity</a>. + {% endif %} {% endblock %} <p>See <a href="https://guide.fatcat.wiki/entity_release.html">the catalog @@ -14,6 +22,7 @@ href="https://guide.fatcat.wiki/editing_quickstart.html">the editing tutorial</a> if this is your first time making an edit. + {{ form.hidden_tag() }} <h3 class="ui dividing header">Editgroup Metadata</h3> @@ -21,21 +30,22 @@ <br> <h3 class="ui dividing header">The Basics</h3> + + {{ edit_macros.form_field_inline(form.release_type, "required") }} + {{ edit_macros.form_field_inline(form.title, "required") }} + {{ edit_macros.form_field_inline(form.original_title) }} + <div class="ui grid"> <div class="three wide column" style="padding-bottom: 0px;"></div> <div class="twelve wide column" style="padding-bottom: 0px;"> <div class="ui equal width fields"> - {{ edit_macros.form_field_basic(form.release_type, "required") }} - {{ edit_macros.form_field_basic(form.release_stage) }} + {{ edit_macros.form_field_basic(form.release_year) }} + {{ edit_macros.form_field_basic(form.release_date) }} </div> </div> <div class="one wide column" style="padding-bottom: 0px;"></div> </div> - {{ edit_macros.form_field_inline(form.title, "required") }} - {{ edit_macros.form_field_inline(form.original_title) }} - {{ edit_macros.form_field_inline(form.work_id) }} - {{ edit_macros.form_field_inline(form.release_date) }} <div class="ui grid"> <div class="three wide column" style="padding-bottom: 0px;"></div> <div class="twelve wide column" style="padding-bottom: 0px;"> @@ -47,6 +57,19 @@ <div class="one wide column" style="padding-bottom: 0px;"></div> </div> + <div class="ui grid"> + <div class="three wide column" style="padding-bottom: 0px;"></div> + <div class="twelve wide column" style="padding-bottom: 0px;"> + <div class="ui equal width fields"> + {{ edit_macros.form_field_basic(form.release_stage) }} + {{ edit_macros.form_field_basic(form.withdrawn_status) }} + </div> + </div> + <div class="one wide column" style="padding-bottom: 0px;"></div> + </div> + + {{ edit_macros.form_field_inline(form.work_id) }} + <br> <h3 class="ui dividing header">Contributors</h3> <div class="list-group" id="contrib_list" name="contrib_list"> |