aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/fatcat_web/__init__.py2
-rw-r--r--python/fatcat_web/forms.py81
-rw-r--r--python/fatcat_web/routes.py30
-rw-r--r--python/fatcat_web/templates/release_create.html364
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");