from flask import render_template, abort, redirect, session, flash
from flask_login import login_required
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
from fatcat_web.search import *
from fatcat_web.forms import *
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_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
"""
if edit_form.editgroup_id.data:
try:
eg = api.get_editgroup(edit_form.editgroup_id.data)
except ApiException as ae:
if ae.status == 404:
edit_form.editgroup_id.errors.append("Editgroup does not exist")
return None
app.log.warning(ae)
raise ae
if eg.changelog_index:
edit_form.editgroup_id.errors.append("Editgroup has already been accepted")
return None
else:
# if no editgroup, create one from description
try:
eg = api.create_editgroup(
Editgroup(description=edit_form.editgroup_description.data or None))
except ApiException as ae:
app.log.warning(ae)
raise ae
# set this session editgroup_id
flash('Started new editgroup {}'.format(
eg.editgroup_id,
eg.editgroup_id,
))
return eg
def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template):
"""
existing (entity)
Create: existing blank, ident blank, editgroup optional
Update: ident set
Need to handle:
- editgroup not set (need to create one)
- creating entity from form
- updating an existing ident
- updating an existing editgroup/ident
Views:
- /container/create
- /container//edit
- /editgroup//container//edit
Helpers:
- get_editgroup_revision(editgroup, entity_type, ident) -> None or entity
TODO: prev_rev interlock
"""
# 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:
abort(400, "Editgroup already merged")
# 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
if entity_type == 'container':
form = ContainerEntityForm()
elif entity_type == 'file':
form = FileEntityForm()
elif entity_type == 'release':
form = ReleaseEntityForm()
else:
raise NotImplementedError
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
entity = form.to_entity()
try:
if entity_type == 'container':
edit = user_api.create_container(editgroup.editgroup_id, entity)
elif entity_type == 'file':
edit = user_api.create_file(editgroup.editgroup_id, entity)
elif entity_type == 'release':
edit = user_api.create_release(editgroup.editgroup_id, entity)
else:
raise NotImplementedError
except ApiException as ae:
app.log.warning(ae)
raise ae
return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident))
else: # it's an update
# all the tricky logic is in the update method
form.update_entity(existing)
# do we need to try to delete the current in-progress edit first?
# 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
try:
generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, existing_edit.edit_id)
except ApiException as ae:
if ae.status == 404:
pass
else:
raise ae
try:
if entity_type == 'container':
edit = user_api.update_container(editgroup.editgroup_id, existing.ident, existing)
elif entity_type == 'file':
edit = user_api.update_file(editgroup.editgroup_id, existing.ident, existing)
elif entity_type == 'release':
edit = user_api.update_release(editgroup.editgroup_id, existing.ident, existing)
else:
raise NotImplementedError
except ApiException as ae:
app.log.warning(ae)
raise ae
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:
if entity_type == 'container':
form = ContainerEntityForm.from_entity(existing)
elif entity_type == 'file':
form = FileEntityForm.from_entity(existing)
elif entity_type == 'release':
form = ReleaseEntityForm.from_entity(existing)
else:
raise NotImplementedError
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,
existing_ident=existing_ident, editgroup=editgroup,
potential_editgroups=potential_editgroups), status
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:
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 = 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_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
generic_entity_delete_edit(user_api, entity_type, editgroup.editgroup_id, edit_id)
return redirect("/editgroup/{}".format(editgroup_id))
### Views ###################################################################
@app.route('/container/create', methods=['GET', 'POST'])
@login_required
def container_create_view():
return generic_entity_edit(None, 'container', None, 'container_create.html')
@app.route('/container//edit', methods=['GET', 'POST'])
@login_required
def container_edit(ident):
return generic_entity_edit(None, 'container', ident, 'container_edit.html')
@app.route('/editgroup//container//edit', methods=['GET', 'POST'])
@login_required
def container_editgroup_edit(editgroup_id, ident):
return generic_entity_edit(editgroup_id, 'container', ident, 'container_edit.html')
@app.route('/editgroup//container/edit//delete', methods=['POST'])
@login_required
def container_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'container', edit_id)
@app.route('/file/create', methods=['GET', 'POST'])
@login_required
def file_create_view():
return generic_entity_edit(None, 'file', None, 'file_create.html')
@app.route('/file//edit', methods=['GET', 'POST'])
@login_required
def file_edit(ident):
return generic_entity_edit(None, 'file', ident, 'file_edit.html')
@app.route('/editgroup//file//edit', methods=['GET', 'POST'])
@login_required
def file_editgroup_edit(editgroup_id, ident):
return generic_entity_edit(editgroup_id, 'file', ident, 'file_edit.html')
@app.route('/editgroup//file/edit//delete', methods=['POST'])
@login_required
def file_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'file', edit_id)
@app.route('/release/create', methods=['GET', 'POST'])
@login_required
def release_create_view():
return generic_entity_edit(None, 'release', None, 'release_create.html')
@app.route('/release//edit', methods=['GET', 'POST'])
@login_required
def release_edit(ident):
return generic_entity_edit(None, 'release', ident, 'release_edit.html')
@app.route('/editgroup//release//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//release/edit//delete', methods=['POST'])
@login_required
def release_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'release', edit_id)
@app.route('/editgroup//creator/edit//delete', methods=['POST'])
def creator_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'creator', edit_id)
@app.route('/editgroup//fileset/edit//delete', methods=['POST'])
def fileset_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'fileset', edit_id)
@app.route('/editgroup//webcapture/edit//delete', methods=['POST'])
def webcapture_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'webcapture', edit_id)
@app.route('/editgroup//work/edit//delete', methods=['POST'])
def work_edit_delete(editgroup_id, edit_id):
return generic_edit_delete(editgroup_id, 'work', edit_id)
### 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//edit/toml', methods=['GET', 'POST'])
@login_required
def container_edit_toml(ident):
return generic_entity_toml_edit(None, 'container', ident, 'entity_edit_toml.html')
@app.route('/editgroup//container//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//edit/toml', methods=['GET', 'POST'])
@login_required
def creator_edit_toml(ident):
return generic_entity_toml_edit(None, 'creator', ident, 'entity_edit_toml.html')
@app.route('/editgroup//creator//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//edit/toml', methods=['GET', 'POST'])
@login_required
def file_edit_toml(ident):
return generic_entity_toml_edit(None, 'file', ident, 'entity_edit_toml.html')
@app.route('/editgroup//file//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//edit/toml', methods=['GET', 'POST'])
@login_required
def fileset_edit_toml(ident):
return generic_entity_toml_edit(None, 'fileset', ident, 'entity_edit_toml.html')
@app.route('/editgroup//fileset//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//edit/toml', methods=['GET', 'POST'])
@login_required
def webcapture_edit_toml(ident):
return generic_entity_toml_edit(None, 'webcapture', ident, 'entity_edit_toml.html')
@app.route('/editgroup//webcapture//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//edit/toml', methods=['GET', 'POST'])
@login_required
def release_edit_toml(ident):
return generic_entity_toml_edit(None, 'release', ident, 'entity_edit_toml.html')
@app.route('/editgroup//release//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//edit/toml', methods=['GET', 'POST'])
@login_required
def work_edit_toml(ident):
return generic_entity_toml_edit(None, 'work', ident, 'entity_edit_toml.html')
@app.route('/editgroup//work//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 redirect('/creator/create/toml')
@app.route('/creator//edit', methods=['GET'])
@login_required
def creator_edit(ident):
return redirect(f'/creator/{ident}/edit/toml')
@app.route('/editgroup//creator//edit', methods=['GET', 'POST'])
@login_required
def creator_editgroup_edit(editgroup_id, ident):
return redirect(f'/editgroup/{editgroup_id}/creator/{ident}/edit/toml')
@app.route('/fileset/create', methods=['GET'])
@login_required
def fileset_create_view():
return redirect('/fileset/create/toml')
@app.route('/fileset//edit', methods=['GET'])
@login_required
def fileset_edit(ident):
return redirect(f'/fileset/{ident}/edit/toml')
@app.route('/editgroup//fileset//edit', methods=['GET', 'POST'])
@login_required
def fileset_editgroup_edit(editgroup_id, ident):
return redirect(f'/editgroup/{editgroup_id}/fileset/{ident}/edit/toml')
@app.route('/webcapture/create', methods=['GET'])
@login_required
def webcapture_create_view():
return redirect('/webcapture/create/toml')
@app.route('/webcapture//edit', methods=['GET'])
@login_required
def webcapture_edit(ident):
return redirect(f'/webcapture/{ident}/edit/toml')
@app.route('/editgroup//webcapture//edit', methods=['GET', 'POST'])
@login_required
def webcapture_editgroup_edit(editgroup_id, ident):
return redirect(f'/editgroup/{editgroup_id}/webcapture/{ident}/edit/toml')
@app.route('/work/create', methods=['GET'])
@login_required
def work_create_view():
return redirect('/work/create/toml')
@app.route('/work//edit', methods=['GET'])
@login_required
def work_edit(ident):
return redirect(f'/work/{ident}/edit/toml')
@app.route('/editgroup//work//edit', methods=['GET', 'POST'])
@login_required
def work_editgroup_edit(editgroup_id, ident):
return redirect(f'/editgroup/{editgroup_id}/work/{ident}/edit/toml')