diff options
-rw-r--r-- | python/fatcat_web/forms.py | 75 | ||||
-rw-r--r-- | python/fatcat_web/routes.py | 44 | ||||
-rw-r--r-- | python/fatcat_web/templates/release_create.html | 85 |
3 files changed, 164 insertions, 40 deletions
diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py index f961faa0..baff62d7 100644 --- a/python/fatcat_web/forms.py +++ b/python/fatcat_web/forms.py @@ -5,9 +5,14 @@ but can't find one that is actually maintained. """ from flask_wtf import FlaskForm -from wtforms import SelectField, DateField, StringField, FormField, FieldList, validators +from wtforms import SelectField, DateField, StringField, IntegerField, \ + FormField, FieldList, validators + +from fatcat_client import ContainerEntity, CreatorEntity, FileEntity, \ + ReleaseEntity, ReleaseContrib release_type_options = [ + ('', 'Unknown'), ('article-journal', 'Journal Article'), ('paper-conference', 'Conference Proceeding'), ('article', 'Article (non-journal)'), @@ -17,6 +22,7 @@ release_type_options = [ ('stub', 'Invalid/Stub'), ] release_status_options = [ + ('', 'Unknown'), ('draft', 'Draft'), ('submitted', 'Submitted'), ('accepted', 'Accepted'), @@ -31,19 +37,31 @@ role_type_options = [ class EntityEditForm(FlaskForm): editgroup_id = StringField('Editgroup ID', - [validators.DataRequired()]) + [validators.Optional(True)]) editgroup_description = StringField('Editgroup Description', [validators.Optional(True)]) edit_description = StringField('Description of Changes', [validators.Optional(True)]) class ReleaseContribForm(FlaskForm): + class Meta: + # this is a sub-form, so disable CSRF + csrf = False #surname #given_name #creator_id (?) #orcid (for match?) - raw_name = StringField('Display Name') - role = SelectField(choices=role_type_options) + raw_name = StringField('Display Name', + [validators.DataRequired()]) + role = SelectField( + [validators.DataRequired()], + choices=role_type_options, + default='author') + +RELEASE_SIMPLE_ATTRS = ['title', 'original_title', 'work_id', 'container_id', + 'release_type', 'release_status', 'release_date', 'doi', 'wikidata_qid', + 'isbn13', 'pmid', 'pmcid', 'volume', 'issue', 'pages', 'publisher', + 'language', 'license_slug'] class ReleaseEntityForm(EntityEditForm): """ @@ -51,17 +69,22 @@ class ReleaseEntityForm(EntityEditForm): - field types: fatcat id - date """ - title = StringField('Title', [validators.InputRequired()]) + title = StringField('Title', + [validators.InputRequired()]) original_title = StringField('Original Title') work_id = StringField('Work FCID') container_id = StringField('Container FCID') - release_type = SelectField(choices=release_type_options) + release_type = SelectField('Release Type', + [validators.DataRequired()], + choices=release_type_options, + default='') release_status = SelectField(choices=release_status_options) release_date = DateField('Release Date', [validators.Optional(True)]) #release_year doi = StringField('DOI', - [validators.Regexp('^10\..*\/.*', message="DOI must be valid")]) + [validators.Regexp('^10\..*\/.*', message="DOI must be valid"), + validators.Optional(True)]) wikidata_qid = StringField('Wikidata QID') isbn13 = StringField('ISBN-13') pmid = StringField('PubMed Id') @@ -79,3 +102,41 @@ class ReleaseEntityForm(EntityEditForm): #refs #abstracts + def from_entity(re): + """ + Initializes form with values from an existing release entity. + """ + ref = ReleaseEntityForm() + for simple_attr in RELEASE_SIMPLE_ATTRS: + setattr(ref, simple_attr, getattr(re, simple_attr)) + return ref + + def to_entity(self): + assert(self.title.data) + entity = ReleaseEntity(title=self.title.data) + self.update_entity(entity) + return entity + + def update_entity(self, re): + """ + Mutates a release entity in place, updating fields with values from + this form. + + Form must be validated *before* calling this function. + """ + for simple_attr in RELEASE_SIMPLE_ATTRS: + a = getattr(self, simple_attr).data + # special case blank strings + if a == '': + a = None + setattr(re, simple_attr, a) + # TODO: don't update authors unless necessary! + re.contribs = [] + for c in self.contribs: + re.contribs.append(ReleaseContrib( + role=c.role.data or None, + raw_name=c.raw_name.data or None, + )) + if self.edit_description.data: + re.edit_extra = dict(description=self.edit_description.data) + diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py index 81c4c5c1..eb62d338 100644 --- a/python/fatcat_web/routes.py +++ b/python/fatcat_web/routes.py @@ -5,6 +5,7 @@ from flask import Flask, render_template, send_from_directory, request, \ url_for, abort, g, redirect, jsonify, session, flash, Response from flask_login import login_required +from fatcat_client import Editgroup from fatcat_client.rest import ApiException from fatcat_tools.transforms import * from fatcat_web import app, api, auth_api, priv_api @@ -312,23 +313,42 @@ def release_lookup(): abort(ae.status) return redirect('/release/{}'.format(resp.ident)) +# XXX: figure out CSRF stuff for local dev @app.route('/release/create', methods=['GET', 'POST']) @login_required +@app.csrf.exempt def release_create(): - form = ReleaseEntityForm() + form = ReleaseEntityForm(csrf_enabled=False) # XXX: if form.is_submitted(): - print("got form!") - print(form.errors) - if form.validate_on_submit(): - return redirect('/') - else: - print("didn't validate...") - if len(form.contribs) == 0: - form.contribs.append_entry() - form.contribs.append_entry() - form.contribs.append_entry() + 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 = 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 <a href="/editgroup/{}">{}</a>' \ + .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_release(entity, editgroup_id=eg.editgroup_id) + # redirect to new release + return redirect('/release/{}'.format(edit.ident)) + elif form.errors: + print("user form errors: {}".format(form.errors)) + print("didn't validate...") + elif len(form.contribs) == 0: form.contribs.append_entry() - return render_template('release_create.html', form=form) + # TODO: check for editgroup in session + editgroup_id = session.get('active_editgroup_id', None) + return render_template('release_create.html', + form=form, editgroup_id=editgroup_id) @app.route('/release/<ident>/history', methods=['GET']) def release_history(ident): diff --git a/python/fatcat_web/templates/release_create.html b/python/fatcat_web/templates/release_create.html index e3a0c9ab..7ede5dfd 100644 --- a/python/fatcat_web/templates/release_create.html +++ b/python/fatcat_web/templates/release_create.html @@ -2,11 +2,11 @@ {% macro form_field_errors(field) -%} {% if field.errors %} - <ul class="errors"> + <div class="ui pointing red label"> {% for err in field.errors %} - <li>{{ err }} + {{ err }} {% endfor %} - </ul> + </div> {% endif %} {%- endmacro %} @@ -21,12 +21,12 @@ {% 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;" #}> - <div class="field inline {% if field.errors %}error{% endif %}"> + <div class="field inline {{ div_classes }} {% if field.errors %}error{% endif %}"> {{ field.label }} </div> </div> <div class="twelve wide column" {# style="padding-left: 0.5rem;" #}> - <div class="field {% if field.errors %}error{% endif %}"> + <div class="field {{ div_classes }} {% if field.errors %}error{% endif %}"> {{ field() }} {{ form_field_errors(field) }} </div> @@ -44,17 +44,32 @@ <form class="ui form" id="add_work_form" method="POST" action="/release/create"> {{ form.hidden_tag() }} - <h3 class="ui dividing header">Edit Meta</h3> - {{ form_field_inline(form.editgroup_id) }} - {{ form_field_inline(form.editgroup_description) }} - {{ form_field_inline(form.edit_description) }} + <br> + <div class="ui accordion"> + <div class="{% if not editgroup_id %}active{% endif %} title"> + <h3><i class="dropdown icon"></i>Editgroup Meta</h3> + </div> + <div class="{% if not editgroup_id %}active{% endif %} content"> + {% if editgroup_id %} + <p>You have an editgroup in progress, and this edit will be included by + default. You can override this below. + {% else %} + <p>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 %} + {{ form_field_inline(form.editgroup_id) }} + {{ form_field_inline(form.editgroup_description) }} + </div> + </div> + <br> <h3 class="ui dividing header">The Basics</h3> <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"> - {{ form_field_basic(form.release_type) }} + {{ form_field_basic(form.release_type, "required") }} {{ form_field_basic(form.release_status) }} </div> </div> @@ -76,6 +91,7 @@ <div class="one wide column" style="padding-bottom: 0px;"></div> </div> + <br> <h3 class="ui dividing header">Contributors</h3> <div class="list-group" id="contrib_list" name="contrib_list"> {% for cform in form.contribs %} @@ -84,10 +100,14 @@ <i class="arrows alternate vertical icon"></i> </div> <div class="three wide column" style="padding-bottom: 0px; padding-left: 0px;"> - {{ cform.role }} + <div class="field {% if cform.role.errors %}error{% endif %}"> + {{ cform.role }} + </div> </div> <div class="eleven wide column" style="padding-bottom: 0px;"> - {{ cform.raw_name}} + <div class="field {% if cform.raw_name.errors %}error{% endif %}"> + {{ cform.raw_name}} + </div> </div> <div class="one wide column right aligned" style="padding-bottom: 0px; padding-left: 0rem;"> <button type="button" class="ui icon red button delete-contrib-button"><i class="trash icon"></i></button> @@ -101,27 +121,47 @@ </button> <br> + <br> <h3 class="ui dividing header">Identifers</h3> <br> {{ form_field_inline(form.doi) }} {{ form_field_inline(form.wikidata_qid) }} {{ form_field_inline(form.isbn13) }} - <div class="ui equal width fields"> - {{ form_field_basic(form.pmid) }} - {{ form_field_basic(form.pmcid) }} + <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"> + {{ form_field_basic(form.pmid) }} + {{ form_field_basic(form.pmcid) }} + </div> + </div> + <div class="one wide column" style="padding-bottom: 0px;"></div> </div> + <br> <h3 class="ui dividing header">Container</h3> <br> {{ form_field_inline(form.container_id) }} {{ form_field_inline(form.publisher) }} <br> - <div class="ui equal width fields"> - {{ form_field_basic(form.pages) }} - {{ form_field_basic(form.volume) }} - {{ form_field_basic(form.issue) }} + <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"> + {{ form_field_basic(form.pages) }} + {{ form_field_basic(form.volume) }} + {{ form_field_basic(form.issue) }} + </div> + </div> + <div class="one wide column" style="padding-bottom: 0px;"></div> </div> + <br><br> + <h3 class="ui dividing header">Submit</h3> + {{ form_field_basic(form.edit_description) }} + This description will be attached to this specific action, not to the + editgroup as a whole. + <br><br> <input class="ui primary submit button" type="submit" value="Create Release!"> </form> @@ -135,8 +175,11 @@ <!-- Form code --> $(document).ready(function() { - $('#release_type').dropdown(); - $('#release_status').dropdown(); + // these javascript dropdowns hide the original <input>, which breaks browser + // form focusing (eg, for required fields) :( + //$('#release_type').dropdown(); + //$('#release_status').dropdown(); + $('.ui.accordion').accordion(); var fixup_contrib_numbering = function(group_item) { items = Array.from(group_item.querySelectorAll(".list-group-item")) |