diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2019-03-21 16:59:18 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2019-03-21 16:59:18 -0700 | 
| commit | 5527ecc2b7bd81bab9bcb065adbbccd941b85be8 (patch) | |
| tree | e8b01a20ee147858408c4058c707638a35649c1c /python | |
| parent | 1f165c34a6179f96b6de0c616f21a319e3c4ccce (diff) | |
| download | fatcat-5527ecc2b7bd81bab9bcb065adbbccd941b85be8.tar.gz fatcat-5527ecc2b7bd81bab9bcb065adbbccd941b85be8.zip | |
major progress on release create form
Diffstat (limited to 'python')
| -rw-r--r-- | python/fatcat_web/__init__.py | 2 | ||||
| -rw-r--r-- | python/fatcat_web/forms.py | 81 | ||||
| -rw-r--r-- | python/fatcat_web/routes.py | 30 | ||||
| -rw-r--r-- | python/fatcat_web/templates/release_create.html | 364 | 
4 files changed, 281 insertions, 196 deletions
| diff --git a/python/fatcat_web/__init__.py b/python/fatcat_web/__init__.py index e80393c2..00bc84fd 100644 --- a/python/fatcat_web/__init__.py +++ b/python/fatcat_web/__init__.py @@ -46,7 +46,7 @@ else:      print("No privileged token found")      priv_api = None -from fatcat_web import routes, auth, cors +from fatcat_web import routes, auth, cors, forms  gitlab_bp = create_flask_blueprint(Gitlab, oauth, auth.handle_oauth)  app.register_blueprint(gitlab_bp, url_prefix='/auth/gitlab') diff --git a/python/fatcat_web/forms.py b/python/fatcat_web/forms.py new file mode 100644 index 00000000..f961faa0 --- /dev/null +++ b/python/fatcat_web/forms.py @@ -0,0 +1,81 @@ + +""" +Note: in thoery could use, eg, https://github.com/christabor/swagger_wtforms, +but can't find one that is actually maintained. +""" + +from flask_wtf import FlaskForm +from wtforms import SelectField, DateField, StringField, FormField, FieldList, validators + +release_type_options = [ +    ('article-journal', 'Journal Article'), +    ('paper-conference', 'Conference Proceeding'), +    ('article', 'Article (non-journal)'), +    ('book', 'Book'), +    ('chapter', 'Book Chapter'), +    ('dataset', 'Dataset'), +    ('stub', 'Invalid/Stub'), +] +release_status_options = [ +    ('draft', 'Draft'), +    ('submitted', 'Submitted'), +    ('accepted', 'Accepted'), +    ('published', 'Published'), +    ('updated', 'Updated'), +] +role_type_options = [ +    ('author', 'Author'), +    ('editor', 'Editor'), +    ('translator', 'Translator'), +] + +class EntityEditForm(FlaskForm): +    editgroup_id = StringField('Editgroup ID', +        [validators.DataRequired()]) +    editgroup_description = StringField('Editgroup Description', +        [validators.Optional(True)]) +    edit_description = StringField('Description of Changes', +        [validators.Optional(True)]) + +class ReleaseContribForm(FlaskForm): +    #surname +    #given_name +    #creator_id (?) +    #orcid (for match?) +    raw_name = StringField('Display Name') +    role = SelectField(choices=role_type_options) + +class ReleaseEntityForm(EntityEditForm): +    """ +    TODO: +    - field types: fatcat id +    - date +    """ +    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_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")]) +    wikidata_qid = StringField('Wikidata QID') +    isbn13 = StringField('ISBN-13') +    pmid = StringField('PubMed Id') +    pmcid = StringField('PubMed Central Id') +    #core_id +    #arxiv_id +    #jstor_id +    volume = StringField('Volume') +    issue = StringField('Issue') +    pages = StringField('Pages') +    publisher = StringField('Publisher (optional)') +    language = StringField('Language (code)') +    license_slug = StringField('License (slug)') +    contribs = FieldList(FormField(ReleaseContribForm)) +    #refs +    #abstracts + diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py index e2c5fc3a..81c4c5c1 100644 --- a/python/fatcat_web/routes.py +++ b/python/fatcat_web/routes.py @@ -11,6 +11,7 @@ from fatcat_web import app, api, auth_api, priv_api  from fatcat_web.auth import handle_token_login, handle_logout, load_user, handle_ia_xauth  from fatcat_web.cors import crossdomain  from fatcat_web.search import * +from fatcat_web.forms import *  ### Views ################################################################### @@ -311,22 +312,23 @@ def release_lookup():          abort(ae.status)      return redirect('/release/{}'.format(resp.ident)) -@app.route('/release/create', methods=['GET']) -@login_required -def release_create_view(): -    return render_template('release_create.html') - -@app.route('/release/create', methods=['POST']) +@app.route('/release/create', methods=['GET', 'POST'])  @login_required  def release_create(): -    raise NotImplementedError -    params = dict() -    for k in request.form: -        if k.startswith('release_'): -            params[k[10:]] = request.form[k] -    release = None -    #edit = api.create_release(release, params=params) -    #return redirect("/release/{}".format(edit.ident)) +    form = ReleaseEntityForm() +    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() +        form.contribs.append_entry() +    return render_template('release_create.html', form=form)  @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 ac8a8169..e3a0c9ab 100644 --- a/python/fatcat_web/templates/release_create.html +++ b/python/fatcat_web/templates/release_create.html @@ -1,212 +1,214 @@  {% extends "base.html" %} + +{% macro form_field_errors(field) -%} +  {% if field.errors %} +    <ul class="errors"> +    {% for err in field.errors %} +        <li>{{ err }} +    {% endfor %} +    </ul> +  {% endif %} +{%- endmacro %} + +{% macro form_field_basic(field, div_classes="") -%} +<div class="field {{ div_classes }} {% if field.errors %}error{% endif %}"> +  {{ field.label }} +  {{ field() }} +  {{ 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;" #}> +    <div class="field inline {% 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 %}"> +      {{ field() }} +      {{ form_field_errors(field) }} +    </div> +  </div> +  <div class="one wide column"> +  </div> +</div> +{%- endmacro %} +  {% block body %}  <div class="ui segment"> -<h1 class="ui header">Adding a New Thing</h1> +<h1 class="ui header">Create New Release Entity</h1> -<form class="ui form" id="add_work_form"> -  <h3 class="ui dividing header">The Basics</h3> +<form class="ui form" id="add_work_form" method="POST" action="/release/create"> +  {{ form.hidden_tag() }} -  <div class="ui huge field required"> -    <label>Title</label> -    <input name="work_title" type="text" placeholder="Title of Work (in English)"> -  </div> +  <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) }} -  <div class="ui field required"> -    <label>Type of Work</label> -    <select class="ui dropdown" id="work_type"> -      <option value="">Primary Type</option> -      <option value="journal-article">Journal Article</option> -      <option value="book">Book</option> -      <option value="book-chapter">Book Chapter</option> -      <option value="dataset">Dataset</option> -      <option value="dissertation">Thesis or Dissertation</option> -      <option value="monograph">Monograph</option> -      <option value="proceedings-article">Conference Proceeding</option> -      <option value="report">Report</option> -      <option value="other">Other</option> -    </select> +  <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_status) }} +      </div> +    </div> +    <div class="one wide column" style="padding-bottom: 0px;"></div>    </div> -  <!-- Primary Creators/Authors --> -  <div class="ui field search" id="work_creators"> -    <label>Primary Creator(s)</label> -    <div class="ui icon input"> -      <input class="prompt" type="text" placeholder="Search..."> -      <i class="search icon"></i> +  {{ form_field_inline(form.title, "required") }} +  {{ form_field_inline(form.original_title) }} +  {{ form_field_inline(form.work_id) }} +  {{ 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;"> +      <div class="ui equal width fields"> +        {{ form_field_basic(form.language) }} +        {{ form_field_basic(form.license_slug) }} +      </div>      </div> -    <div class="results"></div> +    <div class="one wide column" style="padding-bottom: 0px;"></div>    </div> -  <!-- Description (not an abstract) --> -  <div class="ui field"> -    <label>Description</label> -    <div class="field"> -      <label>Not an abstract...</label> -      <textarea rows="2"></textarea> +  <h3 class="ui dividing header">Contributors</h3> +  <div class="list-group" id="contrib_list" name="contrib_list"> +  {% for cform in form.contribs %} +    <div class="list-group-item ui grid" style="padding-right: 1em;"> +      <div class="one wide column middle aligned center aligned sortable-handle" style="padding-bottom: 0px; padding-right: 0px; padding-left: 0px;"> +        <i class="arrows alternate vertical icon"></i> +      </div> +      <div class="three wide column" style="padding-bottom: 0px; padding-left: 0px;"> +        {{ cform.role }} +      </div> +      <div class="eleven wide column" style="padding-bottom: 0px;"> +        {{ cform.raw_name}} +      </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> +      </div>      </div> +  {% endfor %}    </div> - -  <!-- Primary/Original Language --> -  <div class="field"> -    <label>Primary Language</label> -    <select class="ui search select dropdown" id="language-select"> -      <option value="">Select if Appropriate</option> -      <option value="en">English</option> -      <option value="es">Spanish</option> -    </select> +  <br> +  <button type="button" id="add-contrib-button" class="ui right floated icon green button" style="margin-right: 0.3rem;"> +    <i class="plus icon"></i> +  </button> +  <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> -  <!-- Subject / Categorization / Tags --> -  <div class="field"> -    <label>Subject</label> -    <select multiple="" class="ui dropdown" id="subjects"> -      <option value="">Select Subject/Tags</option> -      <option value="AF">Afghanistan</option> -      <option value="AX">Ă…land Islands</option> -      <option value="AL">Albania</option> -      <option value="DZ">Algeria</option> -      <option value="AS">American Samoa</option> -      <option value="AD">Andorra</option> -      <option value="AO">Angola</option> -    </select> +  <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> +  <input class="ui primary submit button" type="submit" value="Create Release!"> -  <h3 class="ui dividing header">Primary Release / Edition</h3> - -  <!-- Contributors (and how) --> -  <div class="ui field search" id="release_creators"> -    <label>Primary Creator(s)</label> -    <div class="ui icon input"> -      <input class="prompt" type="text" placeholder="Search..."> -      <i class="search icon"></i> -    </div> -    <div class="results"></div> -  </div> - -  <!-- Date --> -  <!-- Container / Part-Of --> -  <!-- Publisher --> -  <!-- Identifier --> -  <!-- Language --> -  <!-- Type / Media --> -  <!-- Issue / Volume / Pages / Chapter --> - -  <!-- Anything Else? --> -  <h3 class="ui dividing header">Anything Else?</h3> - -  <!-- File / Copy / URL --> -  <!-- Citations --> - -<div class="ui submit button">Create Work</div>  </form>  </div>  {% endblock %}  {% block postscript %} +<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>  <script> -<!-- Form validation code --> +<!-- Form code -->  $(document).ready(function() { -  $('#add_work_form') -    .form({ -        fields: { -        name: { -            identifier: 'name', -            rules: [ -            { -                type   : 'empty', -                prompt : 'Please enter your name' -            } -            ] -        }, -        skills: { -            identifier: 'skills', -            rules: [ -            { -                type   : 'minCount[2]', -                prompt : 'Please select at least two skills' -            } -            ] -        }, -        gender: { -            identifier: 'gender', -            rules: [ -            { -                type   : 'empty', -                prompt : 'Please select a gender' -            } -            ] -        }, -        username: { -            identifier: 'username', -            rules: [ -            { -                type   : 'empty', -                prompt : 'Please enter a username' -            } -            ] -        }, -        password: { -            identifier: 'password', -            rules: [ -            { -                type   : 'empty', -                prompt : 'Please enter a password' -            }, -            { -                type   : 'minLength[6]', -                prompt : 'Your password must be at least {ruleValue} characters' -            } -            ] -        }, -        terms: { -            identifier: 'terms', -            rules: [ -            { -                type   : 'checked', -                prompt : 'You must agree to the terms and conditions' -            } -            ] -        } -        } -    }) -    ; - -  var example_authors = [ -    { title: 'Andorra' }, -    { title: 'United Arab Emirates' }, -    { title: 'Afghanistan' }, -    { title: 'Antigua' }, -    { title: 'Anguilla' }, -    { title: 'Albania' }, -    { title: 'Armenia' }, -    { title: 'Netherlands Antilles' }, -    { title: 'Angola' }, -    { title: 'Argentina' }, -    { title: 'American Samoa' }, -    { title: 'Austria' }, -    { title: 'Australia' }, -    { title: 'Aruba' }, -  ]; - -  $('#work_creators') -    .search({ -        source: example_authors -    }) -  ; - -  $('#release_creators') -    .search({ -        source: example_authors -    }) -  ; - -  $('#work_type').dropdown(); -  $('#subjects').dropdown(); -  $('#language-select').dropdown(); +  $('#release_type').dropdown(); +  $('#release_status').dropdown(); + +  var fixup_contrib_numbering = function(group_item) { +    items = Array.from(group_item.querySelectorAll(".list-group-item")) +    for (var i = 0; i < items.length; i++) { +      var item_el = items[i]; +      input_el = item_el.querySelectorAll("input")[0]; +      select_el = item_el.querySelectorAll("select")[0]; +      //console.log(input_el.id); +      //console.log(select_el.id); +      input_el.id = "contribs-" + i + "-raw_name"; +      input_el.name = input_el.id; +      select_el.id = "contribs-" + i + "-role"; +      select_el.name = select_el.id; +      //console.log(input_el.id); +      //console.log(select_el.id); +    }; +    console.log("re-named contrib rows up to i=" + i); +  }; + +  var contrib_list = document.getElementById('contrib_list'); +  var contrib_sortable = Sortable.create(contrib_list, { +    handle: '.sortable-handle', +    animation: 150, +    onSort: function(evt) { +      fixup_contrib_numbering(contrib_list); +    }, +  }); +  fixup_contrib_numbering(contrib_list); + +  var contrib_delete_handler = function(ev) { +    row = ev.target.parentNode.parentNode; +    // I don't understand why this hack is needed; maybe because of the sortable stuff? +    if(!row.classList.contains("list-group-item")) { +        row = row.parentNode; +    } +    // console.log(row); +    console.assert(row.classList.contains("list-group-item")); +    row.parentNode.removeChild(row); +    fixup_contrib_numbering(contrib_list); +  }; + +  var attach_contrib_delete_handler = function(topthing) { +    Array.from(topthing.querySelectorAll(".delete-contrib-button")).forEach((el) => { +      el.addEventListener("click", contrib_delete_handler); +    }); +  }; +  attach_contrib_delete_handler(document); + +  // XXX: really need some way to not duplicate this code from above... +  var contrib_template = ` +  <div class="list-group-item ui grid" style="padding-right: 1em;"> +      <div class="one wide column middle aligned center aligned sortable-handle" style="padding-bottom: 0px; padding-right: 0px; padding-left: 0px;"> +        <i class="arrows alternate vertical icon"></i> +      </div> +      <div class="three wide column" style="padding-bottom: 0px; padding-left: 0px;"> +        <select id="contribs-X-role" name="contribs-X-role"><option value="author">Author</option><option value="editor">Editor</option><option value="translator">Translator</option></select> +      </div> +      <div class="eleven wide column" style="padding-bottom: 0px;"> +        <input id="contribs-X-raw_name" name="contribs-X-raw_name" type="text" value=""> +      </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> +      </div> +    </div> +  `; + +  var add_contrib_button = document.getElementById("add-contrib-button"); +  add_contrib_button.addEventListener("click", function(){ +    contrib_list.insertAdjacentHTML('beforeend', contrib_template); +    attach_contrib_delete_handler(contrib_list.lastElementChild); +    fixup_contrib_numbering(contrib_list); +  });    console.log("Page loaded"); | 
