From 4987611cf602751a8248662511438acb2d1c45b7 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Tue, 2 Apr 2019 16:04:37 -0700 Subject: basic container create/edit forms --- python/fatcat_web/editing_routes.py | 106 ++++++++++---- python/fatcat_web/forms.py | 126 ++++++++++++++++ python/fatcat_web/templates/container_create.html | 167 ++-------------------- python/fatcat_web/templates/container_edit.html | 65 +++++++++ 4 files changed, 285 insertions(+), 179 deletions(-) create mode 100644 python/fatcat_web/templates/container_edit.html diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py index dda0bc6f..4d06f6cd 100644 --- a/python/fatcat_web/editing_routes.py +++ b/python/fatcat_web/editing_routes.py @@ -17,33 +17,89 @@ from fatcat_web.forms import * ### Views ################################################################### -@app.route('/container//edit', methods=['GET']) -def container_edit_view(ident): +# XXX: figure out CSRF stuff for local dev +@app.route('/container/create', methods=['GET', 'POST']) +@login_required +@app.csrf.exempt +def container_create(): + form = ContainerEntityForm(csrf_enabled=False) # XXX: + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if form.editgroup_id.data: + # TODO: error handling + eg = user_api.get_editgroup(form.editgroup_id.data) + else: + # if no editgroup, create one from description + eg = user_api.create_editgroup( + Editgroup(description=form.editgroup_description.data or None)) + # set this session editgroup_id + session['active_editgroup_id'] = eg.editgroup_id + print(eg.editgroup_id) # XXX: debug + flash('Started new editgroup {}' \ + .format(eg.editgroup_id, eg.editgroup_id)) + # no merge or anything hard to do; just create the entity + entity = form.to_entity() + edit = user_api.create_container(entity, editgroup_id=eg.editgroup_id) + # redirect to new entity + return redirect('/container/{}'.format(edit.ident)) + elif form.errors: + print("user form errors: {}".format(form.errors)) + print("didn't validate...") + if not form.is_submitted(): + editgroup_id = session.get('active_editgroup_id', None) + form.editgroup_id.data = editgroup_id + return render_template('container_create.html', + form=form, editgroup_id=editgroup_id) + +# XXX: figure out CSRF stuff for local dev +@login_required +@app.csrf.exempt +@app.route('/container//edit', methods=['GET', 'POST']) +def container_edit(ident): + # TODO: prev_rev interlock + # TODO: factor out editgroup active/creation stuff try: entity = api.get_container(ident) except ApiException as ae: abort(ae.status) - return render_template('entity_edit.html') - -@app.route('/container/create', methods=['GET']) -@login_required -def container_create_view(): - return render_template('container_create.html') - -@app.route('/container/create', methods=['POST']) -@login_required -def container_create(): - raise NotImplementedError - params = dict() - for k in request.form: - if k.startswith('container_'): - params[k[10:]] = request.form[k] - container = None - #edit = api.create_container(container, params=params) - #return redirect("/container/{}".format(edit.ident)) + form = ContainerEntityForm(csrf_enabled=False) # XXX: + if form.is_submitted(): + if form.validate_on_submit(): + # API on behalf of user + user_api = auth_api(session['api_token']) + if form.editgroup_id.data: + # TODO: error handling + eg = user_api.get_editgroup(form.editgroup_id.data) + else: + # if no editgroup, create one from description + eg = user_api.create_editgroup( + Editgroup(description=form.editgroup_description.data or None)) + # set this session editgroup_id + session['active_editgroup_id'] = eg.editgroup_id + print(eg.editgroup_id) # XXX: debug + flash('Started new editgroup {}' \ + .format(eg.editgroup_id, eg.editgroup_id)) + # all the tricky logic is in the update method + form.update_entity(entity) + edit = user_api.update_container(entity.ident, entity, + editgroup_id=eg.editgroup_id) + # redirect to entity revision + # TODO: container_rev_view + return redirect('/container/{}'.format(edit.ident)) + elif form.errors: + print("user form errors (didn't validate): {}".format(form.errors)) + else: + form = ContainerEntityForm.from_entity(entity) + if not form.is_submitted(): + editgroup_id = session.get('active_editgroup_id', None) + form.editgroup_id.data = editgroup_id + return render_template('container_edit.html', + form=form, editgroup_id=editgroup_id, entity=entity) @app.route('/creator//edit', methods=['GET']) -def creator_edit_view(ident): +def creator_edit(ident): try: entity = api.get_creator(ident) except ApiException as ae: @@ -51,7 +107,7 @@ def creator_edit_view(ident): return render_template('entity_edit.html') @app.route('/file//edit', methods=['GET']) -def file_edit_view(ident): +def file_edit(ident): try: entity = api.get_file(ident) except ApiException as ae: @@ -59,7 +115,7 @@ def file_edit_view(ident): return render_template('entity_edit.html') @app.route('/fileset//edit', methods=['GET']) -def fileset_edit_view(ident): +def fileset_edit(ident): try: entity = api.get_fileset(ident) except ApiException as ae: @@ -67,7 +123,7 @@ def fileset_edit_view(ident): return render_template('entity_edit.html') @app.route('/webcapture//edit', methods=['GET']) -def webcapture_edit_view(ident): +def webcapture_edit(ident): try: entity = api.get_webcapture(ident) except ApiException as ae: @@ -144,7 +200,7 @@ def release_edit(ident): form.update_entity(entity) edit = user_api.update_release(entity.ident, entity, editgroup_id=eg.editgroup_id) - # redirect to release revision + # redirect to entity revision # TODO: release_rev_view return redirect('/release/{}'.format(edit.ident)) elif form.errors: diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index 632e36c6..ebe6fafe 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -161,3 +161,129 @@ class ReleaseEntityForm(EntityEditForm): if self.edit_description.data: re.edit_extra = dict(description=self.edit_description.data) +container_type_options = ( + ('journal', 'Journal'), + ('proceedings', 'Conference Proceedings'), + ('blog', 'Blog'), +) + +CONTAINER_SIMPLE_ATTRS = ['name', 'container_type', 'publisher', 'issnl', + 'wikidata_qid'] + +class ContainerEntityForm(EntityEditForm): + name = StringField('Name/Title', + [validators.InputRequired()]) + container_type = SelectField('Container Type', + [validators.Optional(True)], + choices=container_type_options, + default='') + publisher = StringField("Publisher") + issnl = StringField("ISSN-L") + wikidata_qid = StringField('Wikidata QID') + + def from_entity(re): + """ + Initializes form with values from an existing container entity. + """ + ref = ContainerEntityForm() + for simple_attr in CONTAINER_SIMPLE_ATTRS: + a = getattr(ref, simple_attr) + a.data = getattr(re, simple_attr) + return ref + + def to_entity(self): + assert(self.name.data) + entity = ContainerEntity(name=self.name.data) + self.update_entity(entity) + return entity + + def update_entity(self, ce): + """ + Mutates a container entity in place, updating fields with values from + this form. + + Form must be validated *before* calling this function. + """ + for simple_attr in CONTAINER_SIMPLE_ATTRS: + a = getattr(self, simple_attr).data + # special case blank strings + if a == '': + a = None + setattr(ce, simple_attr, a) + if self.edit_description.data: + ce.edit_extra = dict(description=self.edit_description.data) + +url_rel_options = [ + ('web', 'Public Web'), + ('webarchive', 'Web Archive'), + ('repository', 'Repository'), + ('social', 'Academic Social Network'), + ('publisher', 'Publisher'), + ('dweb', 'Decentralized Web'), +] + +FILE_SIMPLE_ATTRS = ['size', 'md5', 'sha1', 'sha256', 'mimetype'] + +class FileUrlForm(FlaskForm): + class Meta: + # this is a sub-form, so disable CSRF + csrf = False + + url = StringField('Display Name', + [validators.DataRequired()]) + rel = SelectField( + [validators.DataRequired()], + choices=url_rel_options, + default='web') + +class FileEntityForm(EntityEditForm): + size = IntegerField('Size (bytes)', + [validators.DataRequired()]) + # TODO: positive definite + md5 = StringField("MD5") + sha1 = StringField("SHA-1") + sha256 = StringField("SHA-256") + urls = FieldList(FormField(FileUrlForm)) + mimetype = StringField("Mimetype") + release_ids = FieldList(StringField("Release FCID")) + + def from_entity(re): + """ + Initializes form with values from an existing file entity. + """ + ref = FileEntityForm() + for simple_attr in FILE_SIMPLE_ATTRS: + a = getattr(ref, simple_attr) + a.data = getattr(re, simple_attr) + return ref + + def to_entity(self): + assert(self.name.data) + entity = FileEntity() + self.update_entity(entity) + return entity + + def update_entity(self, fe): + """ + Mutates in place, updating fields with values from this form. + + Form must be validated *before* calling this function. + """ + for simple_attr in FILE_SIMPLE_ATTRS: + a = getattr(self, simple_attr).data + # special case blank strings + if a == '': + a = None + setattr(fe, simple_attr, a) + re.urls = [] + for u in self.urls: + re.contribs.append(FileUrl( + rel=u.role.data or None, + url=u.url.data or None, + )) + re.release_ids = [] + for ri in self.release_ids: + re.release_ids.append(ri.data) + if self.edit_description.data: + fe.edit_extra = dict(description=self.edit_description.data) + diff --git a/python/fatcat_web/templates/container_create.html b/python/fatcat_web/templates/container_create.html index 15288142..5dde37bf 100644 --- a/python/fatcat_web/templates/container_create.html +++ b/python/fatcat_web/templates/container_create.html @@ -1,7 +1,8 @@ -{% extends "base.html" %} -{% block body %} +{% extends "container_edit.html" %} + +{% block edit_form_prefix %}
-

Adding a New Container

+

Create New Container Entity

A "container" is a anything that groups publications together. For example, a journal (eg, "New England Journal of Medicine"), conference proceedings, a @@ -9,160 +10,18 @@ book series, or a blog.

Not all publications are in a container. -

- -

The Basics

- -
- - -
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - - - -

Anything Else?

- -
Create container
- -

Entity will be created as part of the current edit group, which needs to be -submited and approved before the entity will formally be included in the -catalog. + +{% endblock %} +{% block edit_form_suffix %} +

+ +

+ New 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.

-
{% endblock %} -{% block postscript %} - -{% endblock %} diff --git a/python/fatcat_web/templates/container_edit.html b/python/fatcat_web/templates/container_edit.html new file mode 100644 index 00000000..4dbcfbd5 --- /dev/null +++ b/python/fatcat_web/templates/container_edit.html @@ -0,0 +1,65 @@ +{% import "edit_macros.html" as edit_macros %} +{% extends "base.html" %} + +{% block body %} +{% block edit_form_prefix %} +
+

Edit Container Entity

+ +
+{% endblock %} + {{ form.hidden_tag() }} + +
+
+

Editgroup Meta

+
+
+ {% if editgroup_id %} +

You have an editgroup in progress, and this edit will be included by + default. You can override this below. + {% else %} +

No existing editgroup is in progress (or at least, not is selected). + An existing ID can be pasted in, or if you leave that blank but give a + description, a new editgroup will be created for this edit. + {% endif %} + {{ edit_macros.form_field_inline(form.editgroup_id) }} + {{ edit_macros.form_field_inline(form.editgroup_description) }} +

+
+ +

The Basics

+
+ {{ edit_macros.form_field_inline(form.container_type, "required") }} + {{ edit_macros.form_field_inline(form.name, "required") }} + {{ edit_macros.form_field_inline(form.publisher) }} + {{ edit_macros.form_field_inline(form.issnl) }} + {{ edit_macros.form_field_inline(form.wikidata_qid) }} + +
+

Submit

+ {{ 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 %} +

+ +

+ Edit will be part of the current editgroup, which needs to be submited and + approved before the change is included in the catalog. +

+
+{% endblock %} +{% endblock %} + +{% block postscript %} + +{% endblock %} + -- cgit v1.2.3