diff --git a/modules/webdeposit/etc/templates/Makefile.am b/modules/webdeposit/etc/templates/Makefile.am index dc4bb6c08..b927151b4 100644 --- a/modules/webdeposit/etc/templates/Makefile.am +++ b/modules/webdeposit/etc/templates/Makefile.am @@ -1,26 +1,35 @@ ## This file is part of Invenio. ## Copyright (C) 2012 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. templatesdir = $(sysconfdir)/templates templates_DATA = \ - webdeposit_add.html \ - webdeposit_index.html \ - webdeposit_index_deposition_types.html + webdeposit_add.html \ + webdeposit_add_action_bar.html \ + webdeposit_add_base.html \ + webdeposit_add_field.html \ + webdeposit_add_field_label.html \ + webdeposit_add_field_subform.html \ + webdeposit_add_group_end.html \ + webdeposit_add_group_start.html \ + webdeposit_index.html \ + webdeposit_index_deposition_types.html \ + webdeposit_widget_plupload.html \ + webdeposit_myview.html EXTRA_DIST = $(templates_DATA) CLEANFILES = *~ *.tmp diff --git a/modules/webdeposit/etc/templates/webdeposit_add.html b/modules/webdeposit/etc/templates/webdeposit_add.html index 69f0f0c2a..4a1711db6 100644 --- a/modules/webdeposit/etc/templates/webdeposit_add.html +++ b/modules/webdeposit/etc/templates/webdeposit_add.html @@ -1,340 +1,19 @@ {# ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. #} - -{% extends "page.html" %} - -{% block header %} - {{ super() }} - {% js 'js/jquery-ui.min.js', '20-jquery-ui' %} - {% js 'js/plupload/plupload.full.js', '50-webdeposit' %} - {% js 'js/webdeposit_form.js', '50-webdeposit' %} - {% js 'ckeditor/ckeditor.js', '50-ckeditor' %} - {% js 'ckeditor/invenio-ckeditor-config.js', '50-ckeditor' %} - {% css 'img/jquery-ui/themes/base/jquery.ui.theme.css', '20-jquery-ui' %} - {% css 'img/jquery-ui/themes/base/jquery.ui.datepicker.css', '20-jquery-ui' %} - -{% endblock %} - -{% block body %} - -<style> - -.ui-autocomplete-loading { background: white url('{{ url_for('static', filename='img/loading.gif') }}') right center no-repeat; } - -span.ui-icon.ui-icon-circle-triangle-w { color: transparent; cursor: pointer; } -span.ui-icon.ui-icon-circle-triangle-e { color: transparent; cursor: pointer; } - -.typeahead { - max-height: 250px; - overflow-y: auto; - /* prevent horizontal scrollbar */ - overflow-x: hidden; -} - -.l{ - size: 10px; -} - -.required:after { - color: red; - content:" *"; -} - -.rmlink { - cursor: pointer; - display: block; - margin-left: auto; - margin-right: auto -} -</style> - -<div class="page-header"> - <h2>{{ form._title }} - {% if form._drafting %} - <small class="pull-right"> - <small class="muted" id="status-indicator" style="font-size:12px; margin-right:10px;">Saved!</small> - - <a class="btn btn-info" - href="{{ url_for('webdeposit.create_new', deposition_type=deposition_type) }}"> - <i class="icon-edit icon-white"></i> {{ _('New Deposition') }} - </a> - - <a class="btn btn-danger" - href="{{ url_for('webdeposit.delete', deposition_type=deposition_type, uuid=uuid) }}"> - <i class="icon-remove icon-white"></i> {{ _('Delete Deposition') }} - </a> - - - <div class="btn-group"> - <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> - <i class="icon-list"></i> {{ _('Ongoing Depositions') }} - <span class="caret"></span> - </a> - <ul class="dropdown-menu"> - {% for draft in drafts %} - <li> <a href="{{ url_for('webdeposit.add', deposition_type=draft.workflow.name, uuid=draft.uuid) }}"> - {{ '<strong>'|safe if uuid == draft.uuid }} - {{ draft.workflow.name }}: - {% if draft.form_values and draft.form_values.title %} - {{ draft.form_values.title }} - {% else %} - {{ _('Untitled') }} - {% endif %} - {{ '</strong>'|safe if uuid == draft.uuid }} - <span style="font-size: 80%;" class="muted">{{ draft.timestamp|invenio_pretty_date }}</span> - </a> - </li> - {% endfor %} - </ul> - </div> - - </small> - <div class="clearfix"></div> - {% endif %} - </h2> -</div> - -<form enctype="multipart/form-data" name="submitForm" - id="submitForm" class="form-horizontal" method="post" - action="{{ url_for('webdeposit.add', deposition_type=deposition_type, uuid=uuid) }}"> - <fieldset> - - - {% for group, fields in form.get_groups() %} - - {% if loop.index == 1 %} - <div class="row"> - <div id="webdeposit_form_accordion" class="offset1 span8 accordion"> - {% endif %} - - {% if group.name != 'Rest' %} - <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle" data-toggle="collapse" - href="#collapse-{{ loop.index }}"> - {{ group.name }} <b class="caret" style="margin-top: 8px;"></b> - {% if 'meta' in group and 'indication' in group.meta %} - <span class="pull-right muted">{{ group.meta.indication }}</spani> - {% endif %} - </a> - </div> - <div id="collapse-{{ loop.index }}" class="accordion-body collapse in"> - <div class="accordion-inner"> - {% else %} - <br> - {% endif %} - - {% if 'meta' in group and 'description' in group.meta%} - <p>{{ group.meta.description }}</p> - {% endif %} - - {% for field in fields %} - - - {% if field.form %} - - {% endif %} - <div class="control-group {{ "error" if field.errors }}" id="error-group-{{ field.name }}"> - <div class="control-label {{ "error" if field.errors }}"> - {% if "submit" not in field.__html__() and "pluploader" not in field.__html__() %} - {% set label_class = (' required' if field.required else '') %} - {% if field.form %} - <h5> - {{ field.label.text }} - </h5> - {% else %} - {{ field.label(class_=label_class) }} - {% endif %} - {% endif %} - </div> - - - {% if field.form %} - </div> - - - {% for subfield in field.form %} - - <div class="control-group {{ "error" if subfield.errors }}" id="error-group-{{ subfield.name }}"> - - <div class="control-label {{ "error" if subfield.errors }}"> - {% if "submit" not in subfield.__html__() and "pluploader" not in subfield.__html__() %} - {% set label_class = (' required' if subfield.required else '') %} - {{ subfield.label(class_=label_class) }} - {% endif %} - </div> - - <div class="controls" id="field-{{ subfield.name }}"> - {% if subfield._icon_html and "pluploader" not in subfield.__html__() and not field.ckeditor %} - <div class="input-prepend"> - <span class="add-on"> - {{ subfield._icon_html|safe }} - </span> - {% elif "pluploader" not in subfield.__html__() and not field.ckeditor %} - <div style="margin-left: 27px;"> - {% else %} - <div> - {% endif %} - - {{ subfield(class_=field.short_name) }} - - </div> - - {% if subfield.errors %} - {% for error in subfield.errors %} - <div class="alert alert-error help-inline error-list-{{ subfield.name }}">{{ error }}</div> - {% endfor %} - {% endif %} - <div class="alert alert-error help-inline" id="error-{{ subfield.name }}" style="display:none;">error message</div> - <div class="alert alert-info help-inline" id="info-{{ subfield.name }}" style="display:none;">info message</div> - <div class="alert alert-success help-inline" id="success-{{ subfield.name }}" style="display:none;">success message</div> - </div> - </div> - - {% if ("keywords" in field.__html__()) %} - <div class="control-group" style="display: none;"> - <input name="keywords-{{ field.short_name}}" type="text" value=""> - </div> - - {% endif %} - - {% endfor %} - - <hr> - - - {% else %} - - <div {% if "pluploader" not in field.__html__() %} class="controls" {% endif %} id="field-{{ field.name }}"> - {% if field._icon_html and "pluploader" not in field.__html__() and not field.ckeditor %} - <div class="input-prepend"> - <span class="add-on"> - {{ field._icon_html|safe }} - </span> - {% elif "pluploader" not in field.__html__() and not field.ckeditor %} - <div style="margin-left: 27px;"> - {% else %} - <div> - {% endif %} - {{ field(class_=field.short_name) }} - - {% if field.description %} - <div class="help-block" style="font-size:12px; width: 350px; white-space:normal; color:#999999;"> - {{ field.description }} - </div> - {% endif %} - </div> - - {% if field.errors %} - {% for error in field.errors %} - <div class="alert alert-error help-inline error-list-{{ field.name }}">{{ error }}</div> - {% endfor %} - {% endif %} - <div class="alert alert-error help-inline" id="error-{{ field.name }}" style="display:none;">error message</div> - <div class="alert alert-info help-inline" id="info-{{ field.name }}" style="display:none;">info message</div> - <div class="alert alert-success help-inline" id="success-{{ field.name }}" style="display:none;">success message</div> - </div> - - </div> - - {% if ("keywords" in field.__html__()) %} - <div class="control-group" style="display: none;"> - <input name="keywords-{{ field.short_name}}" type="text" value=""> - </div> - {% endif %} - - {% endif %} - {% endfor %} - {% if group != 'Rest' %} - </div> - </div> - </div> - {% endif %} - {% endfor %} - <div class="alert alert-error offset1 span8" id="empty-fields-error" style="font-size:12px; display:none;"></div> - </div> - - </fieldset> -</form> - - -{% endblock %} - - -{% block javascript %} -{{ super() }} - -<script type="text/javascript" src="https://www.dropbox.com/static/api/1/dropbox.js" id="dropboxjs" data-app-key="your_app_key"></script> - -<!-- yahooapis is imported for pluploader --> -<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script> -<script type="text/javascript"> - -$(document).ready(function() { - var required_fields = {{ form.required_field_names|tojson|safe }}; - - webdeposit_init_plupload('.pluploader', - '{{ url_for('webdeposit.plupload', deposition_type=deposition_type, uuid=uuid) }}', - '{{ url_for('webdeposit.plupload_delete', uuid=uuid) }}', - '{{ url_for('webdeposit.plupload_get_file', uuid=uuid) }}', - {{ form.files|safe }}, - '{{ url_for('webdeposit.upload_from_url', deposition_type=deposition_type, uuid=uuid) }}'); - - webdeposit_input_error_check('#submitForm input, #submitForm textarea, #submitForm select', - '{{ url_for("webdeposit.error_check", uuid=uuid) }}', - required_fields); - - webdeposit_check_status('{{ url_for('webdeposit.check_status', uuid=uuid) }}'); - - -// Render autocomplete functions and ckeditor -{% for field in form %} - {% if field.autocomplete %} - webdeposit_field_autocomplete('input[name="{{ field.name }}"]', - '{{ url_for("webdeposit.autocomplete", - uuid=uuid, type=field.name) }}'); - {% endif %} - - {% if field.ckeditor %} - webdeposit_ckeditor_init( '{{ field.id }}', - '{{ url_for("webdeposit.error_check", uuid=uuid) }}', - required_fields ); - {% endif %} - -{% endfor %} - - $(".datepicker").datepicker({dateFormat: 'yy-mm-dd'}); - - $("input#submitButton").click(function(e ) { - e.preventDefault(); - emptyForm = checkEmptyFields(true, '', required_fields); - if (emptyForm[0] == 0){ - $('#empty-fields-error').hide('slow'); - $('#submitForm').submit(); - } - else { - $('#empty-fields-error').html("These fields are required!</br>" + "<a class='close' data-dismiss='alert' href='#'>×</a>" + emptyForm[1]); - $('#empty-fields-error').show('slow'); - } - }); - -}); -</script> - -{% endblock %} +{% extends "webdeposit_add_base.html" %} diff --git a/modules/webdeposit/etc/templates/webdeposit_add_action_bar.html b/modules/webdeposit/etc/templates/webdeposit_add_action_bar.html new file mode 100644 index 000000000..a068ac494 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_action_bar.html @@ -0,0 +1,10 @@ +<div class="well"{% if margin %}style="margin-top: {{margin}}"{% endif %}> + <a class="btn btn-danger pull-left" href="{{ url_for('webdeposit.delete', deposition_type=deposition_type, uuid=uuid) }}"><i class="icon-trash icon-white"></i> {{ _('Delete') }}</a> + <span class="pull-right"> + <span class="loader"><img src="{{ url_for('static', filename='css/images/ajax-loader.gif' ) }}" /></span> + <span class="status-indicator muted"></span> + <button class="btn form-save" data-toggle="tooltip" title="{{_('Press "Save" to save your upload for editing later, as many times you like.')}}" ><i class="icon-file"></i> Save</button> + <button data-target="#submitModal" data-toggle="modal" role="button" data-toggle="tooltip" title="{{_('Press "Submit" to finalize and make your upload public (further editing afterwards is currently not possible).')}}" class="btn btn-primary"><i class="icon-ok icon-white"></i> Submit</button> + </span> + <span class="clearfix"></span> +</div> \ No newline at end of file diff --git a/modules/webdeposit/etc/templates/webdeposit_add_base.html b/modules/webdeposit/etc/templates/webdeposit_add_base.html new file mode 100644 index 000000000..557f8d350 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_base.html @@ -0,0 +1,203 @@ +{# +## This file is part of Invenio. +## Copyright (C) 2013 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +#} +{% extends "page.html" %} + + +{% macro form_action_bar(margin="") -%} + {% include "webdeposit_add_action_bar.html" %} +{%- endmacro %} + +{%- macro form_group_accordion_start(group, idx) -%} + {% include "webdeposit_add_group_start.html" %} +{%- endmacro -%} + +{%- macro form_group_accordion_end(group, idx) -%} + {% include "webdeposit_add_group_end.html" %} +{%- endmacro -%} + +{%- macro field_label(thisfield) -%} + {% include "webdeposit_add_field_label.html" %} +{%- endmacro -%} + +{%- macro field_display(thisfield, field_class="span5", container_class="control-group") -%} + {% include "webdeposit_add_field.html" %} +{%- endmacro -%} + +{%- macro field_display_subform(thisfield) -%} + {% include "webdeposit_add_field_subform.html" %} +{%- endmacro -%} + +{% block header %} + {{ super() }} + {% js 'js/jquery-ui.min.js', '20-jquery-ui' %} + {% js 'js/plupload/plupload.full.js', '50-webdeposit' %} + {% js 'js/webdeposit_form.js', '50-webdeposit' %} + {% js 'js/hogan.js', '40-webdeposit-hogan' %} + {% js 'js/webdeposit_templates.js', '50-webdeposit' %} + {% js 'ckeditor/ckeditor.js', '50-ckeditor' %} + {% js 'ckeditor/invenio-ckeditor-config.js', '50-ckeditor' %} + {% css 'img/jquery-ui/themes/base/jquery.ui.theme.css', '20-jquery-ui' %} + {% css 'img/jquery-ui/themes/base/jquery.ui.datepicker.css', '20-jquery-ui' %} + +<style> + +.ui-autocomplete-loading { background: white url('{{ url_for('static', filename='img/loading.gif') }}') right center no-repeat; } + +span.ui-icon.ui-icon-circle-triangle-w { color: transparent; cursor: pointer; } +span.ui-icon.ui-icon-circle-triangle-e { color: transparent; cursor: pointer; } + +.typeahead { + max-height: 250px; + overflow-y: auto; + /* prevent horizontal scrollbar */ + overflow-x: hidden; +} + +.l{ + size: 10px; +} + +.required:after { + color: red; + content:" *"; +} + +.rmlink { + cursor: pointer; + display: block; + margin-left: auto; + margin-right: auto +} +</style> + +{% endblock %} + +{% block body %} +<div class="row"> + <div id="file_container" class="span8 form-feedback-warning"> + <div id="flash-message"></div> + <form enctype="multipart/form-data" name="submitForm" id="submitForm" class="form-horizontal" method="post" action="{{ url_for('webdeposit.add', deposition_type=deposition_type, uuid=uuid) }}"> + {% block form_header scoped %}{{ form_action_bar() }}{% endblock form_header%} + + {% block form_title scoped %} + <h1>{{ form._title }}</h1> + {% if form._subtitle %} + <p class="muted"><small>{{ form._subtitle|safe }}</small></p> + {% endif %} + {% endblock form_title %} + + {% block form_body scoped %} + {% for group, fields in form.get_groups() %} + {% set grouploop = loop %} + {% block form_group scoped %} + {% if grouploop.first %} + <div id="webdeposit_form_accordion" class="accordion"> + {% endif %} + {% block form_group_header scoped %} + {% if group %} + {{ form_group_accordion_start(group, grouploop.index) }} + {% endif %} + {% endblock %} + + {% block form_group_body scoped %} + {% if group and group.meta.description %} + <p>{{ group.meta.description|urlize }}</p> + {% endif %} + + {% block fieldset scoped %} + <fieldset> + {% for field in fields %} + {% block field_body scoped %} + {% if field.form %} + {{ field_display_subform(field) }} + {% else %} + {{ field_display(field) }} + {% endif %} + {% endblock field_body %} + {% endfor %} + </fieldset> + {% endblock fieldset %} + {% endblock form_group_body%} + + {% block form_group_footer scoped %} + {% if group %} + {{ form_group_accordion_end(group, grouploop.index) }} + {% endif %} + + {% endblock form_group_footer %} + + {% if grouploop.last %}</div>{% endif %} + {% endblock form_group %} + {% endfor %} + {% endblock form_body %} + + </form> + </div> + {% if form._drafting %} + <div class="span4"> + {% include "webdeposit_myview.html" %} + </div> + {% endif %} + </div> +</div> +{% endblock %} + + + +{% block javascript %} +{{ super() }} +{%- block form_script_options %} +<script type="text/javascript"> +var date_options = {dateFormat: 'yy-mm-dd'} +</script> +{%- endblock form_script_options %} +<script type="text/javascript" src="https://www.dropbox.com/static/api/1/dropbox.js" id="dropboxjs" data-app-key="{{config.CFG_DROPBOX_API_KEY}}"></script> +<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script> +<script type="text/javascript"> +$(document).ready(function() { + required_fields = {{ form.required_field_names|tojson|safe }}; + + webdeposit_init_plupload( + '.pluploader', + '{{ url_for('webdeposit.plupload', deposition_type=deposition_type, uuid=uuid) }}', + '{{ url_for('webdeposit.plupload_delete', uuid=uuid) }}', + '{{ url_for('webdeposit.plupload_get_file', uuid=uuid) }}', + {{ form.files|safe }}, + '{{ url_for('webdeposit.upload_from_url', deposition_type=deposition_type, uuid=uuid) }}' + ); + webdeposit_input_error_check('#submitForm input, #submitForm textarea, #submitForm select', '{{ url_for("webdeposit.error_check", uuid=uuid) }}'); + webdeposit_button_click('#submitForm .form-button', '{{ url_for("webdeposit.error_check", uuid=uuid) }}'); + webdeposit_check_status('{{ url_for('webdeposit.check_status', uuid=uuid) }}'); + // Render autocomplete functions and ckeditor +{%- for field in form %} + {%- if field.autocomplete %} + webdeposit_field_autocomplete('input[name="{{ field.name }}"]', '{{ url_for("webdeposit.autocomplete", form_type=form.type, field=field.name) }}'); + {%- endif %} + {%- if field.ckeditor %} + webdeposit_ckeditor_init( '{{ field.id }}', '{{ url_for("webdeposit.error_check", uuid=uuid) }}'); + {%- endif %} +{%- endfor %} + $(".datepicker").datepicker(); + + // Save & submit buttons + webdeposit_init_save('{{ url_for("webdeposit.error_check", uuid=uuid) }}', '.form-save', '#submitForm'); + webdeposit_init_submit('{{ url_for("webdeposit.error_check", uuid=uuid) }}', '.form-submit', '#submitForm'); +}); +</script> +{% endblock javascript %} diff --git a/modules/webdeposit/etc/templates/webdeposit_add_field.html b/modules/webdeposit/etc/templates/webdeposit_add_field.html new file mode 100644 index 000000000..a0faa203c --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_field.html @@ -0,0 +1,18 @@ +<div class="{{container_class}} {{ "error" if thisfield.errors }}{{ 'hide' if thisfield.flags.hidden}}" id="state-group-{{ thisfield.name }}"> + + {{ field_label(thisfield) }} + + <div class="{{ 'controls' if thisfield.widget.__name__ not in ['plupload_widget'] }}" id="field-{{ thisfield.name }}"> + {{ thisfield(class_= field_class + " " + thisfield.short_name) }} + + {% if thisfield.description %} + <p class="muted field-desc"><small>{{thisfield.description|urlize}}</small></p> + {% endif %} + + <div class="alert help-inline {{ 'alert-error' if thisfield.errors else 'hide' }}" id="state-{{ thisfield.name }}" style="margin-top: 5px;"> + {% for error in thisfield.errors %} + <div>{{ error }}</div> + {% endfor %} + </div> + </div> +</div> \ No newline at end of file diff --git a/modules/webdeposit/etc/templates/webdeposit_add_field_label.html b/modules/webdeposit/etc/templates/webdeposit_add_field_label.html new file mode 100644 index 000000000..7b26ac822 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_field_label.html @@ -0,0 +1,14 @@ +{% if thisfield.widget.__name__ not in ['plupload_widget'] and not 'button' in thisfield.__html__()|lower %} + {% if thisfield.label.text %} + {% if thisfield.form %} + <h5>{{ thisfield.label.text }}</h5> + {% else %} + <label class="control-label{{' required' if thisfield.flags.required}}{{ ' error' if thisfield.errors }}" for="{{thisfield.label.field_id}}"> + {%- if thisfield.icon %} + <span class="" style="margin-right: 5px; margin-top: 5px;"><i class={{ thisfield.icon}}></i></span> + {%- endif %} + {{ thisfield.label.text }} + </label> + {% endif %} + {% endif %} +{% endif %} diff --git a/modules/webdeposit/etc/templates/webdeposit_add_field_subform.html b/modules/webdeposit/etc/templates/webdeposit_add_field_subform.html new file mode 100644 index 000000000..9a9efa27e --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_field_subform.html @@ -0,0 +1,7 @@ +<div class="control-group {{ "error" if thisfield.errors }}" id="state-group-{{ thisfield.name }}"> + {{ field_label(thisfield) }} +</div> +{% for subfield in thisfield.form %} + {{ field_display( subfield )}} +{% endfor %} +<hr /> \ No newline at end of file diff --git a/modules/webdeposit/etc/templates/webdeposit_add_group_end.html b/modules/webdeposit/etc/templates/webdeposit_add_group_end.html new file mode 100644 index 000000000..a9ddd69d3 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_group_end.html @@ -0,0 +1 @@ + </div></div></div> \ No newline at end of file diff --git a/modules/webdeposit/etc/templates/webdeposit_add_group_start.html b/modules/webdeposit/etc/templates/webdeposit_add_group_start.html new file mode 100644 index 000000000..b56bece60 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_add_group_start.html @@ -0,0 +1,9 @@ +<div class="accordion-group"> + <div class="accordion-heading"> + {% if group.meta.indication %} + <span class="accordion-toggle muted pull-right">{{ group.meta.indication }}</span> + {% endif %} + <a class="accordion-toggle" data-toggle="collapse" href="#collapse-{{ idx }}"> {{ group.name }} <b class="caret" style="margin-top: 8px;"></b></a> + </div> + <div id="collapse-{{ idx }}" class="accordion-body collapse {{ 'in' if group.meta.classes is none else group.meta.classes }}"> + <div class="accordion-inner"> \ No newline at end of file diff --git a/modules/webdeposit/etc/templates/webdeposit_index.html b/modules/webdeposit/etc/templates/webdeposit_index.html index bc6a85421..72b6a9a32 100644 --- a/modules/webdeposit/etc/templates/webdeposit_index.html +++ b/modules/webdeposit/etc/templates/webdeposit_index.html @@ -1,62 +1,93 @@ {# ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. #} {% extends "page.html" %} {% block body %} <div class="page-header"> <h3> {{ _(deposition_type) }} <small> {{ _('select or create deposition') }} <a class="btn btn-info pull-right" href="{{ url_for('webdeposit.create_new', deposition_type=deposition_type) }}"> <i class="icon-edit icon-white"></i> {{ _('New Deposition') }} </a> </small> </h3> </div> <div class="row"> {% if drafts %} <ul class="nav nav-tabs nav-stacked offset3 span6"> {% for draft in drafts %} <li> <a href="{{ url_for('webdeposit.add', deposition_type=draft.workflow.name, uuid=draft.uuid) }}"> <i class="icon-chevron-right pull-right"></i> {{ '<strong>'|safe if uuid == draft.uuid }} {{ draft.workflow.name }}: {% if draft.form_values and draft.form_values.title %} {{ draft.form_values.title }} {% else %} {{ _('Untitled') }} {% endif %} {{ '</strong>'|safe if uuid == draft.uuid }} <span style="font-size: 80%;" class="muted">{{ draft.timestamp|invenio_pretty_date }}</span> </a> </li> {% endfor %} </ul> {% else %} <div class="span12"> <strong>{{ _('There is no ongoing deposition.') }}</strong> </div> {% endif %} </div> +{% if past_depositions %} + +<div class="row" style="margin-top: 22px;"> + <ul class="nav nav-list"> + <li class="divider" style="overflow: visible;"></li> +</ul> + + <h4 class="offset2"style="margin-top: 25px;">Past depositions</h4> + + <ul class="nav nav-tabs nav-stacked offset3 span6"> + {% for dep in past_depositions %} + {% if dep.extra_data.recid %} + <li> <a href="/record/{{ dep.extra_data.recid }}"> + <i class="icon-chevron-right pull-right"></i> + {{ dep.name }}: + {% if dep.extra_data.title %} + {{ dep.extra_data.title }} + {% else %} + {{ _('Untitled') }} + {% endif %} + </a> + </li> + {% endif %} + {% endfor %} + </ul> + + +</div> + +{% endif %} + {% endblock %} diff --git a/modules/webdeposit/etc/templates/webdeposit_myview.html b/modules/webdeposit/etc/templates/webdeposit_myview.html new file mode 100644 index 000000000..b9861cb2d --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_myview.html @@ -0,0 +1,20 @@ +<div class="well"> + <h2>My uploads</h2> + {% if not drafts %} + <p class="muted">You currently have no uploads.</p> + {% else %} + <h4>Unsubmitted</h4> + <table class="table table-striped"> + {% for draft in drafts %} + <tr> + <td><a href="{{ url_for('webdeposit.add', deposition_type=draft.workflow.name, uuid=draft.uuid) }}">{% if draft.form_values and draft.form_values.title %} + {{ draft.form_values.title }} + {% else %} + {{ _('Untitled') }} + {% endif %}</a></td><td>{{ draft.timestamp|invenio_pretty_date }}</td><td><a href="{{ url_for('webdeposit.delete', deposition_type=deposition_type, uuid=uuid) }}" class="rmlink" rel="tooltip" title="Delete upload"><i class="icon-trash"></i></a></td> + </tr> + {% endfor %} + </table> + {% endif %} +</div> + diff --git a/modules/webdeposit/etc/templates/webdeposit_widget_plupload.html b/modules/webdeposit/etc/templates/webdeposit_widget_plupload.html new file mode 100644 index 000000000..a9c4aaa83 --- /dev/null +++ b/modules/webdeposit/etc/templates/webdeposit_widget_plupload.html @@ -0,0 +1,25 @@ +<div class="pluploader" {{field_id}}> + <div class="well" id="filebox"> + <div id="drag_and_drop_text" style="text-align:center;z-index:-100;"> + <h1><small>Drag and Drop files here</small></h1> + </div> + </div> + <table id="file-table" class="table table-striped table-bordered" style="display:none;"> + <thead> + <tr> + <th>Filename</th> + <th>Size</th> + <th>Status</th> + <td></td> + </tr> + </thead> + <tbody id="filelist"> + </tbody> + </table> + <a class="btn btn-primary" id="pickfiles" >Select files</a> + <a class="btn btn-success disabled" id="uploadfiles"><i class="icon-upload icon-white"></i> Start upload</a> + <a class="btn btn-danger" id="stopupload" + style="display:none;"><i class="icon-stop icon-white"></i> Cancel upload</a> + <span id="upload_speed" class="pull-right"></span> + <div id="upload-errors"></div> +</div> diff --git a/modules/webdeposit/lib/Makefile.am b/modules/webdeposit/lib/Makefile.am index 5a31b075c..4e89e1eda 100644 --- a/modules/webdeposit/lib/Makefile.am +++ b/modules/webdeposit/lib/Makefile.am @@ -1,48 +1,51 @@ ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. SUBDIRS = deposition_forms deposition_fields deposition_types pylibdir = $(libdir)/python/invenio pylib_DATA = __init__.py \ + webdeposit_autocomplete_utils.py \ + webdeposit_api_blueprint.py \ webdeposit_blueprint.py \ - webdeposit_utils.py \ + webdeposit_cook_json_utils.py \ + webdeposit_field.py \ + webdeposit_field_widgets.py \ + webdeposit_filter_utils.py \ + webdeposit_form.py \ + webdeposit_load_deposition_types.py \ webdeposit_load_fields.py \ webdeposit_load_forms.py \ - webdeposit_load_deposition_types.py \ - webdeposit_field_widgets.py \ webdeposit_model.py \ + webdeposit_processor_utils.py \ + webdeposit_regression_tests.py \ + webdeposit_utils.py \ + webdeposit_validation_utils.py \ webdeposit_workflow.py \ webdeposit_workflow_utils.py \ - webdeposit_field.py \ - webdeposit_autocomplete_utils.py \ - webdeposit_validation_utils.py \ - webdeposit_config.py \ - webdeposit_config_utils.py \ - webdeposit_form.py \ - webdeposit_cook_json_utils.py \ - webdeposit_regression_tests.py + webdeposit_api_regression_tests.py jsdir=$(localstatedir)/www/js -js_DATA = webdeposit_form.js +js_DATA = webdeposit_form.js \ + webdeposit_templates.js EXTRA_DIST = $(pylib_DATA) \ $(js_DATA) CLEANFILES = *~ *.tmp *.pyc diff --git a/modules/webdeposit/lib/deposition_fields/Makefile.am b/modules/webdeposit/lib/deposition_fields/Makefile.am index 9cf412d23..7d0a70706 100644 --- a/modules/webdeposit/lib/deposition_fields/Makefile.am +++ b/modules/webdeposit/lib/deposition_fields/Makefile.am @@ -1,37 +1,38 @@ ## This file is part of Invenio. ## Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. pylibdir = $(libdir)/python/invenio/webdeposit_deposition_fields pylib_DATA = __init__.py \ author_field.py \ - integer_text_field.py \ journal_field.py \ publisher_field.py \ title_field.py \ date_field.py \ abstract_field.py \ file_upload_field.py \ issn_field.py \ keywords_field.py \ language_field.py \ notes_field.py \ pages_number_field.py \ doi_field.py \ - record_id_field.py + record_id_field.py \ + wtforms_field.py \ + record_id_field.py CLEANFILES = *~ *.tmp *.pyc diff --git a/modules/webdeposit/lib/deposition_fields/abstract_field.py b/modules/webdeposit/lib/deposition_fields/abstract_field.py index 674c707bb..f4be36412 100644 --- a/modules/webdeposit/lib/deposition_fields/abstract_field.py +++ b/modules/webdeposit/lib/deposition_fields/abstract_field.py @@ -1,34 +1,34 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextAreaField from invenio.webdeposit_field import WebDepositField -__all__ = ['AbstractField'] +__all__ = ['AbstractField'] -class AbstractField(WebDepositField(key='abstract.summary'), TextAreaField): +class AbstractField(WebDepositField, TextAreaField): def __init__(self, **kwargs): - super(AbstractField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-pencil"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') - + defaults = dict( + icon='icon-pencil', + recjson_key='abstract.summary' + ) + defaults.update(kwargs) + super(AbstractField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/author_field.py b/modules/webdeposit/lib/deposition_fields/author_field.py index fc3eed5e6..cbd5699be 100644 --- a/modules/webdeposit/lib/deposition_fields/author_field.py +++ b/modules/webdeposit/lib/deposition_fields/author_field.py @@ -1,42 +1,35 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField from invenio.webdeposit_autocomplete_utils import orcid_authors __all__ = ['AuthorField'] -class AuthorField(WebDepositField(key='authors[0].full_name'), TextField): - +class AuthorField(WebDepositField, TextField): def __init__(self, **kwargs): - super(AuthorField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-user"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') - - def autocomplete(self): - # Load custom autocompletion function - autocomplete = self.config.get_autocomplete_function() - if autocomplete is not None: - return autocomplete(self.data) - - return orcid_authors(self.data) + defaults = dict( + icon='icon-user', + recjson_key='authors[0].full_name', + autocomplete=orcid_authors + ) + defaults.update(kwargs) + super(AuthorField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/date_field.py b/modules/webdeposit/lib/deposition_fields/date_field.py index f49b42842..1f022c401 100644 --- a/modules/webdeposit/lib/deposition_fields/date_field.py +++ b/modules/webdeposit/lib/deposition_fields/date_field.py @@ -1,33 +1,60 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import DateField +from wtforms.validators import optional from invenio.webdeposit_field import WebDepositField +from datetime import date, datetime __all__ = ['Date'] -class Date(WebDepositField(), DateField): - +class Date(WebDepositField, DateField): def __init__(self, **kwargs): - super(Date, self).__init__(**kwargs) - self._icon_html = '<i class="icon-calendar"></i>' + defaults = dict( + icon='icon-calendar', + validators=[optional()] + ) + defaults.update(kwargs) + super(Date, self).__init__(**defaults) + + def json_data(self): + """ + Serialize data into JSON serializalbe object + """ + # Just use _value() to format the date into a string. + if self.data: + return self.data.strftime(self.format) #pylint: disable-msg= + return None - def pre_validate(self, form=None): - return dict(error=0, error_message='') + def process_data(self, value): + """ + Called when loading data from Python (incoming objects can be either + datetime objects or strings, depending on if they are loaded from + an JSON or Python objects). + """ + if isinstance(value, basestring): + self.object_data = datetime.strptime(value, self.format).date() + elif isinstance(value, datetime): + self.object_data = value.date() + elif isinstance(value, date): + self.object_data = value + # Be sure to set both self.object_data and self.data due to internals of + # Field.process() and draft_form_process_and_validate(). + self.data = self.object_data diff --git a/modules/webdeposit/lib/deposition_fields/doi_field.py b/modules/webdeposit/lib/deposition_fields/doi_field.py index a0868f4d5..92df54bd7 100644 --- a/modules/webdeposit/lib/deposition_fields/doi_field.py +++ b/modules/webdeposit/lib/deposition_fields/doi_field.py @@ -1,43 +1,59 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField -from invenio.webdeposit_validation_utils import datacite_doi_validate +from invenio.webdeposit_validation_utils import doi_syntax_validator +from invenio.webdeposit_filter_utils import strip_prefixes, strip_string +from invenio.webdeposit_processor_utils import datacite_lookup __all__ = ['DOIField'] -class DOIField(WebDepositField(key='publication_info.DOI'), TextField): +def missing_doi_warning(dummy_form, field, dummy_submit=False): + """ + Field processor, checking for existence of a DOI, and otherwise + asking people to provide it. + """ + if not field.errors and not field.data: + field.message_state = "warning" + field.messages.append("Please provide a DOI if possible.") + raise StopIteration() - def __init__(self, **kwargs): - super(DOIField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-barcode"></i>' - - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - return validation_json - return datacite_doi_validate(self) +class DOIField(WebDepositField, TextField): + def __init__(self, **kwargs): + defaults = dict( + icon='icon-barcode', + recjson_key='publication_info.DOI', + validators=[ + doi_syntax_validator, + ], + filters=[ + strip_string, + strip_prefixes("doi:", "http://dx.doi.org/"), + ], + processors=[ + missing_doi_warning, + datacite_lookup(display_info=True), + ], + placeholder="e.g. 10.1234/foo.bar...", + ) + defaults.update(kwargs) + super(DOIField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/file_upload_field.py b/modules/webdeposit/lib/deposition_fields/file_upload_field.py index 5418ec64f..d03a7c87a 100644 --- a/modules/webdeposit/lib/deposition_fields/file_upload_field.py +++ b/modules/webdeposit/lib/deposition_fields/file_upload_field.py @@ -1,33 +1,30 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import FileField from invenio.webdeposit_field import WebDepositField __all__ = ['FileUploadField'] -class FileUploadField(WebDepositField(), FileField): - +class FileUploadField(WebDepositField, FileField): def __init__(self, **kwargs): - super(FileUploadField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-file"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') + defaults = dict(icon='icon-file') + defaults.update(kwargs) + super(FileUploadField, self).__init__(**defaults) \ No newline at end of file diff --git a/modules/webdeposit/lib/deposition_fields/integer_text_field.py b/modules/webdeposit/lib/deposition_fields/integer_text_field.py index 4d5fd7879..e6c9b9def 100644 --- a/modules/webdeposit/lib/deposition_fields/integer_text_field.py +++ b/modules/webdeposit/lib/deposition_fields/integer_text_field.py @@ -1,32 +1,28 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import IntegerField from invenio.webdeposit_field import WebDepositField __all__ = ['IntegerTextField'] -class IntegerTextField(WebDepositField(), IntegerField): - - def __init__(self, **kwargs): - super(IntegerTextField, self).__init__(**kwargs) - - def pre_validate(self, form=None): - return dict(error=0, error_message='') +class IntegerTextField(WebDepositField, IntegerField): + # FIXME is field needed? + pass \ No newline at end of file diff --git a/modules/webdeposit/lib/deposition_fields/issn_field.py b/modules/webdeposit/lib/deposition_fields/issn_field.py index e5662ad38..76d5dff75 100644 --- a/modules/webdeposit/lib/deposition_fields/issn_field.py +++ b/modules/webdeposit/lib/deposition_fields/issn_field.py @@ -1,42 +1,34 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField -from invenio.webdeposit_validation_utils import sherpa_romeo_issn_validate +#from invenio.webdeposit_processor_utils import sherpa_romeo_issn_validate __all__ = ['ISSNField'] - -class ISSNField(WebDepositField(key='issn'), TextField): - +class ISSNField(WebDepositField, TextField): def __init__(self, **kwargs): - super(ISSNField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-barcode"></i>' - - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - return validation_json - return sherpa_romeo_issn_validate(self) + defaults = dict( + icon='icon-barcode', + recjson_key='issn', + #validators=[sherpa_romeo_issn_validate] #FIXME + ) + defaults.update(kwargs) + super(ISSNField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/journal_field.py b/modules/webdeposit/lib/deposition_fields/journal_field.py index 4a4814e54..d205308dd 100644 --- a/modules/webdeposit/lib/deposition_fields/journal_field.py +++ b/modules/webdeposit/lib/deposition_fields/journal_field.py @@ -1,51 +1,66 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField from invenio.webdeposit_autocomplete_utils import sherpa_romeo_journals -from invenio.webdeposit_validation_utils import sherpa_romeo_journal_validate +from invenio.webdeposit_processor_utils import sherpa_romeo_journal_process __all__ = ['JournalField'] -class JournalField(WebDepositField(), TextField): - +class JournalField(WebDepositField, TextField): def __init__(self, **kwargs): - super(JournalField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-book"></i>' - - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - - return validation_json - return sherpa_romeo_journal_validate(self) - - def autocomplete(self): - # Load custom autocompletion function - autocomplete = self.config.get_autocomplete_function() - if autocomplete is not None: - return autocomplete(self.data) - return sherpa_romeo_journals(self.data) + defaults = dict( + icon='icon-book', + processors=[sherpa_romeo_journal_process], + autocomplete=sherpa_romeo_journals, + ) + defaults.update(kwargs) + super(JournalField, self).__init__(**defaults) + + + +# from wtforms import TextField +# from invenio.bibknowledge import get_kb_mappings +# from invenio.webdeposit_field import WebDepositField + +# __all__ = ['JournalField'] + + +# def _kb_transform(val): +# ret = {} +# ret['value'] = val['key'] +# ret['label'] = val['key'] +# return ret + + +# class JournalField(WebDepositField, TextField): + +# def __init__(self, **kwargs): +# self._icon_html = '' +# super(JournalField, self).__init__(**kwargs) + +# def pre_validate(self, form): +# return dict(error=0, error_message='') + +# def autocomplete(self, term, limit): +# if not term: +# term = '' +# return map(_kb_transform, get_kb_mappings('journal_name', '', term)[:limit]) diff --git a/modules/webdeposit/lib/deposition_fields/keywords_field.py b/modules/webdeposit/lib/deposition_fields/keywords_field.py index 040c339ac..429a10cb5 100644 --- a/modules/webdeposit/lib/deposition_fields/keywords_field.py +++ b/modules/webdeposit/lib/deposition_fields/keywords_field.py @@ -1,33 +1,35 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField __all__ = ['KeywordsField'] -class KeywordsField(WebDepositField(), TextField): +class KeywordsField(WebDepositField, TextField): def __init__(self, **kwargs): - super(KeywordsField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-tags"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') + defaults = dict( + icon='icon-tags', + #validators=[sherpa_romeo_journal_validate], #FIXME + #autocomplete=sherpa_romeo_journals, + ) + defaults.update(kwargs) + super(KeywordsField, self).__init__(**defaults) \ No newline at end of file diff --git a/modules/webdeposit/lib/deposition_fields/language_field.py b/modules/webdeposit/lib/deposition_fields/language_field.py index 31cfc7444..19315d3fe 100644 --- a/modules/webdeposit/lib/deposition_fields/language_field.py +++ b/modules/webdeposit/lib/deposition_fields/language_field.py @@ -1,33 +1,33 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import SelectField from invenio.webdeposit_field import WebDepositField +from wtforms.validators import optional __all__ = ['LanguageField'] -class LanguageField(WebDepositField(key='language'), SelectField): - +class LanguageField(WebDepositField, SelectField): def __init__(self, **kwargs): - super(LanguageField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-flag"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') + defaults = dict(icon='icon-flag', + recjson_key='language', + validators=[optional()]) + defaults.update(kwargs) + super(LanguageField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/notes_field.py b/modules/webdeposit/lib/deposition_fields/notes_field.py index 432c70d68..2ed5e4931 100644 --- a/modules/webdeposit/lib/deposition_fields/notes_field.py +++ b/modules/webdeposit/lib/deposition_fields/notes_field.py @@ -1,33 +1,32 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextAreaField from invenio.webdeposit_field import WebDepositField __all__ = ['NotesField'] -class NotesField(WebDepositField(), TextAreaField): - +class NotesField(WebDepositField, TextAreaField): def __init__(self, **kwargs): - super(NotesField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-list"></i>' - - def pre_validate(self, form=None): - return dict(error=0, error_message='') + defaults = dict( + icon='icon-list', + ) + defaults.update(kwargs) + super(NotesField, self).__init__(**defaults) \ No newline at end of file diff --git a/modules/webdeposit/lib/deposition_fields/pages_number_field.py b/modules/webdeposit/lib/deposition_fields/pages_number_field.py index 9ba182358..1a31e7333 100644 --- a/modules/webdeposit/lib/deposition_fields/pages_number_field.py +++ b/modules/webdeposit/lib/deposition_fields/pages_number_field.py @@ -1,45 +1,34 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField -from invenio.webdeposit_validation_utils import number_validate +#from invenio.webdeposit_validation_utils import number_validate __all__ = ['PagesNumberField'] -class PagesNumberField(WebDepositField(), TextField): - +class PagesNumberField(WebDepositField, TextField): def __init__(self, **kwargs): - super(PagesNumberField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-th"></i>' - - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - - return validation_json - - # Default validation - return number_validate(self, error_message='Pages must be a number!') + defaults = dict( + icon='icon-th', + #FIXMEvalidators=[number_validate(error_message='Pages must be a number!')] #FIXME + ) + defaults.update(kwargs) + super(PagesNumberField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/publisher_field.py b/modules/webdeposit/lib/deposition_fields/publisher_field.py index 89dc875fc..4abfdebb9 100644 --- a/modules/webdeposit/lib/deposition_fields/publisher_field.py +++ b/modules/webdeposit/lib/deposition_fields/publisher_field.py @@ -1,50 +1,47 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField -from invenio.webdeposit_validation_utils import sherpa_romeo_publisher_validate +from invenio.webdeposit_processor_utils import sherpa_romeo_publisher_process from invenio.webdeposit_autocomplete_utils import sherpa_romeo_publishers __all__ = ['PublisherField'] -class PublisherField(WebDepositField(), TextField): - +class PublisherField(WebDepositField, TextField): def __init__(self, **kwargs): - super(PublisherField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-certificate"></i>' + defaults = dict( + icon='icon-certificate', + processors=[sherpa_romeo_publisher_process], + autocomplete=sherpa_romeo_publishers + ) + defaults.update(kwargs) + super(PublisherField, self).__init__(**defaults) - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - return validation_json - return sherpa_romeo_publisher_validate(self) + # def post_process(self, form, extra_processors=[]): + # sherpa_romeo_publisher_validate(self, form) #FIXME + # super(PublisherField, self).post_process(form, extra_processors=extra_processors) - def autocomplete(self): - # Load custom autocompletion function - autocomplete = self.config.get_autocomplete_function() - if autocomplete is not None: - return autocomplete(self.data) - return sherpa_romeo_publishers(self.data) + # def autocomplete(self, term, limit): #FIXME + # # Load custom auto complete function + # autocomplete = self.config.get_autocomplete_function() + # if autocomplete is not None: + # return autocomplete(self.data) + # return sherpa_romeo_publishers(self.data) diff --git a/modules/webdeposit/lib/deposition_fields/record_id_field.py b/modules/webdeposit/lib/deposition_fields/record_id_field.py index 723e40592..d696b69d8 100644 --- a/modules/webdeposit/lib/deposition_fields/record_id_field.py +++ b/modules/webdeposit/lib/deposition_fields/record_id_field.py @@ -1,35 +1,37 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms import TextField from invenio.webdeposit_field import WebDepositField -from invenio.webdeposit_validation_utils import record_id_validate +from invenio.webdeposit_processor_utils import record_id_process __all__ = ['RecordIDField'] -class RecordIDField(WebDepositField(key='recid'), TextField): +class RecordIDField(WebDepositField, TextField): """ Used to update existing records """ def __init__(self, **kwargs): - super(RecordIDField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-barcode"></i>' - - def pre_validate(self, form=None): - return record_id_validate(self, form) + defaults = dict( + icon='icon-barcode', + recjson_key='recid', + processors=[record_id_process] + ) + defaults.update(kwargs) + super(RecordIDField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/title_field.py b/modules/webdeposit/lib/deposition_fields/title_field.py index 79da8caa4..ff09e52b5 100644 --- a/modules/webdeposit/lib/deposition_fields/title_field.py +++ b/modules/webdeposit/lib/deposition_fields/title_field.py @@ -1,54 +1,46 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -from wtforms import TextField +from wtforms import TextField, ValidationError from invenio.webdeposit_field import WebDepositField __all__ = ['TitleField'] -class TitleField(WebDepositField(key='title.title'), TextField): +def validate_title(form, field): + value = field.data or '' + # Empty string allowed (required validator may be defined on per-field basis) + if value == "" or value.isspace(): + return + error_message = '' + if len(value) <= 4: + raise ValidationError("This field must have at least 4 characters") + + +class TitleField(WebDepositField, TextField): def __init__(self, **kwargs): - super(TitleField, self).__init__(**kwargs) - self._icon_html = '<i class="icon-book"></i>' - - def pre_validate(self, form=None): - # Load custom validation - validators = self.config.get_validators() - if validators is not [] and validators is not None: - validation_json = {} - for validator in validators: - json = validator(self) - validation_json = self.merge_validation_json(validation_json, json) - return validation_json - - value = self.data - if value == "" or value.isspace(): - return dict(error=0, error_message='') - error_message = 'Document Title must have at least 4 characters' - if len(str(value)) < 4: - try: - self.errors.append(error_message) - except AttributeError: - self.errors = list(self.process_errors) - self.errors.append(error_message) - return dict(error=1, - error_message=error_message) - return dict(error=0, error_message='') + defaults = dict( + icon='icon-book', + recjson_key='title.title', + #FIXMEvalidators=[validate_title] + + ) + defaults.update(kwargs) + super(TitleField, self).__init__(**defaults) diff --git a/modules/webdeposit/lib/deposition_fields/wtforms_field.py b/modules/webdeposit/lib/deposition_fields/wtforms_field.py new file mode 100644 index 000000000..2e67b053e --- /dev/null +++ b/modules/webdeposit/lib/deposition_fields/wtforms_field.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2013 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + + +""" +This module makes all WTForms fields available in WebDeposit, and ensure that +they subclass WebDepositField for added functionality + +The code is basically identical to importing all the WTForm fields and for each +field make a subclass according to the pattern (using FloatField as +an example):: + + class FloatField(WebDepositField, wtforms.FloatField): + pass +""" + +import wtforms +from invenio.webdeposit_field import WebDepositField + + +__all__ = [] + +for attr_name in dir(wtforms): + attr = getattr(wtforms, attr_name) + try: + if issubclass(attr, wtforms.Field): + # From a WTForm field, dynamically create a new class the same name as + # the WTForm field (inheriting from WebDepositField() and the WTForm + # field itself). Store the new class in the current module with the + # same name as the WTForms. + # + # For further information please see Python reference documne for + # globals() and type() functions. + globals()[attr_name] = type(str(attr_name), (WebDepositField, attr), {}) + __all__.append(attr_name) + except TypeError: + pass diff --git a/modules/webdeposit/lib/deposition_forms/article_form.py b/modules/webdeposit/lib/deposition_forms/article_form.py index e2ac3b8cf..ff5645eb8 100644 --- a/modules/webdeposit/lib/deposition_forms/article_form.py +++ b/modules/webdeposit/lib/deposition_forms/article_form.py @@ -1,82 +1,92 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA -from wtforms import SubmitField from wtforms.validators import Required +from invenio.config import CFG_SITE_SUPPORT_EMAIL from invenio.webinterface_handler_flask_utils import _ from invenio.webdeposit_form import WebDepositForm as Form from invenio.webdeposit_field_widgets import date_widget, plupload_widget, \ bootstrap_submit - -# Import custom fields from invenio.webdeposit_load_fields import fields __all__ = ['ArticleForm'] class ArticleForm(Form): - doi = fields.DOIField(label=_('DOI')) + doi = fields.DOIField(label=_('DOI'), recjson_key='publication_info.DOI') publisher = fields.PublisherField(label=_('Publisher'), - validators=[Required()]) + validators=[Required()], + recjson_key='imprint.publisher_name') journal = fields.JournalField(label=_('Journal Title'), validators=[Required()]) - issn = fields.ISSNField(label=_('ISSN')) - title = fields.TitleField(label=_('Document Title')) - author = fields.AuthorField(label=_('Author')) - abstract = fields.AbstractField(label=_('Abstract')) + issn = fields.ISSNField(label=_('ISSN'), recjson_key='issn') + title = fields.TitleField(label=_('Document Title'), + recjson_key='title.title') + author = fields.AuthorField(label=_('Author'), + recjson_key='authors[0].full_name') + abstract = fields.AbstractField(label=_('Abstract'), + recjson_key='abstract.summary') pagesnum = fields.PagesNumberField(label=_('Number of Pages')) languages = [("en", _("English")), - ("fre", _("French")), - ("ger", _("German")), - ("dut", _("Dutch")), - ("ita", _("Italian")), - ("spa", _("Spanish")), - ("por", _("Portuguese")), - ("gre", _("Greek")), - ("slo", _("Slovak")), - ("cze", _("Czech")), - ("hun", _("Hungarian")), - ("pol", _("Polish")), - ("nor", _("Norwegian")), - ("swe", _("Swedish")), - ("fin", _("Finnish")), - ("rus", _("Russian"))] + ("fre", _("French")), + ("ger", _("German")), + ("dut", _("Dutch")), + ("ita", _("Italian")), + ("spa", _("Spanish")), + ("por", _("Portuguese")), + ("gre", _("Greek")), + ("slo", _("Slovak")), + ("cze", _("Czech")), + ("hun", _("Hungarian")), + ("pol", _("Polish")), + ("nor", _("Norwegian")), + ("swe", _("Swedish")), + ("fin", _("Finnish")), + ("rus", _("Russian"))] language = fields.LanguageField(label=_('Language'), choices=languages) - date = fields.Date(label=_('Date of Document'), widget=date_widget) + date = fields.Date(label=_('Date of Document'), widget=date_widget, + recjson_key='imprint.date') keywords = fields.KeywordsField(label=_('Keywords')) - notes = fields.NotesField(label=_('Notes')) + notes = fields.NotesField(label=_('Notes'), recjson_key='comment') plupload_file = fields.FileUploadField(widget=plupload_widget) - submit = SubmitField(label=_('Submit Article'), - widget=bootstrap_submit) + submit = fields.SubmitField(label=_('Submit Article'), + widget=bootstrap_submit) """ Form Configuration variables """ _title = _('Submit an Article') + _subtitle = 'Instructions: (i) Press "Save" to save your upload for editing'\ + 'later, as many times you like. (ii) Upload and remove extra files in the'\ + 'bottom of the form. (iii) When ready, press "Submit" to finalize and make'\ + 'your upload public (editing afterwards only possible via submitting changes'\ + 'to <a class="muted"'\ + 'href="mailto:' + CFG_SITE_SUPPORT_EMAIL + '">' +\ + CFG_SITE_SUPPORT_EMAIL+'</a>).' _drafting = True # enable and disable drafting # Group fields in categories groups = [ ('Publisher/Journal', ['doi', 'publisher', 'journal', 'issn'], {'description': "Publisher and Journal fields are required.", 'indication': 'required'}), ('Basic Information', ['title', 'author', 'abstract', 'pagesnum']), ('Other', ['language', 'date', 'keywords', 'notes']) ] diff --git a/modules/webdeposit/lib/deposition_forms/photo_form.py b/modules/webdeposit/lib/deposition_forms/photo_form.py index 79870e891..33eecd69e 100644 --- a/modules/webdeposit/lib/deposition_forms/photo_form.py +++ b/modules/webdeposit/lib/deposition_forms/photo_form.py @@ -1,45 +1,44 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -from wtforms import SubmitField from invenio.webdeposit_form import WebDepositForm as Form from invenio.webinterface_handler_flask_utils import _ # Import custom fields from invenio.webdeposit_load_fields import fields from invenio.webdeposit_field_widgets import date_widget, plupload_widget, \ bootstrap_submit __all__ = ['PhotoForm'] class PhotoForm(Form): title = fields.TitleField(label=_('Photo Title')) author = fields.AuthorField(label=_('Photo Author')) date = fields.Date(label=_('Photo Date'), widget=date_widget) keywords = fields.KeywordsField(label=_('Keywords')) - notes = fields.NotesField(label=_('Comments or Notes')) + notes = fields.NotesField(label=_('Description')) plupload_file = fields.FileUploadField(label=_('Files'), widget=plupload_widget) - submit = SubmitField(label=_('Submit Photo'), widget=bootstrap_submit) + submit = fields.SubmitField(label=_('Submit Photo'), widget=bootstrap_submit) #configuration variables _title = _("Submit a Photo") _drafting = True # enable and disable drafting diff --git a/modules/webdeposit/lib/deposition_forms/poem_form.py b/modules/webdeposit/lib/deposition_forms/poem_form.py index f5aa9b657..616807cae 100644 --- a/modules/webdeposit/lib/deposition_forms/poem_form.py +++ b/modules/webdeposit/lib/deposition_forms/poem_form.py @@ -1,61 +1,60 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA -from wtforms import SubmitField from wtforms.validators import Required from invenio.webinterface_handler_flask_utils import _ from invenio.webdeposit_form import WebDepositForm as Form from invenio.webdeposit_field_widgets import bootstrap_submit # Import custom fields from invenio.webdeposit_load_fields import fields __all__ = ['PoemForm'] class PoemForm(Form): title = fields.TitleField(label=_('Poem Title'), validators=[Required()]) author = fields.AuthorField(label=_('Author'), validators=[Required()]) languages = [("en", _("English")), ("fre", _("French")), ("ger", _("German")), ("dut", _("Dutch")), ("ita", _("Italian")), ("spa", _("Spanish")), ("por", _("Portuguese")), ("gre", _("Greek")), ("slo", _("Slovak")), ("cze", _("Czech")), ("hun", _("Hungarian")), ("pol", _("Polish")), ("nor", _("Norwegian")), ("swe", _("Swedish")), ("fin", _("Finnish")), ("rus", _("Russian"))] language = fields.LanguageField(label=_('Language'), choices=languages, validators=[Required()]) year = fields.Date(label=_('Year'), validators=[Required()]) poem_text = fields.AbstractField(label='Poem Text', validators=[Required()]) - submit = SubmitField(label=_('Submit Poem'), + submit = fields.SubmitField(label=_('Submit Poem'), widget=bootstrap_submit) """ Form Configuration variables """ _title = _('Submit a Poem') _drafting = True # enable and disable drafting diff --git a/modules/webdeposit/lib/deposition_forms/preprint_form.py b/modules/webdeposit/lib/deposition_forms/preprint_form.py index 0eae86a03..0bbbfd318 100644 --- a/modules/webdeposit/lib/deposition_forms/preprint_form.py +++ b/modules/webdeposit/lib/deposition_forms/preprint_form.py @@ -1,61 +1,61 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA -from wtforms import TextField, SubmitField from wtforms.validators import Required from invenio.webdeposit_form import WebDepositForm as Form from invenio.webinterface_handler_flask_utils import _ -from invenio.webdeposit_field_widgets import date_widget, bootstrap_submit +from invenio.webdeposit_field_widgets import plupload_widget, date_widget, \ + bootstrap_submit # Import custom fields from invenio.webdeposit_load_fields import fields __all__ = ['PreprintForm'] class PreprintForm(Form): author = fields.AuthorField(label=_('Author'), validators=[Required()]) subject_category = fields.TitleField(label=_('Subject category'), validators=[Required()]) note = fields.NotesField(label=_('Note')) - institution = TextField(label=_('Institution')) - languages = [("en", _("English")), \ - ("fre", _("French")), \ - ("ger", _("German")), \ - ("dut", _("Dutch")), \ - ("ita", _("Italian")), \ - ("spa", _("Spanish")), \ - ("por", _("Portuguese")), \ - ("gre", _("Greek")), \ - ("slo", _("Slovak")), \ - ("cze", _("Czech")), \ - ("hun", _("Hungarian")), \ - ("pol", _("Polish")), \ - ("nor", _("Norwegian")), \ - ("swe", _("Swedish")), \ - ("fin", _("Finnish")), \ - ("rus", _("Russian"))] + institution = fields.TextField(label=_('Institution')) + languages = [("en", _("English")), + ("fre", _("French")), + ("ger", _("German")), + ("dut", _("Dutch")), + ("ita", _("Italian")), + ("spa", _("Spanish")), + ("por", _("Portuguese")), + ("gre", _("Greek")), + ("slo", _("Slovak")), + ("cze", _("Czech")), + ("hun", _("Hungarian")), + ("pol", _("Polish")), + ("nor", _("Norwegian")), + ("swe", _("Swedish")), + ("fin", _("Finnish")), + ("rus", _("Russian"))] language = fields.LanguageField(label=_("Language"), choices=languages) date = fields.Date(label=_('Date'), widget=date_widget) - file_field = fields.FileUploadField(label=_('File')) - submit = SubmitField(label=_('Submit Preprint'), widget=bootstrap_submit) + file_field = fields.FileUploadField(widget=plupload_widget) + submit = fields.SubmitField(label=_('Submit Preprint'), widget=bootstrap_submit) """ Form Configuration variables """ _title = _("Submit a Preprint") - _drafting = True #enable and disable drafting + _drafting = True # enable and disable drafting diff --git a/modules/webdeposit/lib/deposition_forms/thesis_form.py b/modules/webdeposit/lib/deposition_forms/thesis_form.py index 55585daa3..5ba58ceed 100644 --- a/modules/webdeposit/lib/deposition_forms/thesis_form.py +++ b/modules/webdeposit/lib/deposition_forms/thesis_form.py @@ -1,71 +1,72 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA -from wtforms import SelectField, SubmitField from wtforms.validators import Required from invenio.webdeposit_form import WebDepositForm as Form from invenio.webinterface_handler_flask_utils import _ -from invenio.webdeposit_field_widgets import date_widget, plupload_widget, bootstrap_submit +from invenio.webdeposit_cook_json_utils import add_author +from invenio.webdeposit_field_widgets import date_widget, plupload_widget, \ + bootstrap_submit # Import custom fields from invenio.webdeposit_load_fields import fields __all__ = ['ThesisForm'] class ThesisForm(Form): title = fields.TitleField(label=_('Original Thesis Title'), validators=[Required()]) - subtitle = fields.TitleField(label=_('Original Thesis Subtitle')) - alternative_title = fields.TitleField(label=_('Alternative Title')) - author = fields.AuthorField(label=_('Author')) + subtitle = fields.TitleField(label=_('Original Thesis Subtitle'), + recjson_key='title.subtitle') + author = fields.AuthorField(label=_('Author'), + cook_function=add_author) supervisor = fields.AuthorField(label=_('Thesis Supervisor')) abstract = fields.AbstractField(label=_('Abstract')) - subject = fields.TitleField(label=_('Subject')) - languages = [("en", _("English")), \ - ("fre", _("French")), \ - ("ger", _("German")), \ - ("dut", _("Dutch")), \ - ("ita", _("Italian")), \ - ("spa", _("Spanish")), \ - ("por", _("Portuguese")), \ - ("gre", _("Greek")), \ - ("slo", _("Slovak")), \ - ("cze", _("Czech")), \ - ("hun", _("Hungarian")), \ - ("pol", _("Polish")), \ - ("nor", _("Norwegian")), \ - ("swe", _("Swedish")), \ - ("fin", _("Finnish")), \ - ("rus", _("Russian"))] + languages = [("en", _("English")), + ("fre", _("French")), + ("ger", _("German")), + ("dut", _("Dutch")), + ("ita", _("Italian")), + ("spa", _("Spanish")), + ("por", _("Portuguese")), + ("gre", _("Greek")), + ("slo", _("Slovak")), + ("cze", _("Czech")), + ("hun", _("Hungarian")), + ("pol", _("Polish")), + ("nor", _("Norwegian")), + ("swe", _("Swedish")), + ("fin", _("Finnish")), + ("rus", _("Russian"))] language = fields.LanguageField(label=_("Language"), choices=languages) publisher = fields.PublisherField(label=_('Thesis Publisher')) defence_date = fields.Date(label=_('Date of Defence'), widget=date_widget) funded_choices = [("yes", _("Yes")), ("no", _("No"))] - funded = SelectField(label=_("Has your thesis been funded by the CERN Doctoral Student Program?"), - choices=funded_choices) + funded = fields.SelectField(label=_("Has your thesis been funded by the CERN Doctoral Student Program?"), + choices=funded_choices) - file_field = fields.FileUploadField(label=_('File'), widget=plupload_widget) - submit = SubmitField(label=_('Submit Thesis'), widget=bootstrap_submit) + file_field = fields.FileUploadField(widget=plupload_widget) + submit = fields.SubmitField(label=_('Submit Thesis'), widget=bootstrap_submit) """ Form Configuration variables """ _title = _("Submit a Thesis") _drafting = True # enable and disable drafting diff --git a/modules/webdeposit/lib/webdeposit_api_blueprint.py b/modules/webdeposit/lib/webdeposit_api_blueprint.py new file mode 100644 index 000000000..347aedf75 --- /dev/null +++ b/modules/webdeposit/lib/webdeposit_api_blueprint.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2012, 2013 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + + +import json +from flask import \ + request, \ + jsonify + +from invenio.bibworkflow_config import CFG_WORKFLOW_STATUS +from invenio.webdeposit_load_deposition_types import deposition_metadata +from invenio.webinterface_handler_flask_utils import InvenioBlueprint +from invenio.webdeposit_utils import create_workflow,\ + get_workflow, \ + set_form_status, \ + preingest_form_data, \ + get_preingested_form_data, \ + validate_preingested_data, \ + deposit_files +from invenio.webuser_flask import current_user +from invenio.web_api_key import api_key_required +from invenio.jsonutils import wash_for_json + +blueprint = InvenioBlueprint('webdeposit_api', __name__, + url_prefix='/api/deposit', + config='invenio.websubmit_config') + + +class enum(object): + def __init__(self, **enums): + for enum, code in enums.items(): + self.__setattr__(enum, code) + +ERROR = enum(INVALID_DEPOSITION=1) + + +@blueprint.route('/create/<deposition_type>/', methods=['POST', 'GET']) +@api_key_required +def deposition_create(deposition_type): + + user_id = current_user.get_id() + + if deposition_type not in deposition_metadata: + return False, jsonify({'error': ERROR.INVALID_DEPOSITION, + 'message': 'Invalid deposition.'}) + + workflow = create_workflow(deposition_type, user_id) + return jsonify({'uuid': str(workflow.get_uuid())}) + + +@blueprint.route('/set/<deposition_type>/', methods=['GET', 'POST']) +@api_key_required +def json_set(deposition_type): + if deposition_type not in deposition_metadata: + return False, jsonify({'error': ERROR.INVALID_DEPOSITION, + 'message': 'Invalid deposition.'}) + + user_id = current_user.get_id() + uuid = request.values['uuid'] + if 'form_data' in request.values: + form_data = request.form['form_data'] + form_data = wash_for_json(form_data) + form_data = json.loads(form_data) + preingest_form_data(user_id, form_data, uuid) + + if request.files: + deposit_files(user_id, deposition_type, uuid, preingest=True) + + return 'OK' + + +@blueprint.route('/get/<deposition_type>/', methods=['GET']) +@api_key_required +def json_get(deposition_type): + if request.method == 'GET': + uuid = request.args['uuid'] + user_id = current_user.get_id() + form_data = get_preingested_form_data(user_id, uuid) + # edit the form_data.pop('files') and return it with the actual url of + # the file + return jsonify(form_data) + else: + return '' + + +@blueprint.route('/submit/<deposition_type>/', methods=['GET']) +@api_key_required +def deposition_submit(deposition_type): + uuid = request.values['uuid'] + + user_id = current_user.get_id() + workflow = get_workflow(uuid, deposition_type) + errors = validate_preingested_data(user_id, uuid, deposition_type=None) + if errors: + return jsonify(errors) + + workflow_status = CFG_WORKFLOW_STATUS.RUNNING + while workflow_status != CFG_WORKFLOW_STATUS.FINISHED: + # Continue workflow + workflow.run() + set_form_status(1, uuid, CFG_WORKFLOW_STATUS.FINISHED) + workflow_status = workflow.get_status() + + return jsonify({}) diff --git a/modules/webdeposit/lib/webdeposit_api_regression_tests.py b/modules/webdeposit/lib/webdeposit_api_regression_tests.py new file mode 100644 index 000000000..85a60220c --- /dev/null +++ b/modules/webdeposit/lib/webdeposit_api_regression_tests.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Invenio. +## Copyright (C) 2012, 2013 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + + +from invenio.testutils import make_test_suite, run_test_suite, InvenioTestCase + + +class TestWebDepositAPI(InvenioTestCase): + def clear_tables(self): + from invenio.bibworkflow_model import Workflow, WfeObject + from invenio.sqlalchemyutils import db + + Workflow.query.delete() + WfeObject.query.delete() + db.session.commit() + + def setUp(self): + from random import randint + from invenio.web_api_key import create_new_web_api_key, \ + get_available_web_api_keys + from invenio.webdeposit_load_deposition_types import \ + deposition_metadata + # self.clear_tables() + + create_new_web_api_key(1, key_description='webdeposit_api_testing') + keys = get_available_web_api_keys(1) + self.apikey = keys[0].id + + # Test random deposition + self.deposition = deposition_metadata.keys()[randint(0, len(deposition_metadata.keys()) - 1)] + super(TestWebDepositAPI, self).setUp() + + def tearDown(self): + # self.clear_tables() + super(TestWebDepositAPI, self).tearDown() + + def test_create(self): + from flask import current_app, url_for + from invenio.web_api_key import build_web_request + + url = url_for('webdeposit_api.deposition_create', + deposition_type=self.deposition) + + url_create = build_web_request(url, api_key=self.apikey, + timestamp=False) + with current_app.test_client() as c: + response = c.get(url_create) + + self.assert200(response) + + assert "uuid" in response.json + + def test_json_get_set_functions(self): + import json + from flask import current_app, url_for + from invenio.webdeposit_load_deposition_types import \ + deposition_metadata + from invenio.webdeposit_utils import create_workflow + from wtforms import TextAreaField + from invenio.webdeposit_load_forms import forms + from invenio.web_api_key import build_web_request + + self.uuid = create_workflow(self.deposition, user_id=1).get_uuid() + + # Get form from deposition + for fun in deposition_metadata[self.deposition]['workflow']: + if fun.func_name == 'render': + form_type = fun.__form_type__ + + form = forms[form_type]() + + # Insert form data + form_data = {} + for field in form: + if isinstance(field, TextAreaField): + form_data[field.name] = 'testing webdeposit API' + + data = {'form_data': json.dumps(form_data), + 'uuid': self.uuid} + url = url_for('webdeposit_api.json_set', + deposition_type=self.deposition) + url_set = build_web_request(url, {}, + uid=1, api_key=self.apikey, + timestamp=False) + with current_app.test_client() as c: + response = c.post(url_set, data=data) + assert response._status_code == 200 + + url = url_for('webdeposit_api.json_get', deposition_type=self.deposition) + url_get = build_web_request(url, {'uuid': + self.uuid}, + uid=1, api_key=self.apikey, + timestamp=False) + response = c.get(url_get) + assert response._status_code == 200 + + for field in form: + if isinstance(field, TextAreaField): + assert response.json[field.name] == 'testing webdeposit API' + +TEST_SUITE = make_test_suite(TestWebDepositAPI) + +if __name__ == "__main__": + run_test_suite(TEST_SUITE) diff --git a/modules/webdeposit/lib/webdeposit_autocomplete_utils.py b/modules/webdeposit/lib/webdeposit_autocomplete_utils.py index fd0e08ea6..de7d09923 100644 --- a/modules/webdeposit/lib/webdeposit_autocomplete_utils.py +++ b/modules/webdeposit/lib/webdeposit_autocomplete_utils.py @@ -1,43 +1,54 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from invenio.sherpa_romeo import SherpaRomeoSearch from invenio.orcid import OrcidSearch -def sherpa_romeo_publishers(value): - sherpa_romeo = SherpaRomeoSearch() - publishers = sherpa_romeo.search_publisher(value) - if publishers is None: - return [] - return publishers +def sherpa_romeo_publishers(dummy_form, term, limit=50): + if term: + sherpa_romeo = SherpaRomeoSearch() + publishers = sherpa_romeo.search_publisher(term) + if publishers is None: + return [] + return publishers + return [] -def sherpa_romeo_journals(value): - s = SherpaRomeoSearch() - journals = s.search_journal(value) - if journals is None: - return [] - return journals +def sherpa_romeo_journals(dummy_form, term, limit=50): + """ + Search SHERPA/RoMEO for journal name + """ + if term: + # SherpaRomeoSearch doesnt' like unicode + if isinstance(term, unicode): + term = term.encode('utf8') + s = SherpaRomeoSearch() + journals = s.search_journal(term) + if journals is not None: + return journals[:limit] + return [] -def orcid_authors(value): - orcid = OrcidSearch() - orcid.search_authors(value) - return orcid.get_authors_names() +def orcid_authors(dummy_form, term, limit=50): + if term: + orcid = OrcidSearch() + orcid.search_authors(term) + return orcid.get_authors_names() + return [] diff --git a/modules/webdeposit/lib/webdeposit_blueprint.py b/modules/webdeposit/lib/webdeposit_blueprint.py index 458bcf7f7..57e7e1597 100644 --- a/modules/webdeposit/lib/webdeposit_blueprint.py +++ b/modules/webdeposit/lib/webdeposit_blueprint.py @@ -1,409 +1,380 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """WebDeposit Flask Blueprint""" import os -import shutil -from glob import iglob from flask import current_app, \ render_template, \ request, \ jsonify, \ redirect, \ url_for, \ flash, \ - send_file + send_file, \ + abort from werkzeug.utils import secure_filename from uuid import uuid1 as new_uuid from invenio.cache import cache from invenio.webdeposit_load_deposition_types import deposition_types, \ deposition_metadata from invenio.webinterface_handler_flask_utils import _, InvenioBlueprint -from invenio.webdeposit_utils import get_current_form, \ - get_form, \ - draft_field_set, \ +from invenio.webdeposit_utils import get_form, \ draft_field_list_add, \ delete_workflow, \ create_workflow, \ get_latest_or_new_workflow, \ get_workflow, \ draft_field_get_all, \ - draft_field_error_check, \ + draft_form_process_and_validate, \ + draft_form_autocomplete, \ draft_field_get, \ set_form_status, \ get_form_status, \ create_user_file_system, \ CFG_DRAFT_STATUS, \ url_upload,\ - get_all_drafts + get_all_drafts, \ + deposit_files, \ + delete_file, \ + save_form from invenio.webuser_flask import current_user from invenio.bibworkflow_config import CFG_WORKFLOW_STATUS blueprint = InvenioBlueprint('webdeposit', __name__, url_prefix='/deposit', config='invenio.websubmit_config', menubuilder=[('main.webdeposit', _('Deposit'), 'webdeposit.index_deposition_types', 2)], breadcrumbs=[(_('Deposit'), - 'webdeposit.index_deposition_types')]) + 'webdeposit.index_deposition_types')]) @blueprint.route('/upload_from_url/<deposition_type>/<uuid>', methods=['POST']) @blueprint.invenio_authenticated def upload_from_url(deposition_type, uuid): if request.method == 'POST': url = request.form['url'] if "name" in request.form: name = request.form['name'] else: name = None if "size" in request.form: size = request.form['size'] else: size = None unique_filename = url_upload(current_user.get_id(), deposition_type, uuid, url, name, size) return unique_filename @blueprint.route('/upload/<deposition_type>/<uuid>', methods=['POST']) @blueprint.invenio_authenticated def plupload(deposition_type, uuid): """ The file is splitted in chunks on the client-side and it is merged again on the server-side @return: the path of the uploaded file """ - if request.method == 'POST': - try: - chunks = request.form['chunks'] - chunk = request.form['chunk'] - except KeyError: - chunks = None - pass - name = request.form['name'] - current_chunk = request.files['file'] - - try: - filename = secure_filename(name) + "_" + chunk - except UnboundLocalError: - filename = secure_filename(name) - - CFG_USER_WEBDEPOSIT_FOLDER = create_user_file_system(current_user.get_id(), - deposition_type, - uuid) - - # Save the chunk - current_chunk.save(os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, filename)) - - unique_filename = "" - - if chunks is None: # file is a single chunk - unique_filename = str(new_uuid()) + filename - old_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, filename) - file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, - unique_filename) - os.rename(old_path, file_path) # Rename the chunk - size = os.path.getsize(file_path) - file_metadata = dict(name=name, file=file_path, size=size) - draft_field_list_add(current_user.get_id(), uuid, - "files", file_metadata) - elif int(chunk) == int(chunks) - 1: - '''All chunks have been uploaded! - start merging the chunks''' - filename = secure_filename(name) - chunk_files = [] - for chunk_file in iglob(os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, - filename + '_*')): - chunk_files.append(chunk_file) - - # Sort files in numerical order - chunk_files.sort(key=lambda x: int(x.split("_")[-1])) - - unique_filename = str(new_uuid()) + filename - file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, - unique_filename) - destination = open(file_path, 'wb') - for chunk in chunk_files: - shutil.copyfileobj(open(chunk, 'rb'), destination) - os.remove(chunk) - destination.close() - size = os.path.getsize(file_path) - file_metadata = dict(name=name, file=file_path, size=size) - draft_field_list_add(current_user.get_id(), uuid, - "files", file_metadata) - return unique_filename + return deposit_files(current_user.get_id(), deposition_type, uuid) @blueprint.route('/plupload_delete/<uuid>', methods=['GET', 'POST']) @blueprint.invenio_authenticated def plupload_delete(uuid): - if request.method == 'POST': - files = draft_field_get(current_user.get_id(), uuid, "files") - result = "File Not Found" - filename = request.form['filename'] - files = draft_field_get(current_user.get_id(), uuid, "files") - for i, f in enumerate(files): - if filename == f['file'].split('/')[-1]: # get the unique name from the path - os.remove(f['file']) - del files[i] - result = str(files) + " " - draft_field_set(current_user.get_id(), uuid, "files", files) - result = "File " + f['name'] + " Deleted" - break - return result + return delete_file(current_user.get_id(), uuid) @blueprint.route('/plupload_get_file/<uuid>', methods=['GET']) @blueprint.invenio_authenticated def plupload_get_file(uuid): filename = request.args.get('filename') tmp = "" files = draft_field_get(current_user.get_id(), uuid, "files") for f in files: tmp += f['file'].split('/')[-1] + '<br><br>' if filename == f['file'].split('/')[-1]: return send_file(f['file'], attachment_filename=f['name'], as_attachment=True) return "filename: " + filename + '<br>' + tmp @blueprint.route('/check_status/<uuid>/', methods=['GET', 'POST']) @blueprint.invenio_authenticated def check_status(uuid): form_status = get_form_status(current_user.get_id(), uuid) return jsonify({"status": form_status}) -@blueprint.route('_autocomplete/<uuid>', methods=['GET', 'POST']) +@blueprint.route('/autocomplete/<form_type>/<field>', methods=['GET', 'POST']) @blueprint.invenio_authenticated -def autocomplete(uuid): +def autocomplete(form_type, field): """ Returns a list with of suggestions for the field based on the current value """ - query = request.args.get('term') # value - field_type = request.args.get('type') # field + term = request.args.get('term') # value limit = request.args.get('limit', 50, type=int) - form = get_current_form(current_user.get_id(), uuid=uuid)[1] - form.__dict__["_fields"][field_type].process_data(query) + result = draft_form_autocomplete( + form_type, field, term, limit + ) - #Check if field has an autocomplete function - if hasattr(form.__dict__["_fields"][field_type], "autocomplete"): - return jsonify(results=form.__dict__["_fields"][field_type]. - autocomplete()[:limit]) - else: - return jsonify(results=[]) + return jsonify(results=result) -@blueprint.route('_errorCheck/<uuid>') +@blueprint.route('/save/<uuid>', methods=['POST']) @blueprint.invenio_authenticated def error_check(uuid): - """ Used for field error checking """ - value = request.args.get('attribute') - field_name = request.args.get('name') + Save and run error check on field values - if field_name == "": - return "{}" + The request body must contain a JSON-serialized field/value-dictionary. + A single or multiple fields may be passed in the dictionary, and values + may be any JSON-serializable object. Example:: - draft_field_set(current_user.get_id(), uuid, field_name, value) + { + 'title': 'Invenio Software', + 'authors': [['Smith, Joe', 'CERN'],['Smith, Jane','CERN']] + } + + The response is a JSON-serialized dictionary with the keys: + + * messages: Field/messages-dictionary + * values: Field/value-dictionary of unsubmitted fields that changed value. + * <flag>_on: List of fields, which flag changed to on. + * <flag>_off: List of fields, which flag changed to off. + + + The field/messages-dictionary looks like this:: + + {'title': {'state': '<state>', 'messages': [,...]}} + + where <state> is either 'success' if field was validated successfully, + 'info' if an information message should be displayed, and respectively the + same for 'warning' and 'error'. + + Example response:: + + { + 'messages': {'title': {'state': '<state>', 'messages': [,...]}}, + 'values': {'<field>': <value>, ...}, + 'hidden_on': ['<field>', ...], + 'hidden_off': ['<field>', ...], + 'disabled_on': ['<field>', ...], + 'disabled_off': ['<field>', ...], + } + + @return: A JSON-serialized field/result-dictionary (see above) + """ + if request.method != 'POST': + abort(400) + + # Process data, run validation, set in workflow object and return result + result = draft_form_process_and_validate(current_user.get_id(), uuid, request.json) - check_result = draft_field_error_check(current_user.get_id(), - uuid, field_name, value) try: - return jsonify(check_result) + return jsonify(result) except TypeError: - return jsonify({"error_message": "", "error": 0}) + return jsonify(None) @blueprint.route('/<deposition_type>/delete/<uuid>') @blueprint.invenio_authenticated def delete(deposition_type, uuid): """ Deletes the whole deposition with uuid=uuid (including form drafts) redirects to load another workflow """ if deposition_type not in deposition_metadata: flash(_('Invalid deposition type `%s`.' % deposition_type), 'error') return redirect(url_for('.index_deposition_types')) delete_workflow(current_user.get_id(), uuid) flash(deposition_type + _(' deposition deleted!'), 'error') return redirect(url_for("webdeposit.index", deposition_type=deposition_type)) @blueprint.route('/<deposition_type>/new/') @blueprint.invenio_authenticated def create_new(deposition_type): """ Creates new deposition """ if deposition_type not in deposition_metadata: flash(_('Invalid deposition type `%s`.' % deposition_type), 'error') return redirect(url_for('.index_deposition_types')) workflow = create_workflow(deposition_type, current_user.get_id()) uuid = workflow.get_uuid() flash(deposition_type + _(' deposition created!'), 'info') return redirect(url_for("webdeposit.add", deposition_type=deposition_type, uuid=uuid)) @blueprint.route('/') def index_deposition_types(): """ Renders the deposition types (workflows) list """ current_app.config['breadcrumbs_map'][request.endpoint] = [ (_('Home'), '')] + blueprint.breadcrumbs drafts = get_all_drafts(current_user.get_id()) return render_template('webdeposit_index_deposition_types.html', deposition_types=deposition_types, drafts=drafts) @blueprint.route('/<deposition_type>/') @blueprint.invenio_authenticated def index(deposition_type): if deposition_type not in deposition_metadata: flash(_('Invalid deposition type `%s`.' % deposition_type), 'error') return redirect(url_for('.index_deposition_types')) current_app.config['breadcrumbs_map'][request.endpoint] = [ (_('Home'), '')] + blueprint.breadcrumbs + [(deposition_type, None)] user_id = current_user.get_id() drafts = draft_field_get_all(user_id, deposition_type) + from invenio.bibworkflow_model import Workflow + past_depositions = \ + Workflow.get(Workflow.name == deposition_type, + Workflow.user_id == user_id, + Workflow.status == CFG_WORKFLOW_STATUS.FINISHED).\ + all() + return render_template('webdeposit_index.html', drafts=drafts, deposition_type=deposition_type, - deposition_types=deposition_types) + deposition_types=deposition_types, + past_depositions=past_depositions) @blueprint.route('/<deposition_type>/<uuid>', methods=['GET', 'POST']) @blueprint.invenio_authenticated def add(deposition_type, uuid): """ Runs the workflows and shows the current form/output of the workflow Loads the associated to the uuid workflow. if the current step of the workflow renders a form, it loads it. if the workflow is finished or in case of error, it redirects to the deposition types page flashing also the associated message. Moreover, it handles a form's POST request for the fields and files, and validates the whole form after the submission. @param deposition_type: the type of the deposition to be run. @param uuid: the universal unique identifier for the workflow. """ status = 0 if deposition_type not in deposition_metadata: flash(_('Invalid deposition type `%s`.' % deposition_type), 'error') return redirect(url_for('.index_deposition_types')) elif uuid is None: # get the latest one. if there is no workflow created # lets create a new workflow with given deposition type workflow = get_latest_or_new_workflow(deposition_type) uuid = workflow.get_uuid() #flash(_('Deposition %s') % (uuid,), 'info') return redirect(url_for('.add', deposition_type=deposition_type, uuid=uuid)) else: # get workflow with specific uuid - workflow = get_workflow(deposition_type, uuid) + workflow = get_workflow(uuid, deposition_type) if workflow is None: flash(_('Deposition with uuid `') + uuid + '` not found.', 'error') return redirect(url_for('.index_deposition_types')) cache.delete_many(str(current_user.get_id()) + ":current_deposition_type", str(current_user.get_id()) + ":current_uuid") - cache.add(str(current_user.get_id()) + ":current_deposition_type", deposition_type) + cache.add(str(current_user.get_id()) + ":current_deposition_type", + deposition_type) cache.add(str(current_user.get_id()) + ":current_uuid", uuid) current_app.config['breadcrumbs_map'][request.endpoint] = [ (_('Home'), '')] + blueprint.breadcrumbs + \ [(deposition_type, 'webdeposit.index', {'deposition_type': deposition_type}), (uuid, 'webdeposit.add', {'deposition_type': deposition_type, 'uuid': uuid})] if request.method == 'POST': # Save the files for uploaded_file in request.files.values(): filename = secure_filename(uploaded_file.filename) if filename == "": continue CFG_USER_WEBDEPOSIT_FOLDER = create_user_file_system(current_user.get_id(), deposition_type, uuid) unique_filename = str(new_uuid()) + filename file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, unique_filename) uploaded_file.save(file_path) size = os.path.getsize(file_path) file_metadata = dict(name=filename, file=file_path, size=size) draft_field_list_add(current_user.get_id(), uuid, "files", file_metadata) # Save form values - for (field_name, value) in request.form.items(): - if "submit" in field_name.lower(): - continue - draft_field_set(current_user.get_id(), uuid, field_name, value) + form = get_form(current_user.get_id(), uuid, formdata=request.form) - form = get_form(current_user.get_id(), uuid) # Validate form if not form.validate(): # render the form with error messages # the `workflow.get_output` function returns also the template - return render_template(**workflow.get_output(form_validation=True)) - + form.post_process() + save_form(current_user.get_id(), uuid, form) + return render_template(**workflow.get_output(form=form, + form_validation=True)) #Set the latest form status to finished set_form_status(current_user.get_id(), uuid, CFG_DRAFT_STATUS['finished']) + save_form(current_user.get_id(), uuid, form) workflow.run() status = workflow.get_status() if status != CFG_WORKFLOW_STATUS.FINISHED and \ status != CFG_WORKFLOW_STATUS.ERROR: # render current step of the workflow # the `workflow.get_output` function returns also the template return render_template(**workflow.get_output()) elif status == CFG_WORKFLOW_STATUS.FINISHED: - flash(deposition_type + _(' deposition has been successfully finished.'), - 'success') + msg = deposition_type + _(' deposition has been successfully finished.') + recid = workflow.get_data('recid') + if recid is not None: + msg += ' Record available <a href=/record/%s>here</a>.' % recid + flash(msg, 'success') return redirect(url_for('.index_deposition_types')) elif status == CFG_WORKFLOW_STATUS.ERROR: flash(deposition_type + _(' deposition %s has returned error.'), 'error') current_app.logger.error('Deposition: %s has returned error. %d' % uuid) return redirect(url_for('.index_deposition_types')) diff --git a/modules/webdeposit/lib/webdeposit_config.py b/modules/webdeposit/lib/webdeposit_config.py deleted file mode 100644 index 7f30ad48f..000000000 --- a/modules/webdeposit/lib/webdeposit_config.py +++ /dev/null @@ -1,177 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 CERN. -## -## Invenio is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License as -## published by the Free Software Foundation; either version 2 of the -## License, or (at your option) any later version. -## -## Invenio is distributed in the hope that it will be useful, but -## WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -## General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Invenio; if not, write to the Free Software Foundation, Inc., -## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - -from invenio.webinterface_handler_flask_utils import _ - - -""" -WebDeposit Configuration - -Define here validators and autocomplete functions for fields -to override the default ones. - -The structure must be in dictionaries as follows: - -Deposition name: Form type: 'fields': name of the field - -deposition: - collection: - The collection that the deposition belongs. - -form: - title: - string, the title to be shown to the user above the form. - file_cook: - define here a function if you want different handling in json's transformation - for the files of the form. - template: - the template to be used for the form. - if not defined, the default one is being used. - -fields: - label: - string, the field's label - validators: - list with path strings to be imported with werkzeug's `import_string`. - The validators of the field. - autocomplete: - path string to be imported with werkzeug's `import_string`. - The function that autocompletes the field. - recjson_key: - the association with BibField. Define here a mapping with a BibField's field. - widget: - path string to be imported with werkzeug's `import_string`. - WTField widget. - -Deposition name: Form type: 'fields': name of the field - -deposition: - collection: - The collection that the deposition belongs. - -form: - title: - string, the title to be shown to the user above the form. - file_cook: - define here a function if you want different handling in json's transformation - for the files of the form. - -fields: - label: - string, the field's label - validators: - list with path strings to be imported with werkzeug's `import_string`. - The validators of the field. - autocomplete: - path string to be imported with werkzeug's `import_string`. - The function that autocompletes the field. - recjson_key: - the association with BibField. Define here a mapping with a BibField's field. - widget: - path string to be imported with werkzeug's `import_string`. - WTField widget. - -""" - -config = { - 'Article': { - 'ArticleForm': { - 'fields': { - 'DOIField': { - 'label': 'DOI', - 'validators': ['invenio.webdeposit_validation_utils:datacite_doi_validate'], - 'recjson_key': 'publication_info.DOI' - }, - 'PublisherField': { - 'label': 'Publisher', - 'autocomplete': 'invenio.webdeposit_autocomplete_utils:sherpa_romeo_publishers', - 'validators': ['invenio.webdeposit_validation_utils:sherpa_romeo_publisher_validate'], - 'recjson_key': 'imprint.publisher_name' - }, - 'JournalField': { - 'label': 'Journal Title', - 'autocomplete': 'invenio.webdeposit_autocomplete_utils:sherpa_romeo_journals', - 'validators': ['invenio.webdeposit_validation_utils:sherpa_romeo_journal_validate'] - }, - 'ISSNField': { - 'label': 'ISSN', - 'validators': ['invenio.webdeposit_validation_utils:sherpa_romeo_issn_validate'], - 'recjson_key': 'issn' - }, - 'TitleField': { - 'label': 'Document Title', - 'recjson_key': 'title.title' - }, - 'AuthorField': { - 'label': 'Author', - 'autocomplete': 'invenio.webdeposit_autocomplete_utils:orcid_authors', - 'recjson_key': 'authors[0].full_name' - }, - 'AbstractField': { - 'label': 'Abstract', - 'recjson_key': 'abstract.summary' - }, - 'PagesNumberField': { - 'label': 'Number of Pages', - 'validators': ['invenio.webdeposit_validation_utils:number_validate'] - }, - 'LanguageField': { - 'label': 'Language', - 'recjson_key': 'invenio.webdeposit_cook_json_utils:cook_language' - }, - 'Date': { - 'label': 'Date of Document', - 'widget': 'invenio.webdeposit_field_widgets:date_widget', - 'recjson_key': 'imprint.date' - }, - 'NotesField': { - 'label': 'Notes or Comments', - 'recjson_key': 'comment' - }, - 'KeywordsField': { - 'label': 'Keywords' - }, - 'FileUploadField': { - # The files of a form are handled by the FFT field - 'label': 'File', - 'validators': ['invenio.webdeposit_validation_utils:number_validate'], - 'widget': 'invenio.webdeposit_field_widgets:plupload_widget' - }, - 'SubmitField': { - # The submit field accepts only label and widget configuration - 'label': 'Submit Article', - 'widget': 'invenio.webdeposit_field_widgets:bootstrap_submit' - } - }, - 'title': _('Submit an Article') - }, - 'collection': 'Article' - }, - 'Photo': { - 'PhotoForm': { - 'fields': { - 'NotesField': { - 'recjson_key': 'comment' - } - }, - 'file_cook': 'invenio.webdeposit_cook_json_utils:cook_picture' - }, - 'collection': 'Picture' - } -} diff --git a/modules/webdeposit/lib/webdeposit_config_utils.py b/modules/webdeposit/lib/webdeposit_config_utils.py deleted file mode 100644 index 521b6b8bd..000000000 --- a/modules/webdeposit/lib/webdeposit_config_utils.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 CERN. -## -## Invenio is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License as -## published by the Free Software Foundation; either version 2 of the -## License, or (at your option) any later version. -## -## Invenio is distributed in the hope that it will be useful, but -## WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -## General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Invenio; if not, write to the Free Software Foundation, Inc., -## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - -from werkzeug.utils import import_string -from invenio.cache import cache -from invenio.sqlalchemyutils import db -from invenio.webuser_flask import current_user -from invenio.webdeposit_model import WebDepositDraft -from invenio.webdeposit_cook_json_utils import cook_to_recjson -from invenio.bibworkflow_model import Workflow -from invenio.webinterface_handler_flask_utils import _ -from invenio.webdeposit_cook_json_utils import cook_to_recjson - - -class WebDepositConfiguration(object): - """ Webdeposit configuration class - Returns configuration for fields based on runtime variables - or, if not defined, based on the parameters - - @param deposition_type: initialize the class for a deposition type - - @param form_name: initialize the class for a form - used when a form is defined to load validators and widgets - - @param field_type: initialize the class for a field - used in the field pre_validate and autocomplete methods - to load and call them on runtime - """ - - def __init__(self, deposition_type=None, form_type=None, field_type=None): - self.config = import_string('invenio.webdeposit_config:config') - self.deposition_type = deposition_type - self.form_type = form_type - self.field_type = field_type - self._runtime_vars_init() - - def _runtime_vars_init(self): - """ Initializes user_id, deposition type, uuid and form_type - """ - - self.user_id = current_user.get_id() - - if self.deposition_type is None: - - self.runtime_deposition_type = cache.get(str(self.user_id) + - ":current_deposition_type") - else: - self.runtime_deposition_type = None - - # The uuid is always defined on runtime - self.uuid = cache.get(str(self.user_id) + ":current_uuid") - - if self.uuid is not None and self.form_type is None: - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == self.user_id, - WebDepositDraft.uuid == self.uuid) - # get the draft with the max step - webdeposit_draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) - - self.runtime_form_type = webdeposit_draft.form_type - else: - self.runtime_form_type = None - - #FIXME: Make the deposition_type, form_type and field_type an attribute - def get_deposition_type(self): - return self.runtime_deposition_type or self.deposition_type - - def get_form_type(self): - return self.runtime_form_type or self.form_type - - def get_field_type(self): - return self.field_type - - def _parse_config(self, config_key, deposition_type=None, form_type=None, - field_type=None): - if deposition_type in self.config: - deposition_config = self.config[deposition_type] - if form_type is None and config_key in deposition_config: - return deposition_config[config_key] - - if form_type in deposition_config: - form_config = deposition_config[form_type] - if field_type is None and config_key in form_config: - return form_config[config_key] - - if field_type in form_config['fields']: - field_config = form_config['fields'][field_type] - if config_key in field_config: - if config_key in field_config: - return field_config[config_key] - - return None - - - def get_form_title(self, form_type=None): - """ Returns the title of the form - - @param form_type: the type of the form. - to use this function it must be defined here - or at the class construction - """ - - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - - title = self._parse_config('title', - deposition_type=deposition_type, - form_type=form_type) - if title is not None: - return _(title) - else: - return None - - def get_label(self, field_type=None): - """ Returns the label of the field - - @param field_type: the type of the field. - to use this function it must be defined here - or at the class construction - """ - - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - field_type = field_type or self.get_field_type() - - label = self._parse_config('label', - deposition_type=deposition_type, - form_type=form_type, - field_type=field_type) - - if label is None: - return None - else: - return _(label) - - def get_widget(self, field_type=None): - """ Returns the widget of the field - - @param field_type: the type of the field. - to use this function it must be defined here - or at the class construction - """ - - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - field_type = field_type or self.get_field_type() - - widget = self._parse_config('widget', - deposition_type=deposition_type, - form_type=form_type, - field_type=field_type) - - if widget is None: - return None - else: - return import_string(widget) - - def get_autocomplete_function(self): - """ Returns an autocomplete function of the field - """ - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - field_type = self.get_field_type() - - autocomplete = self._parse_config('autocomplete', - deposition_type=deposition_type, - form_type=form_type, - field_type=field_type) - - if autocomplete is None: - return None - else: - return import_string(autocomplete) - - def get_validators(self, field_type=None): - """ Returns validators function based of the field - """ - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - field_type = field_type or self.get_field_type() - - vals = self._parse_config('validators', - deposition_type=deposition_type, - form_type=form_type, - field_type=field_type) - - validators = [] - if vals is None: - return [] - else: - for validator in vals: - validators.append(import_string(validator)) - - return validators - - def get_recjson_key(self, field_type=None): - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() - field_type = field_type or self.get_field_type() - - return self._parse_config('recjson_key', - deposition_type=deposition_type, - form_type=form_type, - field_type=field_type) - - def get_cook_json_function(self, field_type=None): - recjson_key = self.get_recjson_key(field_type) - if recjson_key is not None: - return cook_to_recjson(recjson_key) - - - def get_template(self, form_type=None): - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() or form_type - - template = self._parse_config('template', - deposition_type=deposition_type, - form_type=form_type) - - if template is None: - return None - else: - return template - - def get_files_cook_function(self, form_type=None): - deposition_type = self.get_deposition_type() - form_type = self.get_form_type() or form_type - - file_cook = self._parse_config('file_cook', - deposition_type=deposition_type, - form_type=form_type) - - if file_cook is None: - return None - else: - return import_string(file_cook) - - def get_collection(self, deposition_type=None): - deposition_type = deposition_type or self.get_deposition_type() - - return self._parse_config('collection', - deposition_type=deposition_type) diff --git a/modules/webdeposit/lib/webdeposit_cook_json_utils.py b/modules/webdeposit/lib/webdeposit_cook_json_utils.py index 1c34d3a5b..89f4f94db 100644 --- a/modules/webdeposit/lib/webdeposit_cook_json_utils.py +++ b/modules/webdeposit/lib/webdeposit_cook_json_utils.py @@ -1,101 +1,107 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Cook Json Functions Functions to be used for transforming (back and forth) a webdeposit json (json representing a form) to rec json format using BibField's JsonReader """ def cook_to_recjson(key): def cook(json_reader, value): json_reader[key] = value return json_reader return cook +def add_author(json_reader, value): + if 'authors' in json_reader: + json_reader['authors'].append({'full_name': value}) + else: + json_reader['authors[0].full_name'] = value + """ Cooks for the deposition files """ from invenio.bibdocfile import BibRecDocs def cook_files(json_reader, file_list): """ @param file_json: list (as created in blueprints) containing dictionaries with files and their metadata """ for file_json in file_list: filename = file_json['name'] path = file_json['file'] json_reader['fft[n]'] = {'path': path, 'new_name': filename} return json_reader def uncook_files(webdeposit_json, recid=None, json_reader=None): if 'files' not in webdeposit_json: webdeposit_json['files'] = [] if recid is None: for f in json_reader['url']: filename = f['url'].split('/')[-1] file_json = { 'name': filename } webdeposit_json['files'].append(file_json) else: for f in BibRecDocs(recid, human_readable=True).list_latest_files(): filename = f.get_full_name() path = f.get_path() size = f.get_size() file_json = { 'name': filename, 'file': path, 'size': size } webdeposit_json['files'].append(file_json) return webdeposit_json def cook_icon(json_reader, icon_path): """ helper for adding an icon to a deposition e.g. Photo deposition """ try: json_reader['fft[-1]']['icon_path'] = icon_path except IndexError: pass return json_reader def cook_picture(json_reader, file_list): """ Same as file cook with an additional icon cook It is assumed that its used for depositing one picture """ if len(file_list) == 1: json_reader = cook_files(json_reader, file_list) json_reader = cook_icon(json_reader, file_list[0]['file']) return json_reader diff --git a/modules/webdeposit/lib/webdeposit_field.py b/modules/webdeposit/lib/webdeposit_field.py index d53188ce0..be78fb067 100644 --- a/modules/webdeposit/lib/webdeposit_field.py +++ b/modules/webdeposit/lib/webdeposit_field.py @@ -1,155 +1,265 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -from wtforms.validators import Required -from invenio.webdeposit_config_utils import WebDepositConfiguration + +""" +Validators +---------- +Following is a short overview over how validators may be defined for fields. + +Inline validators (always executed):: + + class MyForm(...): + myfield = MyField() + + def validate_myfield(form, field): + raise ValidationError("Message") + + +External validators (always executed):: + + def my_validator(form, field): + raise ValidationError("Message") + + class MyForm(...): + myfield = MyField(validators=[my_validator]) + + +Field defined validators (always executed):: + + class MyField(...): + # ... + def pre_validate(self, form): + raise ValidationError("Message") + +Default field validators (executed only if external validators are not defined):: + + class MyField(...): + def __init__(self, **kwargs): + defaults = dict(validators=[my_validator]) + defaults.update(kwargs) + super(MyField, self).__init__(**defaults) + + +See http://wtforms.simplecodes.com/docs/1.0.4/validators.html for how to +write validators. + +Post-processors +--------------- +Post processors follows the same pattern as validators. You may thus specify:: + + * Inline processors: Form.post_process_<field>(form, field) + * External processors: def my_processor(form, field) ... myfield = MyField(processors=[my_processor]) + * Field defined processors (please method documentation): Field.post_process(self, form, extra_processors=[]) + +Auto-complete +------------- + * External auto-completion function: def my_autocomplete(form, field, limit=50) ... myfield = MyField(autocomplete=my_autocomplete) + * Field defined auto-completion function (please method documentation): Field.autocomplete(self, form, limit=50) + +Rec JSON key +------------ + +* External defined: myfield = MyField(recjson_key='...') +* Default field defined:: + + class MyField(...): + def __init__(self, **kwargs): + defaults = {'recjson_key': '...'} + defaults.update(kwargs) + super(MyField, self).__init__(**defaults) +""" + +from invenio.webdeposit_form import CFG_FIELD_FLAGS +from invenio.webdeposit_cook_json_utils import cook_to_recjson __all__ = ['WebDepositField'] -def WebDepositField(key=None): - class WebDepositFieldClass(object): +class WebDepositField(object): + """ + Base field that all webdeposit fields must inherit from. + """ + + def __init__(self, **kwargs): """ - Class that all webdeposit fields must inherit. + Initialize WebDeposit field. + + Every field is associated with a marc field. To define this association you + have to specify the `recjson_key` for the bibfield's `JsonReader` or + the `cook_function` (for more complicated fields). - A helper to add attributes and methods to every webdeposit field. + @param placeholder: str, Placeholder text for input fields. + @param icon: Name of icon (rendering of the icon is done by templates) + @type icon: str + @param autocomplete: callable, A function to auto-complete values for field. + @param processors: list of callables, List of processors to run for field. + @param validators: list of callables, List of WTForm validators. If no validators are provided, validators defined in webdeposit_config will be loaded. + @param hidden: Set to true to hide field. Default: False + @type hidden: bool + @param disabled: Set to true to disable field. Default: False + @type disabled: bool + @param recjson_key: Name of recjson key + @type recjson_key: str + @param cook_function: the cook function + @type cook_function: function + + @see http://wtforms.simplecodes.com/docs/1.0.4/validators.html for + how to write validators. + @see http://wtforms.simplecodes.com/docs/1.0.4/fields.html for further + keyword argument that can be provided on field initialization. """ + # Pop WebDeposit specific kwargs before calling super() + self.placeholder = kwargs.pop('placeholder', None) + self.group = kwargs.pop('group', None) + self.icon = kwargs.pop('icon', None) + self.autocomplete = kwargs.pop('autocomplete', None) + self.processors = kwargs.pop('processors', None) + self.recjson_key = kwargs.pop('recjson_key', None) + self.cook_function = kwargs.pop('cook_function', None) - def __init__(self, **kwargs): - # Create our own Required data member - # for client-side use - if 'validators' in kwargs: - for v in kwargs.get("validators"): - if type(v) is Required: - self.required = True - if 'group' in kwargs: - self.group = kwargs.pop('group') - else: - self.group = None - - super(WebDepositFieldClass, self).__init__(**kwargs) - self.config = WebDepositConfiguration(field_type=self.__class__.__name__) - self.recjson_key = self.config.get_recjson_key() or key - - def merge_validation_json(self, json1, json2): - """ Merges 2 jsons returned from 2 validation functions - - @param json1: the first json - @param json2: the second json - @returns: a dictionary with info, success and error messages, - fields to be hidden/shown/disabled/enabled, - and the dictionary with fields to be updated merged. - Be carefull with jsons that update the same field! - """ - - json = {} - - # Merge the messages of 2 dicts if they exist - def merge_msg(msg_exists, key, json1, json2): - if (json1.get(msg_exists) == 1 or json2.get(msg_exists) == 1): - msg1 = (json1.get(key) or '') - msg2 = (json2.get(key) or '') - if msg1 == '': - msg = msg2 - elif msg2 == '': - msg == msg1 - else: - msg = msg1 + '<br>' + msg2 - return 1, msg - else: - return 0, '' - - json['success'], json['success_message'] = \ - merge_msg('success', 'success_message', json1, json2) - - json['info'], json['info_message'] = \ - merge_msg('info', 'info_message', json1, json2) - - json['error'], json['error_message'] = \ - merge_msg('error', 'error_message', json1, json2) - - - if 'fields' in json1 and 'fields' in json2: - """ Be carefull when the two validators change the - value of the same field. - """ - json['fields'] = dict(json1['fields'].items() + - json2['fields'].items()) - elif 'fields' in json1: - json['fields'] = json1['fields'] - elif 'fields' in json2: - json['fields'] = json2['fields'] - - # Create the union of the lists that specify UI actions on fields - concat_values = lambda key, json1, json2: \ - [i for i in json1.get(key) or []] + \ - [i for i in json2.get(key) or []] - - json['hidden_fields'] = concat_values('hidden_fields', - json1, json2) - - json['visible_fields'] = concat_values('visible_fields', - json1, json2) - - json['enabled_fields'] = concat_values('enabled_fields', - json1, json2) - - json['disabled_fields'] = concat_values('disabled_fields', - json1, json2) - return json - - def has_recjson_key(self): - return self.recjson_key is not None - - def cook_json(self, json_reader): - """ - Fills a json_reader object with the field's value - based on the recjson key - - @param json_reader: BibField's JsonReader object - """ - cook_json_function = self.config.get_cook_json_function() - if cook_json_function is not None: - return cook_json_function(json_reader, self.data) - elif key is not None: # Default behaviour - json_reader[key] = self.data - - return json_reader - - def uncook_json(self, json_reader, webdeposit_json): - """ - The opposite of `cook_json` (duh) - Adds to the webdeposit_json the appropriate value - from the json_reader based on the recjson key - - You have to retrieve the record with BibField and - instantiate a json_reader object before starting - the uncooking - - @param json_reader: BibField's JsonReader object - @param webdeposit_json: a dictionary - @return the updated webdeposit_json - """ - - if self.has_recjson_key() and \ - self.recjson_key in json_reader: - webdeposit_json[self.name] = json_reader[self.recjson_key] - return webdeposit_json - - return WebDepositFieldClass + # Initialize empty message variables, which are usually modified + # during the post-processing phases. + self.messages = [] + self.message_state = '' + + # Get flag values (e.g. hidden, disabled) before super() call. + # See CFG_FIELD_FLAGS for all defined flags. + flag_values = {} + for flag in CFG_FIELD_FLAGS: + flag_values[flag] = kwargs.pop(flag, False) + + # Call super-constructor. + super(WebDepositField, self).__init__(**kwargs) + + # Set flag values after super() call to ensure, flags set during + # super() are overwritten. + for flag, value in flag_values.items(): + if value: + setattr(self.flags, flag, True) + + def get_recjson_key(self): + return self.recjson_key + + def has_cook_function(self): + return self.cook_function is not None + + def has_recjson_key(self): + return self.recjson_key is not None + + def cook_json(self, json_reader): + """ + Fills a json_reader object with the field's value + based on the recjson key + + @param json_reader: BibField's JsonReader object + """ + cook = None + if self.has_recjson_key(): + cook = cook_to_recjson(self.get_recjson_key()) + elif self.has_cook_function(): + cook = self.cook_function + + if cook is not None: + return cook(json_reader, self.data) + + return json_reader + + def uncook_json(self, json_reader, webdeposit_json): + """ + The opposite of `cook_json` (duh) + Adds to the webdeposit_json the appropriate value + from the json_reader based on the recjson key + + You have to retrieve the record with BibField and + instantiate a json_reader object before starting + the uncooking + + @param json_reader: BibField's JsonReader object + @param webdeposit_json: a dictionary + @return the updated webdeposit_json + """ + + if self.has_recjson_key() and \ + self.recjson_key in json_reader: + webdeposit_json[self.name] = json_reader[self.recjson_key] + return webdeposit_json + + def __call__(self, *args, **kwargs): + """ + Set custom keyword arguments when rendering field + """ + if 'placeholder' not in kwargs and self.placeholder: + kwargs['placeholder'] = self.placeholder + if 'disabled' not in kwargs and self.flags.disabled: + kwargs['disabled'] = "disabled" + return super(WebDepositField, self).__call__(*args, **kwargs) + + def post_process(self, form, extra_processors=[], submit=False): + """ + Post process form before saving. + + Usually you can do some of the following tasks in the post + processing: + + * Set field flags (e.g. self.flags.hidden = True or + form.<field>.flags.hidden = True). + * Set messages (e.g. self.messages.append('text') and + self.message_state = 'info'). + * Set values of other fields (e.g. form.<field>.data = ''). + + Processors may stop the processing chain by raising StopIteration. + + IMPORTANT: By default the method will execute custom post processors + defined in the webdeposit_config. If you override the method, be + sure to call this method to ensure extra processors are called:: + + super(MyField, self).post_process(form, extra_processors=extra_processors) + """ + # Run post-processors (either defined) + stop = False + for p in (self.processors or []): + try: + p(form, self, submit) + except StopIteration: + stop = True + break + + if not stop: + for p in (extra_processors or []): + p(form, self, submit) + + def perform_autocomplete(self, form, term, limit=50): + """ + Run auto-complete method for field. Use Form.autocomplete() to + perform auto-completion for a field, since it will take care of + preparing the field with data. + """ + if self.autocomplete: + return self.autocomplete(form, term, limit=limit) + return [] + + def add_message(self, state, message): + """ + Adds a message to display for the field. + The state can be info, error or success. + """ + assert state in ['info', 'error', 'success'] + self.message_state = state + self.messages.append(message) diff --git a/modules/webdeposit/lib/webdeposit_field_widgets.py b/modules/webdeposit/lib/webdeposit_field_widgets.py index 80d738764..f66170b1f 100644 --- a/modules/webdeposit/lib/webdeposit_field_widgets.py +++ b/modules/webdeposit/lib/webdeposit_field_widgets.py @@ -1,119 +1,135 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from wtforms.widgets import html_params, HTMLString - +from invenio.jinja2utils import render_template_to_string def date_widget(field, **kwargs): field_id = kwargs.pop('id', field.id) - html = [u'<input class="datepicker" %s value="" type="text">' - % html_params(id=field_id, name=field_id)] + html = [u'<input class="datepicker" %s type="text">' + % html_params(id=field_id, name=field_id, value=field.data or '')] field_class = kwargs.pop('class', '') or kwargs.pop('class_', '') kwargs['class'] = u'datepicker %s' % field_class kwargs['class'] = u'date %s' % field_class return HTMLString(u''.join(html)) def plupload_widget(field, **kwargs): field_id = kwargs.pop('id', field.id) - # FIXME: Move html code in a template and initialize html variable - # with the render_template_to_string function - html = [u' \ - <div class="pluploader" %s > \ - <div class="well" id="filebox">\ - <div id="drag_and_drop_text" style="text-align:center;z-index:-100;">\ - <h1><small>Drag and Drop files here</small></h1>\ - </div>\ - </div> \ - <table id="file-table" class="table table-striped table-bordered" style="display:none;">\ - <thead>\ - <tr>\ - <th>Filename</th>\ - <th>Size</th>\ - <th>Status</th>\ - <td></td>\ - </tr>\ - </thead>\ - <tbody id="filelist">\ - </tbody>\ - </table>\ - <a class="btn btn-primary" id="pickfiles" >Select files</a> \ - <a class="btn btn-success disabled" id="uploadfiles"><i class="icon-upload icon-white"></i> Start upload</a>\ - <a class="btn btn-danger" id="stopupload" style="display:none;"><i class="icon-stop icon-white"></i> Stop upload</a>\ - <div id="upload-errors"></div>\ - </div>' % html_params(id=field_id)] kwargs['class'] = u'plupload' - return HTMLString(u''.join(html)) + + return HTMLString( + render_template_to_string( + "webdeposit_widget_plupload.html", + field=field, + field_id=field_id, + ) + ) def bootstrap_submit(field, **kwargs): html = u'<input %s >' % html_params(style="float:right; width: 250px;", id="submitButton", class_="btn btn-primary btn-large", name="submitButton", type="submit", value=field.label.text,) html = [u'<div style="float:right;" >' + html + u'</div>'] return HTMLString(u''.join(html)) def ckeditor_widget(field, **kwargs): field.ckeditor = True field_id = kwargs.pop('id', field.id) html = [u'<textarea %s >' - % html_params(id=field_id, name=field_id)] - if field.data is not None: - html.append('%s</textarea>' % field.data) - else: - html.append('</textarea>') - return HTMLString(u''.join(html)) - - field_id = "ckeditor_" + field_id - html = [u'<textarea %s ></textarea>' - % html_params(id=field_id, name=field_id)] + % html_params(id=field_id, name=field_id, value=field.data or '')] + html.append('%s</textarea>' % field.data or '') return HTMLString(u''.join(html)) def dropbox_widget(field, **kwargs): field_id = kwargs.pop('id', field.id) html = [u'<input type="dropbox-chooser"\ name="fileurl"\ style="visibility: hidden;"\ data-link-type="direct"\ id="db-chooser"/></br> \ <div class="pluploader" %s > \ <table id="file-table" class="table table-striped table-bordered" style="display:none;">\ <thead>\ <tr>\ <th>Filename</th>\ <th>Size</th>\ <th>Status</th>\ <td></td>\ </tr>\ </thead>\ <tbody id="filelist">\ </tbody>\ </table>\ <a class="btn btn-success disabled" id="uploadfiles"> \ <i class="icon-upload icon-white"></i> Start upload</a>\ <a class="btn btn-danger" id="stopupload" style="display:none;">\ - <i class="icon-stop icon-white"></i> Stop upload</a>\ + <i class="icon-stop icon-white"></i> Cancel upload</a>\ + <span id="upload_speed" class="pull-right"></span>\ <div id="upload-errors"></div>\ </div>' % html_params(id=field_id)] return HTMLString(u''.join(html)) + + +class ButtonWidget(object): + """ + Renders a button. + """ + + def __init__(self, label="", tooltip=None, icon=None, **kwargs): + """ + Note, the icons assume use of Twitter Bootstrap, + Font Awesome or some other icon library, that allows + inserting icons with a <i>-tag. + + @param tooltip: str, Tooltip text for the button. + @param icon: str, Name of an icon, e.g. icon-barcode. + """ + self.icon = icon + self.label = label + self.default_params = kwargs + self.default_params.setdefault('type', 'button') + if tooltip: + self.default_params.setdefault('data-toggle', 'tooltip') + self.default_params.setdefault('title', tooltip) + super(ButtonWidget, self).__init__() + + def __call__(self, field, **kwargs): + params = self.default_params.copy() + params.update(kwargs) + params.setdefault('id', field.id) + params['class_'] = params.get('class_',"") + " form-button" + + icon = "" + if self.icon: + icon = '<i class="%s"></i> ' % self.icon + + state = "" + if field._value(): + state = '<span class="text-success"> <i class="icon-ok"></i></span>' + + return HTMLString(u'<button %s>%s%s</button><span %s>%s</span>' % (html_params( + name=field.name, **params), icon, self.label, + html_params(id=field.name+'-loader', class_='loader'), state)) diff --git a/modules/webdeposit/lib/webdeposit_filter_utils.py b/modules/webdeposit/lib/webdeposit_filter_utils.py new file mode 100644 index 000000000..8b5a2fd80 --- /dev/null +++ b/modules/webdeposit/lib/webdeposit_filter_utils.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Invenio. +# Copyright (C) 2013 CERN. +# +# Invenio is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# Invenio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Invenio; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + +""" +WTForm filters +-------------- +Filters can be applied to incoming form data, after process_formdata() has run. + +See more information on: +http://wtforms.simplecodes.com/docs/1.0.4/fields.html#wtforms.fields.Field.__init__ +""" + + +def strip_string(value): + """ + Remove leading and trailing spaces from string + """ + if isinstance(value, basestring): + return value.strip() + else: + return value + + +def splitlines_list(value): + """ + Split string per line into a list + """ + if isinstance(value, basestring): + newdata = [] + for line in value.splitlines(): + if line.strip(): + newdata.append(line.strip().encode('utf8')) + return newdata + else: + return value + + +def splitchar_list(c): + """ + Return filter function that split string per char into a list. + + @param c: Character to split on. + """ + def _inner(value): + """ + Split string per char into a list + """ + if isinstance(value, basestring): + newdata = [] + for item in value.split(c): + if item.strip(): + newdata.append(item.strip().encode('utf8')) + return newdata + else: + return value + return _inner + + +def map_func(func): + """ + Return filter function that map a function to each item of a list + + @param func: Function to map. + """ + # FIXME + def _mapper(data): + """ + Map a function to each item of a list + """ + if isinstance(data, list): + return map(func, data) + else: + return data + return _mapper + + +def strip_prefixes(*prefixes): + """ + Return a filter function that removes leading prefixes from a string + """ + def _inner(value): + """ + Remove a leading prefix from string + """ + if isinstance(value, basestring): + for prefix in prefixes: + if value.lower().startswith(prefix): + return value[len(prefix):] + return value + return _inner \ No newline at end of file diff --git a/modules/webdeposit/lib/webdeposit_form.js b/modules/webdeposit/lib/webdeposit_form.js index 972563d77..314e0ab62 100644 --- a/modules/webdeposit/lib/webdeposit_form.js +++ b/modules/webdeposit/lib/webdeposit_form.js @@ -1,463 +1,682 @@ /* * This file is part of Invenio. * Copyright (C) 2013 CERN. * * Invenio is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * Invenio is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Invenio; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ +/* Helpers */ + +function unique_id() { + return Math.round(new Date().getTime() + (Math.random() * 100)); +} + /* - * Plupload + * Get settings for performing an AJAX request with $.ajax + * that will POST a JSON object to the given URL + * + * @param settings: A hash with the keys: url, data. */ +function json_options(settings){ + // Perform AJAX request with JSON data. + return { + url: settings['url'], + type: 'POST', + cache: false, + data: JSON.stringify(settings['data']), + contentType: "application/json; charset=utf-8", + dataType: 'json' + }; +} -function unique_ID() { - return Math.round(new Date().getTime() + (Math.random() * 100)); +/* + * Serialize a form into JSON + */ +function serialize_object(selector){ + var o = {}; + var a = $(selector).serializeArray(); + $.each(a, function() { + if (o[this.name] !== undefined) { + if (!o[this.name].push) { + o[this.name] = [o[this.name]]; + } + o[this.name].push(this.value || ''); + } else { + o[this.name] = this.value || ''; + } + }); + return o; } +function getBytesWithUnit(bytes){ + if( isNaN( bytes ) ){ + return ''; + } + var units = [' bytes', ' KB', ' MB', ' GB']; + var amountOf2s = Math.floor( Math.log( +bytes )/Math.log(2) ); + if( amountOf2s < 1 ){ + amountOf2s = 0; + } + var i = Math.floor( amountOf2s / 10 ); + bytes = +bytes / Math.pow( 2, 10*i ); + + // Rounds to 2 decimals places. + bytes_to_fixed = bytes.toFixed(2); + if( bytes.toString().length > bytes_to_fixed.toString().length ){ + bytes = bytes_to_fixed; + } + return bytes + units[i]; +} + +/* + * Initialize PLUpload + */ function webdeposit_init_plupload(selector, url, delete_url, get_file_url, db_files, dropbox_url) { uploader = new plupload.Uploader({ // General settings runtimes : 'html5', url : url, max_file_size : '460mb', chunk_size : '1mb', //unique_names : true, browse_button : 'pickfiles', - drop_element : 'filebox' + drop_element : 'field-plupload_file' // Specify what files to browse for //filters : [ // {title : "Image files", extensions : "jpg,gif,png,tif"}, // {title : "Compressed files", extensions : "rar,zip,tar,gz"}, // {title : "PDF files", extensions : "pdf"} //] }); + queue_progress = new plupload.QueueProgress(); + uploader.init(); $(function() { if (!jQuery.isEmptyObject(db_files)) { $('#file-table').show('slow'); $.each(db_files, function(i, file) { // Simulate a plupload file object - id = unique_ID(); + id = unique_id(); var plfile = new plupload.File({ id: id, name: file.name, size: file.size }); // Dont touch it! // For some reason the constructor doesn't initialize // the data members plfile.id = id; plfile.name = file.name; plfile.size = file.size; - plfile.loaded = file.size; - plfile.status = 5; + // loaded is set to 0 as a temporary fix plupload's bug in + // calculating current upload speed. For checking if a file + // has been uploaded, check file.status + plfile.loaded = 0; //file.size; + plfile.status = 5; //status = plupload.DONE plfile.percent = 100; plfile.unique_filename = file.unique_filename; /////// uploader.files.push(plfile); $('#filelist').append( '<tr id="' + plfile.id + '" style="display:none;">' + '<td><a href="' + get_file_url + "?filename=" + plfile.unique_filename + '">' + plfile.name + '</a></td>' + - '<td>' + plupload.formatSize(plfile.size) + '</td>' + + '<td>' + getBytesWithUnit(plfile.size) + '</td>' + '<td width="30%"><div class="progress active"><div class="bar" style="width: 100%;"></div></div></td>' + '<td><a id="' + plfile.id + '_rm" class="rmlink"><i class="icon-trash"></i></a></td>' + '</tr>'); $('#filelist #' + plfile.id).show('fast'); $("#" + plfile.id + "_rm").on("click", function(event) { uploader.removeFile(plfile); }); }); } }); $('#uploadfiles').click(function(e) { - uploader.start(); - $('#uploadfiles').hide(); + $('#uploadfiles').addClass('disabled'); $('#stopupload').show(); + uploader.start(); e.preventDefault(); $.each(dropbox_files, function(i, file){ $.ajax({ type: 'POST', url: dropbox_url, data: $.param({ name: file.name, size: file.size, url: file.url }) }).done(function(data){ $('#' + file.id + " .progress").removeClass("progress-striped"); $('#' + file.id + " .bar").css('width', "100%"); $('#' + file.id + '_link').html('<a href="' + get_file_url + "?filename=" + data + '">' + file.name + '</a>'); }); }); dropbox_files = []; - $('#uploadfiles').addClass('disabled'); - $('#stopupload').hide(); - $('#uploadfiles').show(); }); $('#stopupload').click(function(d){ uploader.stop(); $('#stopupload').hide(); - $('#uploadfiles').show(); + $('#uploadfiles').removeClass('disabled'); $.each(uploader.files, function(i, file) { if (file.loaded < file.size) { $("#" + file.id + "_rm").show(); - $('#' + file.id + " .bar").css('width', "0%"); + //$('#' + file.id + " .bar").css('width', "0%"); } }); + $('#upload_speed').html(''); + uploader.total.reset(); }); uploader.bind('FilesRemoved', function(up, files) { $.each(files, function(i, file) { $('#filelist #' + file.id).hide('fast'); - if (file.loaded === file.size) { + if (file.status === plupload.DONE) { //If file has been successfully uploaded $.ajax({ type: "POST", url: delete_url, data: $.param({ filename: file.unique_filename }) }); } }); if(uploader.files.length === 0) { $('#uploadfiles').addClass("disabled"); $('#file-table').hide('slow'); } }); uploader.bind('UploadProgress', function(up, file) { $('#' + file.id + " .bar").css('width', file.percent + "%"); + upload_speed = getBytesWithUnit(up.total.bytesPerSec) + " per sec"; console.log("Progress " + file.name + " - " + file.percent); + $('#upload_speed').html(upload_speed); + up.total.reset(); }); + + uploader.bind('UploadFile', function(up, file) { $('#' + file.id + "_rm").hide(); }); uploader.bind('FilesAdded', function(up, files) { $('#uploadfiles').removeClass("disabled"); $('#file-table').show('slow'); + up.total.reset(); $.each(files, function(i, file) { $('#filelist').append( '<tr id="' + file.id + '" style="display:none;z-index:-100;">' + '<td id="' + file.id + '_link">' + file.name + '</td>' + - '<td>' + plupload.formatSize(file.size) + '</td>' + - '<td width="30%"><div class="progress progress-stri´ped active"><div class="bar" style="width: 0%;"></div></div></td>' + + '<td>' + getBytesWithUnit(file.size) + '</td>' + + '<td width="30%"><div class="progress progress-striped active"><div class="bar" style="width: 0%;"></div></div></td>' + '<td><a id="' + file.id + '_rm" class="rmlink"><i class="icon-trash"></i></a></td>' + '</tr>'); $('#filelist #' + file.id).show('fast'); $('#' + file.id + '_rm').on("click", function(event){ uploader.removeFile(file); }); }); }); uploader.bind('FileUploaded', function(up, file, responseObj) { console.log("Done " + file.name); $('#' + file.id + " .progress").removeClass("progress-striped"); $('#' + file.id + " .bar").css('width', "100%"); $('#' + file.id + '_rm').show(); $('#' + file.id + '_link').html('<a href="' + get_file_url + "?filename=" + responseObj.response + '">' + file.name + '</a>'); file.unique_filename = responseObj.response; if (uploader.total.queued === 0) $('#stopupload').hide(); + file.loaded = 0; + $('#upload_speed').html(''); $('#uploadfiles').addClass('disabled'); $('#uploadfiles').show(); + up.total.reset(); + }); + $("#filelist").sortable(); + $("#filelist").disableSelection(); +} + +/* + * Initialize save-button + */ +function webdeposit_init_save(url, selector, form_selector) { + $(selector).click(function(e){ + // Stop propagation of event to prevent form submission + e.preventDefault(); + + webdeposit_set_status(tpl_webdeposit_status_saving, {name: null, value: null}); + + $.ajax( + json_options({url: url, data: serialize_object(form_selector)}) + ).done(function(data) { + var errors = false; + // FIXME- get errors from response + webdeposit_handle_response(data); + webdeposit_set_status(tpl_webdeposit_status_saved, {name: name, value: null}); + if(errors) { + webdeposit_set_status(tpl_webdeposit_status_saved_with_errors, {name: name, value: null}); + webdeposit_flash_message({state:'warning', message: tpl_message_errors.render({})}); + } else { + webdeposit_set_status(tpl_webdeposit_status_saved, {name: name, value: value}); + webdeposit_flash_message({state:'success', message: tpl_message_success.render({})}); + } + }).fail(function() { + webdeposit_flash_message({state:'error', message: tpl_message_server_error.render({})}); + check_empty_fields(name); + webdeposit_set_status(tpl_webdeposit_status_error, {name: name, value: value}); + }); + + return false; }); +} + +/* + * Initialize submit-button + */ +function webdeposit_init_submit(url, selector, form_selector) { + $(selector).click(function(e){ + e.preventDefault(); + // webdeposit_set_status(tpl_webdeposit_status_saving, {}); + // //emptyForm = checkEmptyFields(null); + // if (emptyForm[0] == 0){ + // $('#empty-fields-error').hide('slow'); + // webdeposit_set_status(tpl_webdeposit_status_saved, {}); + // } + // else { + // $('#empty-fields-error').html("<a class='close' data-dismiss='alert' href='#'>×</a>These fields are required:<ul>" + emptyForm[1] + "</ul>" ); + // $('#empty-fields-error').show('slow'); + // webdeposit_set_status(tpl_webdeposit_status_saved_with_errors, {}); + // } + }); } /* Error checking */ var errors = 0; var oldJournal; -function webdeposit_handle_field_data(name, value, data, url, required_fields) { - // handles a response from the server for the field - if (data.error == 1) { - errorMsg = data.error_message; - $('#error-' + name).html(errorMsg); - $('.error-list-' + name).hide('slow'); - $('#error-' + name).show('slow'); - $("#error-group-" + name).addClass('error'); - errors++; - } else { - $('#error-' + name).hide('slow'); - $('.error-list-' + name).hide('slow'); - $("#error-group-" + name).removeClass('error'); - if (errors > 0) - errors--; - emptyForm = checkEmptyFields(false, name, required_fields); - if (emptyForm[0] === 0) { - $('#empty-fields-error').hide('slow'); - } - else { - $('#empty-fields-error').html("These fields are required!</br>" + emptyForm[1]); - $('#empty-fields-error').show(); - } - } - - dismiss = '<button type="button" class="close" data-dismiss="alert">×</button>'; - - if (data.success == 1) { - success = '<div class="alert alert-success help-inline" id="success-' + name + '" style="display:none;">' + - dismiss + data.success_message + - '</div>'; - $('#success-' + name).remove(); - $('#field-' + name).append(success); - $('#success-' + name).show('slow'); - } - else { - $('#success-' + name).remove(); +/* + * Handle update of field message box. + * + * @return: True if message was set, False if no message was set. + */ +function webdeposit_handle_field_msg(name, data) { + if(!data) { + return false; } - if (data.info == 1) { - info = '<div class="alert alert-info help-inline" id="info-' + name + '" style="display:none;">' + - dismiss + data.info_message + - '</div>'; - $('#info-' + name).remove(); - $('#field-' + name).append(info); - $('#info-' + name).css('margin-top', '10px'); - $('#info-' + name).css('clear', 'both'); - $('#info-' + name).css('float', 'left'); - $('#info-' + name).show('slow'); - } - else { - $('#info-' + name).remove(); + state = ''; + if(data.state) { + state = data.state; } - if (data.fields) { - $.each(data.fields, function(name, value) { - if (name == 'files'){ - $.each(value, function(i, file){ - id = unique_ID(); - - new_file = { - id: id, - name: file.name, - size: file.size - }; - - $('#filelist').append( - '<tr id="' + id + '" style="display:none;">' + - '<td id="' + id + '_link">' + file.name + '</td>' + - '<td>' + plupload.formatSize(file.size) + '</td>' + - '<td width="30%"><div class="progress active"><div class="bar" style="width: 100%;"></div></div></td>' + - '</tr>'); - $('#filelist #' + id).show('fast'); - }); - $('#file-table').show('slow'); - } - else { - $('#error-' + name).hide('slow'); - errors--; - old_value = $('[name=' + name + ']').val(); - if (old_value != value) { - if (typeof ckeditor === 'undefined') - $('[name=' + name + ']').val(value); - else if (ckeditor.name == name) - ckeditor.setData(value); - webdeposit_handle_new_value(name, value, url, required_fields); - } + if(data.messages && data.messages.length !== 0) { + $('#state-' + name).html( + tpl_field_message.render({ + name: name, + state: state, + messages: data.messages + }) + ); + + ['info','warning','error','success'].map(function(s){ + $("#state-group-" + name).removeClass(s); + $("#state-" + name).removeClass('alert-'+s); + if(s == state) { + $("#state-group-" + name).addClass(state); + $("#state-" + name).addClass('alert-'+state); } }); + + $('#state-' + name).show('fast'); + return true; + } else { + webdeposit_clear_error(name); + return false; } +} - if (data.disabled_fields) { - $.each(data.disabled_fields, function(i, field){ - $('#'+field).attr('disabled','disabled'); +function webdeposit_clear_error(name){ + $('#state-' + name).hide(); + $('#state-' + name).html(""); + ['info','warning','error','success'].map(function(s){ + $("#state-group-" + name).removeClass(s); + $("#state-" + name).removeClass('alert-'+s); + }); +} + +function webdeposit_handle_field_values(name, value) { + if (name == 'files'){ + $.each(value, function(i, file){ + id = unique_id(); + + new_file = { + id: id, + name: file.name, + size: file.size + }; + + $('#filelist').append( + '<tr id="' + id + '" style="display:none;">' + + '<td id="' + id + '_link">' + file.name + '</td>' + + '<td>' + getBytesWithUnit(file.size) + '</td>' + + '<td width="30%"><div class="progress active"><div class="bar" style="width: 100%;"></div></div></td>' + + '</tr>'); + $('#filelist #' + id).show('fast'); }); + $('#file-table').show('slow'); + } else { + webdeposit_clear_error(name); + errors--; + old_value = $('[name=' + name + ']').val(); + if (old_value != value) { + if (typeof ckeditor === 'undefined') + $('[name=' + name + ']').val(value); + else if (ckeditor.name == name) + ckeditor.setData(value); + //webdeposit_handle_new_value(name, value, url); + } } +} - if (data.enabled_fields) { - $.each(data.enabled_fields, function(i, field){ - $('#'+field).removeiAttr('disabled'); +/* + * Handle server response for multiple fields. + */ +function webdeposit_handle_response(data) { + if('messages' in data) { + $.each(data['messages'], webdeposit_handle_field_msg); + } + if('values' in data) { + $.each(data['values'], webdeposit_handle_field_values); + } + if('hidden_on' in data) { + $.each(data['hidden_on'], function(idx, field){ + $('#state-group-'+field).hide("slow"); }); } - - if (data.hidden_fields) { - $.each(data.hidden_fields, function(i, field){ - $('#error-group-'+field).hide(); + if('hidden_off' in data) { + $.each(data['hidden_off'], function(idx, field){ + $('#state-group-'+field).show("slow"); }); } - - if (data.visible_fields) { - $.each(data.visible_fields, function(i, field){ - $('#error-group-'+field).show(); + if('disabled_on' in data) { + $.each(data['disabled_on'], function(idx, field){ + $('#'+field).attr('disabled','disabled'); + }); + } + if('disabled_off' in data) { + $.each(data['disabled_off'], function(idx, field){ + $('#'+field).removeAttr('disabled'); }); } +} - } +/* + * Set value of status indicator in form (e.g. saving, saved, ...) + */ +function webdeposit_set_status(tpl, ctx) { + $('.status-indicator').show(); + $('.status-indicator').html(tpl.render(ctx)); +} + +function webdeposit_set_loader(selector, tpl, ctx) { + $(selector).show(); + $(selector).html(tpl.render(ctx)); +} + +/* + * Flash a message in the top. + */ +function webdeposit_flash_message(ctx) { + $('#flash-message').html(tpl_flash_message.render(ctx)); + $('#flash-message').show(); +} -function webdeposit_handle_new_value(name, value, url, required_fields) { +function webdeposit_handle_new_value(name, value, url) { // sends an ajax request with the data $.getJSON(url, { name: name, attribute: value }, function(data){ - webdeposit_handle_field_data(name, value, data, url, required_fields); - $('#status-indicator').html("Saved!"); + webdeposit_handle_field_data(name, value, data, url); + webdeposit_set_status(tpl_webdeposit_status_saved, {name: name, value: value}); }); } -function webdeposit_input_error_check(selector, url, required_fields) { - $(selector).change( function() { + +/* + * Save and check field values for errors. + */ +function webdeposit_input_error_check(selector, url) { + $(selector).change( function() { name = this.name; value = this.value; - $('#status-indicator').html("Saving " + $("label[for="+this.name+"]").html() + "..."); - $.getJSON(url, { - name: name, - attribute: value - }, function(data){ - webdeposit_handle_field_data(name, value, data, url, required_fields); - $('#status-indicator').html("Saved!"); + + webdeposit_set_status(tpl_webdeposit_status_saving, {name: name, value: value}); + + request_data = {}; + request_data[name] = value; + + $.ajax( + json_options({url: url, data: request_data}) + ).done(function(data) { + webdeposit_handle_response(data); + webdeposit_set_status(tpl_webdeposit_status_saved, {name: name, value: value}); + }).fail(function() { + check_empty_fields(name); + webdeposit_set_status(tpl_webdeposit_status_error, {name: name, value: value}); }); - return false; - }); + + return false; + }); +} + +/* + * Click form-button + */ +function webdeposit_button_click(selector, url) { + $(selector).click( function() { + name = this.name; + loader_selector = '#' + name + '-loader'; + + webdeposit_set_loader(loader_selector, tpl_loader, {name: name}); + + request_data = {}; + request_data[name] = true; + + $.ajax( + json_options({url: url, data: request_data}) + ).done(function(data) { + webdeposit_handle_response(data); + webdeposit_set_loader(loader_selector, tpl_loader_success, {name: name}); + }).fail(function() { + webdeposit_set_loader(loader_selector, tpl_loader_failed, {name: name}); + }); + + return false; + }); } + + /* * CKEditor */ -function webdeposit_ckeditor_init(selector, url, required_fields) { +function webdeposit_ckeditor_init(selector, url) { CKEDITOR.replace(selector); ckeditor = CKEDITOR.instances[selector]; ckeditor.on('blur',function(event){ - webdeposit_handle_new_value(selector, ckeditor.getData(), url, required_fields); + webdeposit_handle_new_value(selector, ckeditor.getData(), url); }); } /********************************************************/ +/* + * Check if required field is empty + * + * @param field: Name of field, or null to check all fields. + */ +function check_empty_fields(field) { + check_fields = []; + empty_fields = []; + + if (field && $.inArray(field, required_fields)) { + check_fields = [field]; + } else if (field === undefined) { + check_fields = required_fields; + } + check_fields.map(function(f){ + label = $("label[for='"+f+"']").html() || ''; + value = $('#'+f).val(); -function checkEmptyFields(all_fields, field, required_fields) { - var emptyFields = ""; - var empty = 0; - $(":text, :file, :checkbox, select, textarea").each(function() { - // Run the checks only for fields that are required - if ($.inArray(this.name, required_fields) > -1) { - if(($(this).val() === "") || ($(this).val() === null)) { - emptyFields += "- " + $("label[for='"+this.name+"']").html() + "</br>"; - if ( (all_fields === true) || (field == this.name)) { - $('#error-'+this.name).html($("label[for='"+this.name+"']").html() + " field is required!"); - $('#error-'+this.name).show('slow'); - } - empty = 1; + if(value === "" || value === null) { + webdeposit_handle_field_msg(field, {state: 'error', message: tpl_required_field_message.render({label: label.toString().trim(), value: value})}); + empty_fields.push(f); } else { - $('#error-'+this.name).hide('slow'); + webdeposit_handle_field_msg(field, {state: '', message: ''}); } - } }); - // Return the text only if all fields where requested - if ( (empty == 1) && all_fields) - return [1, emptyFields]; - else - return [0, emptyFields]; + + return empty_fields; } +// function checkEmptyFields(all_fields, field, required_fields) { +// var emptyFields = ""; +// var empty = 0; +// $(":text, :file, :checkbox, select, textarea").each(function() { +// // Run the checks only for fields that are required +// if ($.inArray(this.name, required_fields) > -1) { +// if(($(this).val() === "") || ($(this).val() === null)) { +// emptyFields += "<li>" + $("label[for='"+this.name+"']").html() + "</li>"; +// if ( (all_fields === true) || (field == this.name)) { +// $('#error-'+this.name).html($("label[for='"+this.name+"']").html() + " field is required!"); +// $("#error-group-" + this.name).addClass('error'); +// $('#error-'+this.name).show('slow'); +// } +// empty = 1; +// } else { +// $('#error-'+this.name).hide('slow'); +// } +// } +// }); +// // Return the text only if all fields where requested +// if ( (empty == 1) && all_fields) +// return [1, emptyFields]; +// else +// return [0, emptyFields]; +// } + var autocomplete_request = $.ajax(); function webdeposit_field_autocomplete(selector, url) { var source = function(query) { $(selector).addClass('ui-autocomplete-loading'); var typeahead = this; autocomplete_request.abort(); autocomplete_request = $.ajax({ type: 'GET', url: url, data: $.param({ term: query }) }).done(function(data) { typeahead.process(data.results); $(selector).removeClass('ui-autocomplete-loading'); }).fail(function(data) { typeahead.process([query]); $(selector).removeClass('ui-autocomplete-loading'); }); }; // FIXME: typeahead doesn't support a delay option // so for every change an ajax request is // being sent to the server. $(selector).typeahead({ source: source, minLength: 5, items: 50 }); } function webdeposit_check_status(url){ setInterval(function() { $.ajax({ type: 'GET', url: url }).done(function(data) { if (data.status == 1) location.reload(); }); }, 10000); } var dropbox_files = []; if (document.getElementById("db-chooser") !== null) { document.getElementById("db-chooser").addEventListener("DbxChooserSuccess", function(e) { $('#file-table').show('slow'); $.each(e.files, function(i, file){ - id = unique_ID(); + id = unique_id(); dbfile = { id: id, name: file.name, size: file.bytes, url: file.link }; $('#filelist').append( '<tr id="' + id + '" style="display:none;">' + '<td id="' + id + '_link">' + file.name + '</td>' + - '<td>' + plupload.formatSize(file.bytes) + '</td>' + + '<td>' + getBytesWithUnit(file.bytes) + '</td>' + '<td width="30%"><div class="progress active"><div class="bar" style="width: 0%;"></div></div></td>' + '<td><a id="' + id + '_rm" class="rmlink"><i class="icon-trash"></i></a></td>' + '</tr>'); $('#filelist #' + id).show('fast'); $('#uploadfiles').removeClass("disabled"); + $('#' + dbfile.id + '_rm').on("click", function(event){ + $('#' + dbfile.id).hide('fast'); + }); dropbox_files.push(dbfile); }); }, false); } diff --git a/modules/webdeposit/lib/webdeposit_form.py b/modules/webdeposit/lib/webdeposit_form.py index d8e01f051..9f58733c4 100644 --- a/modules/webdeposit/lib/webdeposit_form.py +++ b/modules/webdeposit/lib/webdeposit_form.py @@ -1,108 +1,260 @@ # -*- coding: utf-8 -*- -## -## This file is part of Invenio. -## Copyright (C) 2013 CERN. -## -## Invenio is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License as -## published by the Free Software Foundation; either version 2 of the -## License, or (at your option) any later version. -## -## Invenio is distributed in the hope that it will be useful, but -## WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -## General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Invenio; if not, write to the Free Software Foundation, Inc., -## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA - -from wtforms import Label +# +# This file is part of Invenio. +# Copyright (C) 2013 CERN. +# +# Invenio is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# Invenio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Invenio; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA + from invenio.wtforms_utils import InvenioForm as Form -from invenio.webdeposit_config_utils import WebDepositConfiguration from invenio.webdeposit_cook_json_utils import cook_files, uncook_files +CFG_GROUPS_META = { + 'classes': None, + 'indication': None, + 'description': None +} +""" +Default group metadata. +""" -class WebDepositForm(Form): +CFG_FIELD_FLAGS = [ + 'hidden', + 'disabled' +] +""" +List of WTForm field flags to be saved in draft. - """ Generic WebDeposit Form class """ +See more about WTForm field flags on: +http://wtforms.simplecodes.com/docs/1.0.4/fields.html#wtforms.fields.Field.flags +""" - def __init__(self, **kwargs): - super(WebDepositForm, self).__init__(**kwargs) +""" +Form customization - # Load and apply configuration from config file - self.config = WebDepositConfiguration(form_type=self.__class__.__name__) +you can customize the following for the form - custom_title = self.config.get_form_title(self.__class__.__name__) - if custom_title is not None: - self._title = custom_title +_title: str, the title to be rendered on top of the form +_subtitle: str/html. explanatory text to be shown under the title. +_drafting: bool, show or hide the drafts at the right of the form - for field in self._fields.values(): - custom_label = self.config.get_label(field.__class__.__name__) - if custom_label is not None: - setattr(field, 'label', Label(field.id, custom_label)) +""" - custom_widget = self.config.get_widget(field.__class__.__name__) - if custom_widget is not None: - setattr(field, 'widget', custom_widget) + +class WebDepositForm(Form): + + """ Generic WebDeposit Form class """ + + def __init__(self, **kwargs): + super(WebDepositForm, self).__init__(**kwargs) + self._messages = None self.groups_meta = {} if hasattr(self, 'groups'): - for group in self.groups: + for idx, group in enumerate(self.groups): group_name = group[0] fields = group[1] for field in fields: setattr(self[field], 'group', group_name) + + self.groups_meta[group_name] = CFG_GROUPS_META.copy() if len(group) == 3: # If group has metadata - group_meta = group[2] - self.groups_meta[group_name] = group_meta + self.groups_meta[group_name].update(group[2]) + + if not hasattr(self, 'template'): + self.template = 'webdeposit_add.html' + + if not hasattr(self, '_drafting'): + self._drafting = True + + self.type = self.__class__.__name__ + + def reset_field_data(self, exclude=[]): + """ + Reset the fields.data value to that of field.object_data. + + Useful after initializing a form with both formdata and draftdata where + the formdata is missing field values (usually because we are saving a + single field). + + @param exclude: List of field names to exclude. + """ + for name, field in self._fields.items(): + if name not in exclude: + field.data = field.object_data def cook_json(self, json_reader): for field in self._fields.values(): try: json_reader = field.cook_json(json_reader) except AttributeError: # Some fields (eg. SubmitField) don't have a cook json function pass - cook_files_function = self.config.get_files_cook_function() or cook_files - json_reader = cook_files_function(json_reader, self.files) + json_reader = cook_files(json_reader, self.files) return json_reader def uncook_json(self, json_reader, webdeposit_json, recid=None): for field in self._fields.values(): if hasattr(field, 'uncook_json'): # WTFields are not mapped with rec json webdeposit_json = field.uncook_json(json_reader, webdeposit_json) webdeposit_json = uncook_files(webdeposit_json, recid=recid, json_reader=json_reader) return webdeposit_json def get_groups(self): - groups = [({"name": 'Rest'}, [])] - # Just a dict for optimization - groups_hash = {} + """ + Get a list of the (group metadata, list of fields)-tuples + + The last element of the list has no group metadata (i.e. None), + and contains the list of fields not assigned to any group. + """ + fields_included = set() + field_groups = [] + + if hasattr(self, 'groups'): + for group in self.groups: + group_obj = { + 'name': group[0], + 'meta': CFG_GROUPS_META.copy(), + } + + fields = [] + for field_name in group[1]: + fields.append(self[field_name]) + fields_included.add(field_name) + + if len(group) == 3: + group_obj['meta'].update(group[2]) + + field_groups.append((group_obj, fields)) + + # Append missing fields not defined in groups + rest_fields = [] for field in self: - if hasattr(field, 'group') and field.group is not None: - if not field.group in groups_hash: - groups_hash[field.group] = len(groups) - # Append group to the list - groups.append(({"name": field.group}, [])) - # Append field to group's field list - groups[groups_hash[field.group]][1].append(field) - - if field.group in self.groups_meta: - # Add group's meta (description etc) - groups[groups_hash[field.group]][0]['meta'] = \ - self.groups_meta[field.group] - else: - # Append to Rest - groups[0][1].append(field) - - # Append rest fields in the end - rest = groups.pop(0) - groups.append(rest) - return groups + if field.name not in fields_included: + rest_fields.append(field) + if rest_fields: + field_groups.append((None, rest_fields)) + + return field_groups + + @property + def json_data(self): + """ + Return form data in a format suitable for the standard JSON encoder, by + calling Field.json_data() on each field if it exists, otherwise is uses + the value of Field.data. + """ + return dict( + (name, f.json_data() if getattr(f, 'json_data', None) else f.data) + for name, f in self._fields.items() + ) + + def get_template(self): + """ + Get template to render this form. + Define a data member `template` to customize which template to use. + + By default, it will render the template `webdeposit_add.html` + + """ + + return [self.template] + + def post_process(self, fields=[], submit=False): + """ + Run form post-processing by calling `post_process` on each field, + passing any extra `Form.post_process_<fieldname>` processors to the + field. + + If ``fields'' are specified, only the given fields' processors will be + run (which may touch all fields of the form). + + The post processing allows the form to alter other fields in the form, + via e.g. contacting external services (e.g a DOI field could retrieve + title, authors from CrossRef/DataCite). + """ + for name, field, in self._fields.items(): + if not fields or name in fields: + inline = getattr( + self.__class__, 'post_process_%s' % name, None) + if inline is not None: + extra = [inline] + else: + extra = [] + field.post_process(self, extra_processors=extra, submit=False) + + def autocomplete(self, field_name, term, limit=50): + """ + Auto complete a form field. + + Assumes that formdata has already been loaded by into the form, so that + the search term can be access by field.data. + """ + if field_name in self._fields: + return self._fields[field_name].perform_autocomplete( + self, + term, + limit=limit, + )[:limit] + return [] + + @property + def messages(self): + """ + Return a dictionary of form messages. + """ + _messages = dict( + ( + name, + { + 'state': f.message_state + if hasattr(f, 'message_state') and f.message_state + else '', + 'messages': f.messages, + } + ) for name, f in self._fields.items() + ) + + if self.errors: + _messages.update(dict( + ( + name, + { + 'state': 'error', + 'messages': messages, + } + ) for name, messages in self.errors.items() + + )) + return _messages + + @property + def flags(self): + """ + Return dictionary of fields and their set flags + + Note only flags from CFG_FIELD_FLAGS that is set to True are returned. + """ + return dict( + ( + name, + filter(lambda flag: getattr(f.flags, flag), CFG_FIELD_FLAGS) + ) for name, f in self._fields.items() + ) diff --git a/modules/webdeposit/lib/webdeposit_load_fields.py b/modules/webdeposit/lib/webdeposit_load_fields.py index 53c9c9f74..6afb11ba9 100644 --- a/modules/webdeposit/lib/webdeposit_load_fields.py +++ b/modules/webdeposit/lib/webdeposit_load_fields.py @@ -1,59 +1,61 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. import os from pprint import pformat from wtforms import Field from invenio.config import CFG_PYLIBDIR, CFG_LOGDIR from invenio.pluginutils import PluginContainer def plugin_builder(plugin_name, plugin_code): if plugin_name == '__init__': return try: + candidates = [] all = getattr(plugin_code, '__all__') for name in all: candidate = getattr(plugin_code, name) if issubclass(candidate, Field): - return candidate + candidates.append(candidate) + return candidates except AttributeError: pass CFG_FIELDS = PluginContainer(os.path.join(CFG_PYLIBDIR, 'invenio', 'webdeposit_deposition_fields', '*_field.py'), plugin_builder=plugin_builder) - class Fields(object): pass fields = Fields() -for field in CFG_FIELDS.itervalues(): - ## Change the names of the fields from the file names to the class names. - if field is not None: - fields.__setattr__(field.__name__, field) +for field_list in CFG_FIELDS.itervalues(): + for field in field_list: + ## Change the names of the fields from the file names to the class names. + if field is not None: + fields.__setattr__(field.__name__, field) ## Let's report about broken plugins open(os.path.join(CFG_LOGDIR, 'broken-deposition-fields.log'), 'w').write( pformat(CFG_FIELDS.get_broken_plugins())) __all__ = ['fields'] diff --git a/modules/webdeposit/lib/webdeposit_validation_utils.py b/modules/webdeposit/lib/webdeposit_processor_utils.py similarity index 52% copy from modules/webdeposit/lib/webdeposit_validation_utils.py copy to modules/webdeposit/lib/webdeposit_processor_utils.py index 6b7e1e67b..343076297 100644 --- a/modules/webdeposit/lib/webdeposit_validation_utils.py +++ b/modules/webdeposit/lib/webdeposit_processor_utils.py @@ -1,204 +1,246 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# from wtforms.validators import ValidationError, StopValidation, Regexp +from werkzeug import MultiDict from invenio.dataciteutils import DataciteMetadata from invenio.sherpa_romeo import SherpaRomeoSearch from invenio.bibfield import get_record - -#FIXME: make the functions to return functions so that -# in the config file we can define parameters -# eg. length(5,10) - - -def datacite_doi_validate(field, dummy_form=None): - value = field.data - if value == "" or value.isspace(): - return dict() - datacite = DataciteMetadata(value) - if datacite.error: - return dict(info=1, info_message="Couldn't retrieve doi metadata") - - return dict(fields=dict(publisher=datacite.get_publisher(), - title=datacite.get_titles(), - date=datacite.get_dates(), - abstract=datacite.get_description()), - success=1, - success_message='Datacite.org metadata imported successfully') - - -def sherpa_romeo_issn_validate(field, dummy_form=None): - value = field.data +# +# General purpose processors +# + + +def replace_field_data(field_name): + """ + Returns a processor, which will replace the given field names value with + the value from the field where the processor is installed. + """ + def _inner(form, field, submit=False): + getattr(form, field_name).data = field.data + return _inner + +# +# DOI-related processors +# + + +def datacite_dict_mapper(datacite, form, mapping): + """ + Helper function to map DataCite metadata to form fields based on a mapping + """ + for func_name, field_name in mapping.items(): + setattr(form, field_name, getattr(datacite, func_name)()) + + +class DataCiteLookup(object): + """ + Lookup DOI metadata in DataCite but only if DOI is not locally + administered. + """ + def __init__(self, display_info=False, mapping=None, + mapping_func=None, exclude_prefix='10.5072'): + self.display_info = display_info + self.mapping = mapping or dict( + get_publisher='publisher', + get_titles='title', + get_dates='date', + get_description='abstract', + ) + self.mapping_func = mapping_func or datacite_dict_mapper + self.prefix = exclude_prefix + + def __call__(self, form, field, submit=False): + if not field.errors and field.data and not field.data.startswith(self.prefix + '/'): + try: + datacite = DataciteMetadata(field.data) + if datacite.error: + if self.display_info: + field.add_message('info', + "DOI metadata could not be retrieved.") + return + if self.mapping_func: + self.mapping_func(datacite, form, self.mapping) + if self.display_info: + field.add_message('info', + "DOI metadata successfully imported from DataCite.") + except Exception: + # Ignore errors + pass + + +datacite_lookup = DataCiteLookup + + +def sherpa_romeo_issn_process(form, field, submit=False): + value = field.data or '' if value == "" or value.isspace(): return dict(error=0, error_message='') s = SherpaRomeoSearch() s.search_issn(value) if s.error: - return dict(error=1, error_message=s.error_message) + field.add_message('info', s.error_message) + return if s.get_num_hits() == 1: journal = s.parser.get_journals(attribute='jtitle') journal = journal[0] publisher = s.parser.get_publishers(journal=journal) if publisher is not None and publisher != []: - return dict(error=0, error_message='', - fields=dict(journal=journal, - publisher=publisher['name'])) + if hasattr(form, 'journal'): + form.journal.data = journal + + if hasattr(form, 'publisher'): + form.publisher.data = publisher['name'] + return else: - return dict(error=0, error_message='', - fields=dict(journal=journal)) + if hasattr(form, 'journal'): + form.journal.data = journal + return - return dict(info=1, info_message="Couldn't find Journal") + field.add_message('info', "Couldn't find Journal.") -def sherpa_romeo_publisher_validate(field, dummy_form=None): - value = field.data +def sherpa_romeo_publisher_process(form, field, submit=False): + value = field.data or '' if value == "" or value.isspace(): - return dict(error=0, error_message='') + return s = SherpaRomeoSearch() s.search_publisher(value) if s.error: - return dict(info=1, info_message=s.error_message) + field.add_message('info', s.error_message) conditions = s.parser.get_publishers(attribute='conditions') if conditions is not None and s.get_num_hits() == 1: conditions = conditions[0] else: conditions = [] if conditions != []: conditions_html = "<u>Conditions</u><br><ol>" if isinstance(conditions['condition'], str): conditions_html += "<li>" + conditions['condition'] + "</li>" else: for condition in conditions['condition']: conditions_html += "<li>" + condition + "</li>" copyright_links = s.parser.get_publishers(attribute='copyrightlinks') if copyright_links is not None and copyright_links != []: copyright_links = copyright_links[0] else: copyright_links = None if isinstance(copyright_links, list): copyright_links_html = "" for copyright_link in copyright_links['copyrightlink']: copyright_links_html += '<a href="' + copyright_link['copyrightlinkurl'] + \ '">' + copyright_link['copyrightlinktext'] + "</a><br>" elif isinstance(copyright_links, dict): if isinstance(copyright_links['copyrightlink'], list): for copyright_link in copyright_links['copyrightlink']: copyright_links_html = '<a href="' + copyright_link['copyrightlinkurl'] + \ '">' + copyright_link['copyrightlinktext'] + "</a><br>" else: copyright_link = copyright_links['copyrightlink'] copyright_links_html = '<a href="' + copyright_link['copyrightlinkurl'] + \ '">' + copyright_link['copyrightlinktext'] + "</a><br>" home_url = s.parser.get_publishers(attribute='homeurl') if home_url is not None and home_url != []: home_url = home_url[0] home_url = '<a href="' + home_url + '">' + home_url + "</a>" else: home_url = None info_html = "" if home_url is not None: info_html += "<p>" + home_url + "</p>" if conditions is not None: info_html += "<p>" + conditions_html + "</p>" if copyright_links is not None: info_html += "<p>" + copyright_links_html + "</p>" if info_html != "": - return dict(error=0, error_message='', - info=1, info_message=info_html) - return dict(error=0, error_message='') + field.add_message('info', info_html) -def sherpa_romeo_journal_validate(field, dummy_form=None): - value = field.data +def sherpa_romeo_journal_process(form, field, submit=False): + value = field.data or '' if value == "" or value.isspace(): - return dict(error=0, error_message='') + return s = SherpaRomeoSearch() s.search_journal(value, 'exact') if s.error: - return dict(info=1, info_message=s.error_message) + field.add_message('info', s.error_message) + return if s.get_num_hits() == 1: issn = s.parser.get_journals(attribute='issn') if issn != [] and issn is not None: issn = issn[0] publisher = s.parser.get_publishers(journal=value) if publisher is not None and publisher != []: - return dict(error=0, error_message='', - fields=dict(issn=issn, - publisher=publisher['name'])) - return dict(error=0, error_message='', - info=1, info_message="Journal's Publisher not found", - fields=dict(publisher="", issn=issn)) + if hasattr(form, 'issn'): + form.issn.data = issn + + if hasattr(form, 'publisher'): + form.publisher.data = publisher['name'] + form.publisher.post_process(form) + return + + field.add_message('info', "Journal's Publisher not found") + if hasattr(form, 'issn'): + form.issn.data = issn + if hasattr(form, 'publisher'): + form.publisher.data = publisher + form.publisher.post_process(form) else: - return dict(info=1, info_message="Couldn't find ISSN") - return dict(error=0, error_message='') + field.add_message('info', "Couldn't find ISSN.") -def number_validate(field, dummy_form=None, error_message='It must be a number!'): - value = field.data +def record_id_process(form, field, submit=False): + value = field.data or '' if value == "" or value.isspace(): - return dict(error=0, error_message='') + return def is_number(s): try: float(s) return True except ValueError: return False - if not is_number(value): - try: - field.errors.append(error_message) - except AttributeError: - field.errors = list(field.process_errors) - field.errors.append(error_message) - return dict(error=1, - error_message=error_message) - else: - return dict(error=0, error_message='') - - -def record_id_validate(field, form=None): - value = field.data - is_number = number_validate(field)['error'] == 0 \ - and (value != "" or not value.isspace()) - - if is_number: + if is_number(field.data): json_reader = get_record(value) else: - return dict(error=1, error_message="Record id must be a number!") + field.add_message('error', "Record id must be a number!") + return + if json_reader is not None: webdeposit_json = form.uncook_json(json_reader, {}, value) - #FIXME: update current json + #FIXME: update current json, past self, what do you mean?? :S + + field.add_message('info', '<a href="/record/"' + value + + '>Record</a> was loaded successfully') - return dict(info=1, - info_message='<a href="/record/"' + value + - '>Record</a> loaded successfully', - fields=webdeposit_json) + form.process(MultiDict(webdeposit_json)) else: - return dict(info=1, info_message="Record doesn't exist") + field.add_message('info', "Record doesn't exist") diff --git a/modules/webdeposit/lib/webdeposit_regression_tests.py b/modules/webdeposit/lib/webdeposit_regression_tests.py index e0934da7f..28675ac0d 100644 --- a/modules/webdeposit/lib/webdeposit_regression_tests.py +++ b/modules/webdeposit/lib/webdeposit_regression_tests.py @@ -1,219 +1,309 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from invenio.testutils import make_test_suite, run_test_suite, InvenioTestCase class TestWebDepositUtils(InvenioTestCase): def clear_tables(self): from invenio.bibworkflow_model import Workflow, WfeObject - from invenio.webdeposit_model import WebDepositDraft from invenio.sqlalchemyutils import db Workflow.query.delete() WfeObject.query.delete() - WebDepositDraft.query.delete() db.session.commit() def setUp(self): self.clear_tables() super(TestWebDepositUtils, self).setUp() def tearDown(self): self.clear_tables() super(TestWebDepositUtils, self).tearDown() def test_workflow_creation(self): from invenio.webdeposit_load_deposition_types import \ deposition_metadata from invenio.bibworkflow_model import Workflow from invenio.webdeposit_workflow import DepositionWorkflow from invenio.webdeposit_utils import get_latest_or_new_workflow, \ get_workflow, delete_workflow from invenio.sqlalchemyutils import db from invenio.webuser_flask import login_user login_user(1) number_of_dep_types = len(deposition_metadata) # Test for every deposition type for deposition_type in deposition_metadata.keys(): # New workflow is created workflow = get_latest_or_new_workflow(deposition_type, user_id=1) assert workflow is not None # The just created workflow is retrieved as latest workflow2 = get_latest_or_new_workflow(deposition_type, user_id=1) assert workflow2 is not None assert str(workflow2.uuid) == str(workflow.uuid) # and also retrieved with its uuid - workflow = get_workflow(deposition_type, workflow.uuid) + workflow = get_workflow(workflow.uuid, deposition_type) assert workflow is not None # Test get_workflow function with random arguments - workflow = get_workflow('deposition_type_that_doesnt_exist', - 'some_uuid') + workflow = get_workflow('some_uuid', + 'deposition_type_that_doesnt_exist') assert workflow is None deposition_type = deposition_metadata.keys()[-1] - workflow = get_workflow(deposition_type, - 'some_uuid_that_doesnt_exist') + workflow = get_workflow('some_uuid_that_doesnt_exist', deposition_type) assert workflow is None # Create workflow without using webdeposit_utils wf = deposition_metadata[deposition_type]["workflow"] workflow = DepositionWorkflow(deposition_type=deposition_type, workflow=wf, user_id=1) # Test that the retrieved workflow is the same and not None - workflow2 = get_workflow(deposition_type, workflow.get_uuid()) + workflow2 = get_workflow(workflow.get_uuid(), deposition_type) assert workflow2 is not None assert workflow2.get_uuid() == workflow.get_uuid() # Check the number of created workflows workflows = db.session.query(Workflow).all() assert len(workflows) == number_of_dep_types + 1 uuid = workflow.get_uuid() delete_workflow(1, uuid) - workflow = get_workflow(deposition_type, uuid) + workflow = get_workflow(uuid, deposition_type) assert workflow is None def test_form_functions(self): from invenio.webdeposit_load_deposition_types import \ deposition_metadata from invenio.webdeposit_load_forms import forms - from invenio.webdeposit_model import WebDepositDraft from invenio.webdeposit_workflow import DepositionWorkflow - from invenio.webdeposit_utils import get_current_form, get_form, \ - get_form_status, CFG_DRAFT_STATUS - from invenio.sqlalchemyutils import db + from invenio.webdeposit_utils import get_form, \ + get_form_status, set_form_status, CFG_DRAFT_STATUS + from invenio.bibworkflow_model import Workflow from invenio.webdeposit_workflow_utils import render_form, \ wait_for_submission from invenio.cache import cache for metadata in deposition_metadata.values(): for wf_function in metadata['workflow']: if 'render_form' == wf_function.func_name: break from invenio.webuser_flask import login_user login_user(1) - wf = [render_form(forms.values()[0]), wait_for_submission()] deposition_workflow = DepositionWorkflow(deposition_type='TestWorkflow', workflow=wf, user_id=1) uuid = deposition_workflow.get_uuid() cache.delete_many("1:current_deposition_type", "1:current_uuid") cache.add("1:current_deposition_type", 'TestWorkflow') cache.add("1:current_uuid", uuid) # Run the workflow to insert a form to the db deposition_workflow.run() # There is only one form in the db - drafts = db.session.query(WebDepositDraft) - assert len(drafts.all()) == 1 - + workflows = Workflow.get(module_name='webdeposit') + assert len(workflows.all()) == 1 + assert len(workflows[0].extra_data['drafts']) == 1 # Test that guest user doesn't have access to the form - uuid, form = get_current_form(0, deposition_type='TestWorkflow', - uuid=uuid) + form = get_form(0, uuid=uuid) assert form is None # Test that the current form has the right type - uuid, form = get_current_form(1, deposition_type='TestWorkflow', - uuid=deposition_workflow.get_uuid()) + form = get_form(1, uuid=deposition_workflow.get_uuid()) assert isinstance(form, forms.values()[0]) assert str(uuid) == str(deposition_workflow.get_uuid()) # Test that form is returned with get_form function form = get_form(1, deposition_workflow.get_uuid()) assert form is not None form = get_form(1, deposition_workflow.get_uuid(), step=0) assert form is not None # Second step doesn't have a form form = get_form(1, deposition_workflow.get_uuid(), step=1) assert form is None form_status = get_form_status(1, deposition_workflow.get_uuid()) assert form_status == CFG_DRAFT_STATUS['unfinished'] form_status = get_form_status(1, deposition_workflow.get_uuid(), step=2) assert form_status is None - db.session.query(WebDepositDraft).\ - update({'status': CFG_DRAFT_STATUS['finished']}) - + set_form_status(1, uuid, CFG_DRAFT_STATUS['finished']) form_status = get_form_status(1, deposition_workflow.get_uuid()) assert form_status == CFG_DRAFT_STATUS['finished'] def test_field_functions(self): from datetime import datetime from invenio.sqlalchemyutils import db from invenio.webdeposit_workflow import DepositionWorkflow from invenio.webdeposit_model import WebDepositDraft from invenio.webdeposit_workflow_utils import render_form from invenio.webdeposit_utils import draft_field_get from invenio.webdeposit_deposition_forms.article_form import ArticleForm from invenio.cache import cache - wf = [render_form(ArticleForm)] user_id = 1 workflow = DepositionWorkflow(workflow=wf, deposition_type='TestWorkflow', user_id=user_id) cache.delete_many("1:current_deposition_type", "1:current_uuid") cache.add("1:current_deposition_type", 'TestWorkflow') cache.add("1:current_uuid", workflow.get_uuid()) workflow.run() # Insert a form uuid = workflow.get_uuid() # Test for a field that's not there value = draft_field_get(user_id, uuid, 'field_that_doesnt_exist') assert value is None # Test for a field that hasn't been inserted in db yet value = draft_field_get(user_id, uuid, 'publisher') assert value is None values = {'publisher': 'Test Publishers Association'} db.session.query(WebDepositDraft).\ filter(WebDepositDraft.uuid == uuid, WebDepositDraft.step == 0).\ update({"form_values": values, "timestamp": datetime.now()}) + def test_record_creation(self): + import os + from wtforms import TextAreaField + from datetime import datetime + + from invenio.search_engine import record_exists + from invenio.cache import cache + from invenio.config import CFG_PREFIX + from invenio.webuser_flask import login_user + from invenio.bibworkflow_model import Workflow + from invenio.bibworkflow_config import CFG_WORKFLOW_STATUS + from invenio.bibsched_model import SchTASK + + from invenio.webdeposit_utils import get_form, create_workflow, \ + set_form_status, CFG_DRAFT_STATUS + from invenio.webdeposit_load_deposition_types import \ + deposition_metadata + from invenio.webdeposit_workflow_utils import \ + create_record_from_marc + from invenio.bibfield import get_record + + login_user(1) + for deposition_type in deposition_metadata.keys(): + + deposition = create_workflow(deposition_type, 1) + assert deposition is not None + + # Check if deposition creates a record + create_rec = create_record_from_marc() + function_exists = False + for workflow_function in deposition.workflow: + if create_rec.func_code == workflow_function .func_code: + function_exists = True + if not function_exists: + # if a record is not created, + #continue with the next deposition + continue + + uuid = deposition.get_uuid() + + cache.delete_many("1:current_deposition_type", "1:current_uuid") + cache.add("1:current_deposition_type", deposition_type) + cache.add("1:current_uuid", uuid) + + # Run the workflow + deposition.run() + + # Create form's json based on the field name + form = get_form(1, uuid=uuid) + webdeposit_json = {} + + # Fill the json with dummy data + for field in form: + if isinstance(field, TextAreaField): + # If the field is associated with a marc field + if field.has_recjson_key() or field.has_cook_function(): + webdeposit_json[field.name] = "test " + field.name + + draft = dict(form_type=form.__class__.__name__, + form_values=webdeposit_json, + step=0, # dummy step + status=CFG_DRAFT_STATUS['finished'], + timestamp=str(datetime.now())) + + # Add a draft for the first step + Workflow.set_extra_data(user_id=1, uuid=uuid, + key='drafts', value={0: draft}) + + workflow_status = CFG_WORKFLOW_STATUS.RUNNING + while workflow_status != CFG_WORKFLOW_STATUS.FINISHED: + # Continue workflow + deposition.run() + set_form_status(1, uuid, CFG_WORKFLOW_STATUS.FINISHED) + workflow_status = deposition.get_status() + + # Workflow is finished. Test if record is created + recid = deposition.get_data('recid') + assert recid is not None + # Test that record id exists + assert record_exists(recid) == 1 + + # Test that the task exists + task_id = deposition.get_data('task_id') + assert task_id is not None + + bibtask = SchTASK.query.filter(SchTASK.id == task_id).first() + assert bibtask is not None + + # Run bibupload, bibindex, webcoll manually + cmd = "%s/bin/bibupload %s" % (CFG_PREFIX, task_id) + assert not os.system(cmd) + rec = get_record(recid) + marc = rec.legacy_export_as_marc() + for field in form: + if isinstance(field, TextAreaField): + # If the field is associated with a marc field + if field.has_recjson_key() or field.has_cook_function(): + assert "test " + field.name in marc + + TEST_SUITE = make_test_suite(TestWebDepositUtils) if __name__ == "__main__": run_test_suite(TEST_SUITE) diff --git a/modules/webdeposit/lib/webdeposit_templates.js b/modules/webdeposit/lib/webdeposit_templates.js new file mode 100644 index 000000000..eed1c4e79 --- /dev/null +++ b/modules/webdeposit/lib/webdeposit_templates.js @@ -0,0 +1,13 @@ +var tpl_webdeposit_status_saved = Hogan.compile('Saved <i class="icon-ok"></i>'); +var tpl_webdeposit_status_saved_with_errors = Hogan.compile('<span class="text-warning">Saved, but with errors <i class="icon-warning-sign"></i></span>'); +var tpl_webdeposit_status_saving = Hogan.compile('Saving <img src="/css/images/ajax-loader.gif" />'); +var tpl_webdeposit_status_error = Hogan.compile('<span class="text-error">Not saved due to server error. Please try to reload your browser <i class="icon-warning-sign"></i></span>'); +var tpl_field_message = Hogan.compile('{{#messages}}<div>{{{.}}}</div>{{/messages}}'); +var tpl_required_field_message = Hogan.compile('{{{label}}} is required.'); +var tpl_flash_message = Hogan.compile('<div class="alert alert-{{state}}"><a class="close" data-dismiss="alert" href="#"">×</a>{{{message}}}</div>'); +var tpl_message_success = Hogan.compile('Successfully saved.'); +var tpl_message_errors = Hogan.compile('The form was saved, but there were errors. Please see below.'); +var tpl_message_server_error = Hogan.compile('The form could not be saved, due to a communication problem with the server. Please try to reload your browser <i class="icon-warning-sign"></i>'); +var tpl_loader = Hogan.compile('<img src="/css/images/ajax-loader.gif" />'); +var tpl_loader_success = Hogan.compile('<span class="text-success"> <i class="icon-ok"></i></span>'); +var tpl_loader_failed = Hogan.compile('<span class="muted"> <i class="icon-warning-sign"></i></span>'); diff --git a/modules/webdeposit/lib/webdeposit_utils.py b/modules/webdeposit/lib/webdeposit_utils.py index 6912076c5..ab27f6b36 100644 --- a/modules/webdeposit/lib/webdeposit_utils.py +++ b/modules/webdeposit/lib/webdeposit_utils.py @@ -1,623 +1,852 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +""" WebDeposit Utils +Set of utilities to be used by blueprint, forms and fields. + +It contains functions to start a workflow, retrieve it and edit its data. +The basic entities are the forms and fields which are stored in json. +(forms are referred as drafts before they haven't been submitted yet.) + +The file field is handled separately and all the files are attached in the json +as a list in the 'files' key. + +Some functions contain the keyword `preingest`. This refers to the json that is +stored in the 'pop_obj' key, which is used to store data before running the +workflow. This is being used e.g. in the wedbeposit api where the workflow is +being run without a user submitting the forms, so this json is being used to +preinsert data into the webdeposit workflow. +""" + + import os +import shutil +from flask import request +from glob import iglob +from werkzeug.utils import secure_filename +from werkzeug.exceptions import BadRequestKeyError +from werkzeug import MultiDict from datetime import datetime -from sqlalchemy import desc -from wtforms import FormField from sqlalchemy.orm.exc import NoResultFound from uuid import uuid1 as new_uuid from urllib2 import urlopen, URLError +from invenio.cache import cache from invenio.sqlalchemyutils import db from invenio.webdeposit_model import WebDepositDraft from invenio.bibworkflow_model import Workflow from invenio.bibworkflow_config import CFG_WORKFLOW_STATUS from invenio.webdeposit_load_forms import forms +from invenio.webdeposit_form import CFG_FIELD_FLAGS from invenio.webuser_flask import current_user from invenio.webdeposit_load_deposition_types import deposition_metadata from invenio.webdeposit_workflow import DepositionWorkflow +from invenio.webdeposit_workflow_utils import render_form from invenio.config import CFG_WEBDEPOSIT_UPLOAD_FOLDER -""" Deposition Type Functions """ - CFG_DRAFT_STATUS = { 'unfinished': 0, 'finished': 1 } +# +# Setters/Getters for bibworklflow +# + +def draft_getter(step=None): + """Returns a json with the current form values. + If step is None, the latest draft is returned.""" + def draft_getter_func(json): + try: + if step is None: + return json['drafts'][max(json['drafts'])] + else: + try: + return json['drafts'][step] + except KeyError: + pass + + try: + return json['drafts'][unicode(step)] + except KeyError: + pass + raise NoResultFound + except KeyError: + try: + return {'timestamp': json['pop_obj']['timestamp']} + except KeyError: + # there is no pop object + return None + return draft_getter_func + + +def draft_setter(step=None, key=None, value=None, data=None, field_setter=False): + """Alters a draft's specified value. + If the field_setter is true, it uses the key value to update + the dictionary `form_values` otherwise it updates the draft.""" + def draft_setter_func(json): + try: + if step is None: + draft = json['drafts'][max(json['drafts'])] + else: + draft = json['drafts'][step] + except (ValueError, KeyError): + # There are no drafts or they are empty + return + + if key: + if field_setter: + draft['form_values'][key] = value + else: + draft[key] = value + + if data: + if field_setter: + draft['form_values'].update(data) + else: + draft.update(data) + + draft['timestamp'] = str(datetime.now()) + return draft_setter_func + + +def add_draft(draft): + """ Adds a form draft. """ + def setter(json): + step = draft.pop('step') + if not 'drafts' in json: + json['drafts'] = {} + if not step in json['drafts'] and \ + not unicode(step) in json['drafts']: + json['drafts'][step] = draft + return setter + + +def draft_field_list_setter(field_name, value): + def setter(json): + try: + draft = json['drafts'][max(json['drafts'])] + except (ValueError, KeyError): + # There are no drafts or they are empty + return + values = draft['form_values'] + try: + if isinstance(values[field_name], list): + values[field_name].append(value) + else: + new_values_list = [values[field_name]] + new_values_list.append(value) + values[field_name] = new_values_list + except KeyError: + values[field_name] = [value] + + draft['timestamp'] = str(datetime.now()) + return setter + + +# +# Workflow functions +# + def get_latest_or_new_workflow(deposition_type, user_id=None): """ Creates new workflow or returns a new one """ user_id = user_id or current_user.get_id() wf = deposition_metadata[deposition_type]["workflow"] # get latest draft in order to get workflow's uuid - latest_workflow = db.session.query(Workflow).\ - filter( + try: + latest_workflow = Workflow.get_most_recent( Workflow.user_id == user_id, Workflow.name == deposition_type, Workflow.module_name == 'webdeposit', - Workflow.status != CFG_WORKFLOW_STATUS.FINISHED).\ - order_by(db.desc(Workflow.modified)).\ - first() - - if latest_workflow is None: + Workflow.status != CFG_WORKFLOW_STATUS.FINISHED) + except NoResultFound: # We didn't find other workflows # Let's create a new one return DepositionWorkflow(deposition_type=deposition_type, workflow=wf) # Create a new workflow # based on the latest draft's uuid - uuid = latest_workflow .uuid + uuid = latest_workflow. uuid return DepositionWorkflow(deposition_type=deposition_type, workflow=wf, uuid=uuid) -def get_workflow(deposition_type, uuid): +def get_workflow(uuid, deposition_type=None): """ Returns a workflow instance with uuid=uuid or None """ + + # Check if uuid exists first and get the deposition_type if None + try: + workflow = Workflow.get(uuid=uuid).one() + if deposition_type is None: + deposition_type = workflow.name + except NoResultFound: + return None + try: wf = deposition_metadata[deposition_type]["workflow"] except KeyError: # deposition type not found return None - # Check if uuid exists first - try: - db.session.query(Workflow). \ - filter_by(uuid=uuid).one() - except NoResultFound: - return None return DepositionWorkflow(uuid=uuid, deposition_type=deposition_type, workflow=wf) def create_workflow(deposition_type, user_id=None): """ Creates a new workflow and returns it """ try: wf = deposition_metadata[deposition_type]["workflow"] except KeyError: # deposition type not found return None return DepositionWorkflow(deposition_type=deposition_type, workflow=wf, user_id=user_id) -def delete_workflow(user_id, uuid): +def delete_workflow(dummy_user_id, uuid): """ Deletes all workflow related data (workflow and drafts) """ + Workflow.delete(uuid=uuid) - db.session.query(Workflow). \ - filter_by(uuid=uuid, - user_id=user_id). \ - delete() - - db.session.query(WebDepositDraft). \ - filter_by(uuid=uuid).\ - delete() - db.session.commit() - -def get_current_form(user_id, deposition_type=None, uuid=None): - """Returns the latest draft(wtform object) of the deposition_type - or the form with the specific uuid. - if it doesn't exist, creates a new one +# +# Form loading and saving functions +# +def get_form(user_id, uuid, step=None, formdata=None, load_draft=True, + validate_draft=False): """ + Returns the current state of the workflow in a form or a previous + state (step) - if user_id is None: - return None + @param user_id: - try: - if uuid is not None: - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step, the latest - try: - webdeposit_draft = max(webdeposit_draft_query.all(), - key=lambda w: w.step) - except ValueError: - # No drafts found - raise NoResultFound - elif deposition_type is not None: - webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - Workflow.name == deposition_type, - WebDepositDraft.timestamp == db.func.max( - WebDepositDraft.timestamp).select())[0] - else: - webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.timestamp == db.func.max( - WebDepositDraft.timestamp).select())[0] - except NoResultFound: - # No Form draft was found - return None, None - - form = forms[webdeposit_draft.form_type]() - draft_data = webdeposit_draft.form_values - - for field_name in form.data.keys(): - if isinstance(form._fields[field_name], FormField) \ - and field_name in draft_data: - subfield_names = \ - form._fields[field_name]. \ - form._fields.keys() - #upperfield_name, subfield_name = field_name.split('-') - for subfield_name in subfield_names: - if subfield_name in draft_data[field_name]: - form._fields[field_name].\ - form._fields[subfield_name]. \ - process_data(draft_data[field_name][subfield_name]) - elif field_name in draft_data: - form[field_name].process_data(draft_data[field_name]) - - return webdeposit_draft.uuid, form - - -def get_form(user_id, uuid, step=None): - """ Returns the current state of the workflow in a form - or a previous state (step) - """ + @param uuid: - #FIXME: merge with get_current_form + @param step: - if step is None: - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - try: - # get the draft with the max step - webdeposit_draft = max(webdeposit_draft_query.all(), - key=lambda w: w.step) - except ValueError: - return None - else: + @param formdata: + Dictionary of formdata. + @param validate_draft: + If draft data exists, and no formdata is provided, the form will be + validated if this parameter is set to true. + """ + # Get draft data + if load_draft: try: + webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid, - WebDepositDraft.step == step).one() - except NoResultFound: + Workflow.get_extra_data(user_id=user_id, + uuid=uuid, + getter=draft_getter(step)) + except (ValueError, NoResultFound): + # No drafts found return None - form = forms[webdeposit_draft.form_type]() - - draft_data = webdeposit_draft.form_values - - for field_name in form.data.keys(): - if isinstance(form._fields[field_name], FormField) \ - and field_name in draft_data: - subfield_names = \ - form._fields[field_name].\ - form.fields.keys() - #upperfield_name, subfield_name = field_name.split('-') - for subfield_name in subfield_names: - if subfield_name in draft_data[field_name]: - form._fields[field_name].\ - form._fields[subfield_name].\ - process_data(draft_data[field_name][subfield_name]) - elif field_name in draft_data: - form[field_name].process_data(draft_data[field_name]) - + # If a field is not present in formdata, Form.process() will assume it is + # blank instead of using the draft_data value. Most of the time we are only + # submitting a single field in JSON via AJAX requests. We therefore reset + # non-submitted fields to the draft_data value. + draft_data = webdeposit_draft['form_values'] if load_draft else {} + if formdata: + formdata = MultiDict(formdata) + form = forms[webdeposit_draft['form_type']](formdata=formdata, **draft_data) + if formdata: + form.reset_field_data(exclude=formdata.keys()) + + # Set field flags + if load_draft: + for name, flags in webdeposit_draft.get('form_field_flags', {}).items(): + for check_flags in CFG_FIELD_FLAGS: + if check_flags in flags: + setattr(form[name].flags, check_flags, True) + else: + setattr(form[name].flags, check_flags, False) + + # Process files if 'files' in draft_data: # FIXME: sql alchemy(0.8.0) returns the value from the # column form_values with keys and values in unicode. # This creates problem when the dict is rendered # in the page to be used by javascript functions. There must # be a more elegant way than decoding the dict from unicode. draft_data['files'] = decode_dict_from_unicode(draft_data['files']) for file_metadata in draft_data['files']: # Replace the path with the unique filename if isinstance(file_metadata, basestring): import json file_metadata = json.loads(file_metadata) filepath = file_metadata['file'].split('/') unique_filename = filepath[-1] file_metadata['unique_filename'] = unique_filename form.__setattr__('files', draft_data['files']) else: form.__setattr__('files', {}) + + if validate_draft and draft_data and formdata is None: + form.validate() + return form -def get_form_status(user_id, uuid, step=None): - if step is None: - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - try: - # get the draft with the max step - webdeposit_draft = max(webdeposit_draft_query.all(), - key=lambda w: w.step) - except ValueError: - return None - else: - try: - webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid, - WebDepositDraft.step == step).one() - except NoResultFound: - return None +def save_form(user_id, uuid, form): + """ + Saves the draft form_values and form_field_flags of a form. + """ + json_data = dict((key, value) for key, value in form.json_data.items() + if value is not None) - return webdeposit_draft.status + draft_data_update = { + 'form_values': json_data, + 'form_field_flags': form.flags, + } + Workflow.set_extra_data( + user_id=user_id, + uuid=uuid, + setter=draft_setter(data=draft_data_update) + ) -def set_form_status(user_id, uuid, status, step=None): - if step is None: - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - try: - # get the draft with the max step - webdeposit_draft = max(webdeposit_draft_query.all(), - key=lambda w: w.step) - except ValueError: - return None - else: + +def get_form_status(user_id, uuid, step=None): + try: webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid, - WebDepositDraft.step == step).one() + Workflow.get_extra_data(user_id=user_id, + uuid=uuid, + getter=draft_getter(step)) + except ValueError: + # No drafts found + raise NoResultFound + except NoResultFound: + return None + + return webdeposit_draft['status'] + - webdeposit_draft.status = status - db.session.commit() +def set_form_status(user_id, uuid, status, step=None): + try: + Workflow.set_extra_data(user_id=user_id, + uuid=uuid, + setter=draft_setter(step, 'status', status)) + except ValueError: + # No drafts found + raise NoResultFound + except NoResultFound: + return None def get_last_step(steps): if type(steps[-1]) is list: return get_last_step[-1] else: return steps[-1] def get_current_step(uuid): - webdep_workflow = \ - db.session.query(Workflow). \ - filter(Workflow.uuid == uuid). \ - one() + webdep_workflow = Workflow.get(Workflow.uuid == uuid).one() steps = webdep_workflow.task_counter return get_last_step(steps) """ Draft Functions (or instances of forms) -old implementation with redis cache of the functions is provided in comments -(works only in the article form, needs to be generic) """ def draft_field_get(user_id, uuid, field_name, subfield_name=None): """ Returns the value of a field or, in case of error, None """ - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step - draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) - - values = draft.form_values + values = \ + Workflow.get_extra_data(user_id=user_id, uuid=uuid, + getter=draft_getter())['form_values'] try: if subfield_name is not None: return values[field_name][subfield_name] return values[field_name] except KeyError: return None -def draft_field_error_check(user_id, uuid, field_name, value): - """ Retrieves the form based on the uuid - and returns a json string evaluating the field's value +def draft_form_autocomplete(form_type, field_name, term, limit): """ + Auto-complete field value + """ + try: + form = forms[form_type]() + return form.autocomplete(field_name, term, limit=limit) + except KeyError: + return [] - form = get_form(user_id, uuid=uuid) - - subfield_name = None - if '-' in field_name: # check if its subfield - field_name, subfield_name = field_name.split('-') - - form = form._fields[field_name].form - field_name = subfield_name - form._fields[field_name].process_data(value) - return form._fields[field_name].pre_validate(form) +def draft_form_process_and_validate(user_id, uuid, data): + """ + Process, validate and store incoming form data and return response. + """ + # The form is initialized with form and draft data. The original draft_data + # is accessible in Field.object_data, Field.raw_data is the new form data + # and Field.data is the processed form data or the original draft data. + # + # Behind the scences, Form.process() is called, which in turns call + # Field.process_data(), Field.process_formdata() and any filters defined. + # + # Field.object_data contains the value of process_data(), while Field.data + # contains the value of process_formdata() and any filters applied. + form = get_form(user_id, uuid=uuid, formdata=data) + + # Run form validation which will call Field.pre_valiate(), Field.validators, + # Form.validate_<field>() and Field.post_validate(). Afterwards Field.data + # has been validated and any errors will be present in Field.errors. + form.validate() + + # Call Form.run_processors() which in turn will call Field.run_processors() + # that allow fields to set flags (hide/show) and values of other fields + # after the entire formdata has been processed and validated. + validated_flags, validated_data, validated_msgs = ( + form.flags, form.data, form.messages + ) + form.post_process(fields=data.keys()) + post_processed_flags, post_processed_data, post_processed_msgs = ( + form.flags, form.data, form.messages + ) + + # Save draft data + save_form(user_id, uuid, form) + + ### Build result dictionary + process_field_names = data.keys() + # Determine if some fields where changed during post-processing. + changed_values = dict((name, value) for name, value in post_processed_data.items() if validated_data[name] != value) + # Determine changed flags + changed_flags = dict((name, flags) for name, flags in post_processed_flags.items() if validated_flags[name] != flags) + # Determine changed messages + changed_msgs = dict((name, messages) for name, messages in post_processed_msgs.items() if validated_msgs[name] != messages or name in process_field_names) + + result = {} + if changed_msgs: + result['messages'] = changed_msgs + if changed_values: + result['values'] = changed_values + if changed_flags: + for flag in CFG_FIELD_FLAGS: + fields = [(name, flag in field_flags) for name, field_flags in changed_flags.items()] + result[flag+'_on'] = map(lambda x: x[0], filter(lambda x: x[1], fields)) + result[flag+'_off'] = map(lambda x: x[0], filter(lambda x: not x[1], fields)) + + return result def draft_field_set(user_id, uuid, field_name, value): """ Alters the value of a field """ - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step - draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) - values = draft.form_values - - subfield_name = None - if '-' in field_name: # check if its subfield - field_name, subfield_name = field_name.split('-') - - if subfield_name is not None: - try: - values[field_name][subfield_name] = value - except (KeyError, TypeError): - values[field_name] = dict() - values[field_name][subfield_name] = value - else: - values[field_name] = value # change value - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - filter(WebDepositDraft.uuid == uuid, - WebDepositDraft.step == draft.step).\ - update({"form_values": values, - "timestamp": datetime.now()}) + Workflow.set_extra_data(user_id=user_id, uuid=uuid, + setter=draft_setter(key=field_name, value=value, + field_setter=True)) def draft_field_list_add(user_id, uuid, field_name, value, - subfield=None): + dummy_subfield=None): """Adds value to field Used for fields that contain multiple values e.g.1: { field_name : value1 } OR { field_name : [value1] } --> { field_name : [value1, value2] } e.g.2 { } --> { field_name : [value] } e.g.3 { } --> { field_name : {key : value} } """ - webdeposit_draft_query = \ - db.session.query(WebDepositDraft). \ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step - draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) - values = draft.form_values + Workflow.set_extra_data(user_id=user_id, uuid=uuid, + setter=draft_field_list_setter(field_name, value)) - try: - if isinstance(values[field_name], list): - values[field_name].append(value) - elif subfield is not None: - if not isinstance(values[field_name], dict): - values[field_name] = dict() - values[field_name][subfield] = value + +def preingest_form_data(user_id, form_data, uuid=None, + append=False, cached_data=False): + """Used to insert form data to the workflow before running it + Creates an identical json structure to the draft json. + If cached_data is enabled, the data will be used by the next workflow + initiated by the user, so the uuid can be ommited in this case. + + @param user_id: the user id + + @param uuid: the id of the workflow + + @param form_data: a json with field_name -> value structure + + @param append: set to True if you want to append the values to the existing + ones + + @param cached_data: set to True if you want to cache the data. + """ + def preingest_data(form_data, append): + def preingest(json): + if 'pop_obj' not in json: + json['pop_obj'] = {} + for field, value in form_data.items(): + if append: + try: + if isinstance(json['pop_obj'][field], list): + json['pop_obj'][field].append(value) + else: + new_values_list = [json['pop_obj'][field]] + new_values_list.append(value) + json['pop_obj'][field] = new_values_list + except KeyError: + json['pop_obj'][field] = [value] + else: + json['pop_obj'][field] = value + json['pop_obj']['timestamp'] = str(datetime.now()) + return preingest + + if cached_data: + cache.set(str(user_id) + ':cached_form_data', form_data) + else: + Workflow.set_extra_data(user_id=user_id, uuid=uuid, + setter=preingest_data(form_data, append)) + + # Ingest the data in the forms, in case there are any + if append: + for field_name, value in form_data.items(): + draft_field_list_add(user_id, uuid, field_name, value) else: - new_values_list = [values[field_name]] - new_values_list.append(value) - values[field_name] = new_values_list - except KeyError: - values[field_name] = [value] + for field_name, value in form_data.items(): + draft_field_set(user_id, uuid, field_name, value) + + +def get_preingested_form_data(user_id, uuid=None, key=None, cached_data=False): + def get_preingested_data(key): + def getter(json): + if 'pop_obj' in json: + if key is None: + return json['pop_obj'] + else: + return json['pop_obj'][key] + else: + return {} + return getter + + if cached_data: + return cache.get(str(user_id) + ':cached_form_data') + return Workflow.get_extra_data(user_id, uuid=uuid, + getter=get_preingested_data(key)) + + +def validate_preingested_data(user_id, uuid, deposition_type=None): + """Validates all preingested data by trying to match the json with every + form. Then the validation function is being called for each form. + """ + form_data = get_preingested_form_data(user_id, uuid) - db.session.query(WebDepositDraft).\ - filter(WebDepositDraft.uuid == uuid, - WebDepositDraft.step == draft.step).\ - update({"form_values": values, - "timestamp": datetime.now()}) + deposition = get_workflow(uuid, deposition_type) + + form_types = [] + # Get all form types from workflow + for fun in deposition.workflow: + if '__form_type__' in fun.__dict__: + form_render = render_form(forms[fun.__form_type__]) + if form_render.func_code == fun.func_code: + form_types.append(fun.__form_type__) + + errors = {} + for form_type in form_types: + form = forms[form_type]() + for field in form: + if field.name in form_data: + field.data = form_data.pop(field.name) + + form.validate() + + errors.update(form.errors) + + return errors def get_all_drafts(user_id): + """ Returns a dictionary with deposition types and their """ + return dict( + db.session. + query(Workflow.name, + db.func.count(Workflow.uuid)). + filter(Workflow.status != CFG_WORKFLOW_STATUS.FINISHED, + Workflow.user_id == user_id). + group_by(Workflow.name). + all()) + drafts = dict( db.session.query(Workflow.name, db.func.count( db.func.distinct(WebDepositDraft.uuid))). join(WebDepositDraft.workflow). filter(db.and_(Workflow.user_id == user_id, Workflow.status != CFG_WORKFLOW_STATUS.FINISHED)). group_by(Workflow.name).all()) return drafts def get_draft(user_id, uuid, field_name=None): """ Returns draft values in a field_name => field_value dictionary or if field_name is defined, returns the associated value """ - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step - draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) + draft = Workflow.get(user_id=user_id, uuid=uuid) - form_values = draft.form_values + form_values = draft['form_values'] if field_name is None: return form_values else: try: return form_values[field_name] except KeyError: # field_name doesn't exist return form_values # return whole row -def delete_draft(user_id, deposition_type, uuid): - """ Deletes the draft with uuid=uuid - and returns the most recently used draft - if there is no draft left, returns None - (usage not recommended inside workflow context) - """ - - db.session.query(WebDepositDraft). \ - filter_by(uuid=uuid, user_id=user_id). \ - delete() - db.session.commit() - - latest_draft = \ - db.session.query(WebDepositDraft). \ - filter_by(user_id=user_id, - deposition_type=deposition_type). \ - order_by(desc(WebDepositDraft.timestamp)). \ - first() - if latest_draft is None: # There is no draft left - return None - else: - return latest_draft.uuid - - def draft_field_get_all(user_id, deposition_type): """ Returns a list with values of the field_names specified containing all the latest drafts of deposition of type=deposition_type """ - ## Select drafts with max step from each uuid. - subquery = \ - db.session.query(WebDepositDraft.uuid, - db.func.max(WebDepositDraft.step)). \ - join(WebDepositDraft.workflow).\ - filter(db.and_(Workflow.status != CFG_WORKFLOW_STATUS.FINISHED, - Workflow.user_id == user_id, - Workflow.name == deposition_type, - Workflow.module_name == 'webdeposit')). \ - group_by(WebDepositDraft.uuid) - - drafts = \ - WebDepositDraft.query. \ - filter(db.tuple_(WebDepositDraft.uuid, WebDepositDraft.step). - in_(subquery)). \ - order_by(db.desc(WebDepositDraft.timestamp)). \ - all() - return drafts - + ## Select drafts with max step workflow. + workflows = Workflow.get(Workflow.status != CFG_WORKFLOW_STATUS.FINISHED, + Workflow.user_id == user_id, + Workflow.name == deposition_type, + Workflow.module_name == 'webdeposit').all() -def set_current_draft(user_id, uuid): - webdeposit_draft_query = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - WebDepositDraft.uuid == uuid) - # get the draft with the max step - draft = max(webdeposit_draft_query.all(), key=lambda w: w.step) + drafts = [] + get_max_draft = draft_getter() - draft.timestamp = datetime.now() - db.session.commit() + class Draft(object): + def __init__(self, dictionary, workflow): + for k, v in dictionary.items(): + setattr(self, k, v) + setattr(self, 'workflow', workflow) + setattr(self, 'uuid', workflow.uuid) + for workflow in workflows: + max_draft = get_max_draft(workflow.extra_data) + if max_draft is not None: + drafts.append(Draft(max_draft, workflow)) -def get_current_draft(user_id, deposition_type): - webdeposit_draft = \ - db.session.query(WebDepositDraft).\ - join(Workflow).\ - filter(Workflow.user_id == user_id, - Workflow.name == deposition_type).\ - order_by(desc(WebDepositDraft.timestamp)). \ - first() - return webdeposit_draft + drafts = sorted(drafts, key=lambda d: d.timestamp, reverse=True) + return drafts def create_user_file_system(user_id, deposition_type, uuid): # Check if webdeposit folder exists if not os.path.exists(CFG_WEBDEPOSIT_UPLOAD_FOLDER): os.makedirs(CFG_WEBDEPOSIT_UPLOAD_FOLDER) # Create user filesystem # user/deposition_type/uuid/files CFG_USER_WEBDEPOSIT_FOLDER = os.path.join(CFG_WEBDEPOSIT_UPLOAD_FOLDER, "user_" + str(user_id)) if not os.path.exists(CFG_USER_WEBDEPOSIT_FOLDER): os.makedirs(CFG_USER_WEBDEPOSIT_FOLDER) CFG_USER_WEBDEPOSIT_FOLDER = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, deposition_type) if not os.path.exists(CFG_USER_WEBDEPOSIT_FOLDER): os.makedirs(CFG_USER_WEBDEPOSIT_FOLDER) CFG_USER_WEBDEPOSIT_FOLDER = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, uuid) if not os.path.exists(CFG_USER_WEBDEPOSIT_FOLDER): os.makedirs(CFG_USER_WEBDEPOSIT_FOLDER) return CFG_USER_WEBDEPOSIT_FOLDER def decode_dict_from_unicode(unicode_input): if isinstance(unicode_input, dict): return dict((decode_dict_from_unicode(key), decode_dict_from_unicode(value)) for key, value in unicode_input.iteritems()) elif isinstance(unicode_input, list): return [decode_dict_from_unicode(element) for element in unicode_input] elif isinstance(unicode_input, unicode): return unicode_input.encode('utf-8') else: return unicode_input def url_upload(user_id, deposition_type, uuid, url, name=None, size=None): try: data = urlopen(url).read() except URLError: return "Error" CFG_USER_WEBDEPOSIT_FOLDER = create_user_file_system(user_id, deposition_type, uuid) unique_filename = str(new_uuid()) + name file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, unique_filename) f = open(file_path, 'wb') f.write(data) if size is None: size = os.path.getsize(file_path) if name is None: name = url.split('/')[-1] file_metadata = dict(name=name, file=file_path, size=size) draft_field_list_add(current_user.get_id(), uuid, "files", file_metadata) return unique_filename + + +def deposit_files(user_id, deposition_type, uuid, preingest=False): + """Attach files to a workflow + Upload a single file or a file in chunks. + Function must be called within a blueprint function that handles file + uploading. + + Request post parameters: + chunks: number of chunks + chunk: current chunk number + name: name of the file + + @param user_id: the user id + + @param deposition_type: the deposition the files will be attached + + @param uuid: the id of the deposition + + @param preingest: set to True if you want to store the file metadata in the + workflow before running the workflow, i.e. to bind the + files to the workflow and not in the last form draft. + + @return: the path of the uploaded file + """ + if request.method == 'POST': + try: + chunks = request.form['chunks'] + chunk = request.form['chunk'] + except KeyError: + chunks = None + pass + + current_chunk = request.files['file'] + try: + name = request.form['name'] + except BadRequestKeyError: + name = current_chunk.filename + try: + filename = secure_filename(name) + "_" + chunk + except UnboundLocalError: + filename = secure_filename(name) + + CFG_USER_WEBDEPOSIT_FOLDER = create_user_file_system(user_id, + deposition_type, + uuid) + + # Save the chunk + current_chunk.save(os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, filename)) + + unique_filename = "" + + if chunks is None: # file is a single chunk + unique_filename = str(new_uuid()) + filename + old_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, filename) + file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, + unique_filename) + os.rename(old_path, file_path) # Rename the chunk + if current_chunk.content_length != 0: + size = current_chunk.content_length + else: + size = os.path.getsize(file_path) + content_type = current_chunk.content_type or '' + file_metadata = dict(name=name, file=file_path, + content_type=content_type, size=size) + if preingest: + preingest_form_data(user_id, uuid, {'files': file_metadata}) + else: + draft_field_list_add(user_id, uuid, + "files", file_metadata) + elif int(chunk) == int(chunks) - 1: + '''All chunks have been uploaded! + start merging the chunks''' + filename = secure_filename(name) + chunk_files = [] + for chunk_file in iglob(os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, + filename + '_*')): + chunk_files.append(chunk_file) + + # Sort files in numerical order + chunk_files.sort(key=lambda x: int(x.split("_")[-1])) + + unique_filename = str(new_uuid()) + filename + file_path = os.path.join(CFG_USER_WEBDEPOSIT_FOLDER, + unique_filename) + destination = open(file_path, 'wb') + for chunk in chunk_files: + shutil.copyfileobj(open(chunk, 'rb'), destination) + os.remove(chunk) + destination.close() + size = os.path.getsize(file_path) + file_metadata = dict(name=name, file=file_path, size=size) + if preingest: + preingest_form_data(user_id, uuid, {'files': file_metadata}, + append=True) + else: + draft_field_list_add(user_id, uuid, + "files", file_metadata) + return unique_filename + + +def delete_file(user_id, uuid, preingest=False): + if request.method == 'POST': + files = draft_field_get(user_id, uuid, "files") + result = "File Not Found" + filename = request.form['filename'] + if preingest: + files = get_preingested_form_data(user_id, uuid, 'files') + else: + files = draft_field_get(user_id, uuid, "files") + + for i, f in enumerate(files): + if filename == f['file'].split('/')[-1]: + # get the unique name from the path + os.remove(f['file']) + del files[i] + result = str(files) + " " + if preingest: + preingest_form_data(user_id, uuid, files) + else: + draft_field_set(current_user.get_id(), uuid, + "files", files) + result = "File " + f['name'] + " Deleted" + break + return result diff --git a/modules/webdeposit/lib/webdeposit_validation_utils.py b/modules/webdeposit/lib/webdeposit_validation_utils.py index 6b7e1e67b..6053644f7 100644 --- a/modules/webdeposit/lib/webdeposit_validation_utils.py +++ b/modules/webdeposit/lib/webdeposit_validation_utils.py @@ -1,204 +1,163 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -from invenio.dataciteutils import DataciteMetadata -from invenio.sherpa_romeo import SherpaRomeoSearch -from invenio.bibfield import get_record - -#FIXME: make the functions to return functions so that -# in the config file we can define parameters -# eg. length(5,10) - - -def datacite_doi_validate(field, dummy_form=None): - value = field.data - if value == "" or value.isspace(): - return dict() - datacite = DataciteMetadata(value) - if datacite.error: - return dict(info=1, info_message="Couldn't retrieve doi metadata") - - return dict(fields=dict(publisher=datacite.get_publisher(), - title=datacite.get_titles(), - date=datacite.get_dates(), - abstract=datacite.get_description()), - success=1, - success_message='Datacite.org metadata imported successfully') - - -def sherpa_romeo_issn_validate(field, dummy_form=None): - value = field.data - if value == "" or value.isspace(): - return dict(error=0, error_message='') - s = SherpaRomeoSearch() - s.search_issn(value) - if s.error: - return dict(error=1, error_message=s.error_message) - - if s.get_num_hits() == 1: - journal = s.parser.get_journals(attribute='jtitle') - journal = journal[0] - publisher = s.parser.get_publishers(journal=journal) - if publisher is not None and publisher != []: - return dict(error=0, error_message='', - fields=dict(journal=journal, - publisher=publisher['name'])) - else: - return dict(error=0, error_message='', - fields=dict(journal=journal)) - - return dict(info=1, info_message="Couldn't find Journal") - - -def sherpa_romeo_publisher_validate(field, dummy_form=None): - value = field.data - if value == "" or value.isspace(): - return dict(error=0, error_message='') - s = SherpaRomeoSearch() - s.search_publisher(value) - if s.error: - return dict(info=1, info_message=s.error_message) - - conditions = s.parser.get_publishers(attribute='conditions') - if conditions is not None and s.get_num_hits() == 1: - conditions = conditions[0] - else: - conditions = [] - if conditions != []: - conditions_html = "<u>Conditions</u><br><ol>" - if isinstance(conditions['condition'], str): - conditions_html += "<li>" + conditions['condition'] + "</li>" - else: - for condition in conditions['condition']: - conditions_html += "<li>" + condition + "</li>" - - copyright_links = s.parser.get_publishers(attribute='copyrightlinks') - if copyright_links is not None and copyright_links != []: - copyright_links = copyright_links[0] - else: - copyright_links = None - - if isinstance(copyright_links, list): - copyright_links_html = "" - for copyright_link in copyright_links['copyrightlink']: - copyright_links_html += '<a href="' + copyright_link['copyrightlinkurl'] + \ - '">' + copyright_link['copyrightlinktext'] + "</a><br>" - elif isinstance(copyright_links, dict): - if isinstance(copyright_links['copyrightlink'], list): - for copyright_link in copyright_links['copyrightlink']: - copyright_links_html = '<a href="' + copyright_link['copyrightlinkurl'] + \ - '">' + copyright_link['copyrightlinktext'] + "</a><br>" - else: - copyright_link = copyright_links['copyrightlink'] - copyright_links_html = '<a href="' + copyright_link['copyrightlinkurl'] + \ - '">' + copyright_link['copyrightlinktext'] + "</a><br>" - - home_url = s.parser.get_publishers(attribute='homeurl') - if home_url is not None and home_url != []: - home_url = home_url[0] - home_url = '<a href="' + home_url + '">' + home_url + "</a>" - else: - home_url = None - - info_html = "" - if home_url is not None: - info_html += "<p>" + home_url + "</p>" - - if conditions is not None: - info_html += "<p>" + conditions_html + "</p>" - - if copyright_links is not None: - info_html += "<p>" + copyright_links_html + "</p>" - - if info_html != "": - return dict(error=0, error_message='', - info=1, info_message=info_html) - return dict(error=0, error_message='') - - -def sherpa_romeo_journal_validate(field, dummy_form=None): - value = field.data - if value == "" or value.isspace(): - return dict(error=0, error_message='') - - s = SherpaRomeoSearch() - s.search_journal(value, 'exact') - if s.error: - return dict(info=1, info_message=s.error_message) - - if s.get_num_hits() == 1: - issn = s.parser.get_journals(attribute='issn') - if issn != [] and issn is not None: - issn = issn[0] - publisher = s.parser.get_publishers(journal=value) - if publisher is not None and publisher != []: - return dict(error=0, error_message='', - fields=dict(issn=issn, - publisher=publisher['name'])) - return dict(error=0, error_message='', - info=1, info_message="Journal's Publisher not found", - fields=dict(publisher="", issn=issn)) - else: - return dict(info=1, info_message="Couldn't find ISSN") - return dict(error=0, error_message='') - - -def number_validate(field, dummy_form=None, error_message='It must be a number!'): - value = field.data +""" +Validation functions +""" + +import re +from wtforms.validators import ValidationError, StopValidation, Regexp +from invenio.config import CFG_SITE_NAME + + +# +# General purpose validators +# +class RequiredIf(object): + """ + Require field if value of another field is set to a certain value. + """ + def __init__(self, other_field_name, values, message=None): + self.other_field_name = other_field_name + self.values = values + self.message = message + + def __call__(self, form, field): + try: + other_field = getattr(form, self.other_field_name) + other_val = other_field.data + if other_val in self.values: + if not field.data or isinstance(field.data, basestring) \ + and not field.data.strip(): + if self.message is None: + self.message = 'This field is required.' + field.errors[:] = [] + raise StopValidation(self.message % { + 'other_field': other_field.label.text, + 'value': other_val + }) + except AttributeError: + pass + + +# +# DOI-related validators +# +doi_syntax_validator = Regexp( + "(^$|(doi:)?10\.\d+(.\d+)*/.*)", + flags=re.I, + message="The provided DOI is invalid - it should look similar to " + "'10.1234/foo.bar'." +) + +""" +DOI syntax validator +""" + + +class InvalidDOIPrefix(object): + """ + Validates if DOI + """ + def __init__(self, prefix='10.5072', message=None, + message_testing=None): + """ + @param doi_prefix: DOI prefix, e.g. 10.5072 + """ + self.doi_prefix = prefix + # Remove trailing slash + if self.doi_prefix[-1] == '/': + self.doi_prefix = self.doi_prefix[:-1] + + if not message_testing: + self.message_testing = "The prefix 10.5072 is invalid. The prefix" \ + "is only used for testing purposes, and no DOIs with this " \ + "prefix are attached to any meaningful content." + if not message: + self.message = 'The prefix %(prefix)s is ' \ + 'administered automatically by %(CFG_SITE_NAME)s.' + + ctx = dict( + prefix=prefix, + CFG_SITE_NAME=CFG_SITE_NAME + ) + self.message = self.message % ctx + self.message_testing = self.message_testing % ctx + + def __call__(self, form, field): + value = field.data + + # Defined prefix + if value: + if value.startswith("%s/" % self.doi_prefix): + raise ValidationError(self.message) + + # Testing name space + if self.doi_prefix != "10.5072" and value.startswith("10.5072/"): + raise ValidationError(self.message_testing) + + +class PreReservedDOI(object): + """ + Validate that user did not edit pre-reserved DOI. + """ + def __init__(self, field_name, message=None, prefix='10.5072'): + self.field_name = field_name + self.message = message or 'You are not allowed to edit a ' \ + 'pre-reserved DOI. Click the Pre-reserve ' \ + 'DOI button to resolve the problem.' + self.prefix = prefix + + def __call__(self, form, field): + attr_value = getattr(form, self.field_name).data + if attr_value and field.data and field.data != attr_value and field.data.startswith("%s/" % self.prefix): + raise StopValidation(self.message) + # Stop further validation if DOI equals pre-reserved DOI. + if attr_value and field.data and field.data == attr_value: + raise StopValidation() + + +# +# Aliases +# +required_if = RequiredIf +invalid_doi_prefix_validator = InvalidDOIPrefix +pre_reserved_doi_validator = PreReservedDOI + + +def number_validate(form, field, submit=False, error_message='It must be a number!'): + value = field.data or '' if value == "" or value.isspace(): - return dict(error=0, error_message='') + return def is_number(s): try: float(s) return True except ValueError: return False if not is_number(value): try: field.errors.append(error_message) except AttributeError: field.errors = list(field.process_errors) field.errors.append(error_message) - return dict(error=1, - error_message=error_message) - else: - return dict(error=0, error_message='') - - -def record_id_validate(field, form=None): - value = field.data - is_number = number_validate(field)['error'] == 0 \ - and (value != "" or not value.isspace()) - - if is_number: - json_reader = get_record(value) - else: - return dict(error=1, error_message="Record id must be a number!") - if json_reader is not None: - webdeposit_json = form.uncook_json(json_reader, {}, value) - #FIXME: update current json - - return dict(info=1, - info_message='<a href="/record/"' + value + - '>Record</a> loaded successfully', - fields=webdeposit_json) - else: - return dict(info=1, info_message="Record doesn't exist") + field.add_message('error', error_message) + return diff --git a/modules/webdeposit/lib/webdeposit_workflow.py b/modules/webdeposit/lib/webdeposit_workflow.py index e38edea3a..b2e8c895f 100644 --- a/modules/webdeposit/lib/webdeposit_workflow.py +++ b/modules/webdeposit/lib/webdeposit_workflow.py @@ -1,217 +1,229 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from invenio.bibworkflow_engine import BibWorkflowEngine from invenio.bibworkflow_model import Workflow, BibWorkflowObject from invenio.bibworkflow_client import restart_workflow from invenio.bibfield_jsonreader import JsonReader from uuid import uuid1 as new_uuid class DepositionWorkflow(object): """ class for running webdeposit workflows using the BibWorkflow engine The user_id and workflow must always be defined If the workflow has been initialized before, the appropriate uuid must be passed as a parameter. Otherwise a new workflow will be created The workflow functions must have the following structure: def function_name(arg1, arg2): def fun_name2(obj, eng): # do stuff return fun_name2 """ def __init__(self, engine=None, workflow=[], uuid=None, deposition_type=None, user_id=None): self.obj = {} self.set_user_id(user_id) self.set_uuid(uuid) self.deposition_type = deposition_type self.current_step = 0 self.set_engine(engine) self.set_workflow(workflow) self.set_object() def set_uuid(self, uuid=None): """ Sets the uuid or obtains a new one """ if uuid is None: uuid = new_uuid() self.uuid = uuid else: self.uuid = uuid def get_uuid(self): return self.uuid def set_engine(self, engine=None): """ Initializes the BibWorkflow engine """ if engine is None: engine = BibWorkflowEngine(name=self.get_deposition_type(), uuid=self.get_uuid(), user_id=self.get_user_id(), module_name="webdeposit") self.eng = engine self.eng.save() def set_workflow(self, workflow): """ Sets the workflow """ self.eng.setWorkflow(workflow) self.workflow = workflow self.steps_num = len(workflow) self.obj['steps_num'] = self.steps_num def set_object(self): - self.db_workflow_obj = \ - WfeObject.query.filter(WfeObject.workflow_id == self.get_uuid()). \ - first() + db_workflow_objects = \ + WfeObject.query.filter(WfeObject.workflow_id == self.get_uuid()) + try: + self.db_workflow_obj = max(db_workflow_objects.all(), + key=lambda w: w.id) + except ValueError: + self.db_workflow_obj = None + if self.db_workflow_obj is None: self.bib_obj = BibWorkflowObject(data=self.obj, workflow_id=self.get_uuid(), user_id=self.get_user_id()) else: self.bib_obj = BibWorkflowObject(wfobject_id=self.db_workflow_obj.id, workflow_id=self.get_uuid(), user_id=self.get_user_id()) def get_object(self): return self.bib_obj def set_deposition_type(self, deposition_type=None): if deposition_type is not None: self.obj['deposition_type'] = deposition_type def get_deposition_type(self): return self.obj['deposition_type'] deposition_type = property(get_deposition_type, set_deposition_type) def set_user_id(self, user_id=None): if user_id is not None: self.user_id = user_id else: from invenio.webuser_flask import current_user self.user_id = current_user.get_id() self.obj['user_id'] = self.user_id def get_user_id(self): return self.user_id def get_status(self): """ Returns the status of the workflow (check CFG_WORKFLOW_STATUS from bibworkflow_engine) """ status = \ Workflow.query. \ filter(Workflow.uuid == self.get_uuid()).\ one().status return status - def get_output(self, form_validation=None): + def get_data(self, key): + if key in self.bib_obj.data: + return self.bib_obj.data[key] + else: + self.set_object() # refresh the current object and check again + if key in self.bib_obj.data: + return self.bib_obj.data[key] + return None + + def get_output(self, form=None, form_validation=False): """ Returns a representation of the current state of the workflow (a dict with the variables to fill the jinja template) """ + from invenio.webdeposit_utils import get_form, \ + draft_field_get_all + user_id = self.user_id uuid = self.get_uuid() - from invenio.webdeposit_utils import get_form, \ - draft_field_get_all - form = get_form(user_id, uuid) + if form is None: + form = get_form( + user_id, uuid, validate_draft=not form_validation + ) deposition_type = self.obj['deposition_type'] drafts = draft_field_get_all(user_id, deposition_type) if form_validation: form.validate() - # Get the template from configuration for this form - template = form.config.get_template() or 'webdeposit_add.html' + # Get the template for this form + template = form.get_template() return dict(template_name_or_list=template, workflow=self, deposition_type=deposition_type, form=form, drafts=drafts, uuid=uuid) def run(self): """ Runs or resumes the workflow """ finished = self.eng.db_obj.counter_finished > 1 if finished: # The workflow is finished, nothing to do return wfobjects = \ WfeObject.query. \ filter(WfeObject.workflow_id == self.get_uuid()) wfobject = max(wfobjects.all(), key=lambda w: w.modified) starting_point = wfobject.task_counter restart_workflow(self.eng, [self.bib_obj], starting_point, stop_on_halt=True) def run_next_step(self): if self.current_step >= self.steps_num: self.obj['break'] = True return function = self.workflow[self.current_step] function(self.obj, self) self.current_step += 1 self.obj['step'] = self.current_step def jump_forward(self): restart_workflow(self.eng, [self.bib_obj], 'next', stop_on_halt=True) def jump_backwards(self, dummy_synchronize=False): if self.current_step > 1: self.current_step -= 1 else: self.current_step = 1 def get_workflow_from_db(self): return Workflow.query.filter(Workflow.uuid == self.get_uuid()).first() def cook_json(self): user_id = self.obj['user_id'] uuid = self.get_uuid() from invenio.webdeposit_utils import get_form json_reader = JsonReader() for step in range(self.steps_num): try: form = get_form(user_id, uuid, step) json_reader = form.cook_json(json_reader) except: # some steps don't have any form ... pass return json_reader - - def get_data(self, key): - if key in self.bib_obj.data: - return self.bib_obj.data[key] - else: - return None diff --git a/modules/webdeposit/lib/webdeposit_workflow_utils.py b/modules/webdeposit/lib/webdeposit_workflow_utils.py index 1c693ca91..ea07909ca 100644 --- a/modules/webdeposit/lib/webdeposit_workflow_utils.py +++ b/modules/webdeposit/lib/webdeposit_workflow_utils.py @@ -1,141 +1,228 @@ # -*- coding: utf-8 -*- ## ## This file is part of Invenio. ## Copyright (C) 2012, 2013 CERN. ## ## Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. import os import time -from sqlalchemy import func +from datetime import datetime from invenio.sqlalchemyutils import db -from invenio.webdeposit_config_utils import WebDepositConfiguration -from invenio.webdeposit_model import WebDepositDraft from invenio.bibworkflow_model import Workflow from invenio.bibfield_jsonreader import JsonReader from tempfile import mkstemp from invenio.bibtask import task_low_level_submission -from invenio.config import CFG_TMPSHAREDDIR +from invenio.config import CFG_TMPSHAREDDIR, CFG_PREFIX +from invenio.dbquery import run_sql """ Functions to implement workflows The workflow functions must have the following structure: def function_name(arg1, arg2): def fun_name2(obj, eng): # do stuff return fun_name2 """ def authorize_user(user_id=None): def user_auth(obj, dummy_eng): if user_id is not None: obj.data['user_id'] = user_id else: from invenio.webuser_flask import current_user obj.data['user_id'] = current_user.get_id() return user_auth +def populate_form_data(form_data): + """ Pass a json to initialize the values of the forms. + If two forms use the same name for a field, + the value is passed to the one that is rendered first.""" + def populate(obj, eng): + obj.data['form_values'] = form_data + return populate + + def render_form(form): def render(obj, eng): + from invenio.webdeposit_utils import get_last_step, CFG_DRAFT_STATUS, \ + add_draft, get_preingested_form_data, preingest_form_data + uuid = eng.uuid - # TODO: get the current step from the object - step = max(obj.db_obj.task_counter) # data['step'] + user_id = obj.data['user_id'] + #TODO: create out of the getCurrTaskId() which is a list + # an incremental key that represents also steps in complex workflows. + step = get_last_step(eng.getCurrTaskId()) form_type = form.__name__ - from invenio.webdeposit_utils import CFG_DRAFT_STATUS - webdeposit_draft = WebDepositDraft(uuid=uuid, - form_type=form_type, - form_values={}, - step=step, - status=CFG_DRAFT_STATUS['unfinished'], - timestamp=func.current_timestamp()) - db.session.add(webdeposit_draft) - db.session.commit() + + if obj.data.has_key('form_values') and obj.data['form_values'] is not None: + form_values = obj.data['form_values'] + else: + form_values = {} + # Prefill the form from cache + cached_form = get_preingested_form_data(user_id, cached_data=True) + + # Check for preingested data from webdeposit API + preingested_form_data = get_preingested_form_data(user_id, uuid) + if preingested_form_data != {} and preingested_form_data is not None: + form_data = preingested_form_data + elif cached_form is not None: + form_data = cached_form + # Clear cache + preingest_form_data(user_id, None, cached_data=True) + else: + form_data = {} + + # Filter the form_data to match the current form + for field in form(): + if field.name in form_data: + form_values[field.name] = form_data[field.name] + + draft = dict(form_type=form_type, + form_values=form_values, + status=CFG_DRAFT_STATUS['unfinished'], + timestamp=str(datetime.now()), + step=step) + + Workflow.set_extra_data(user_id=user_id, uuid=uuid, + setter=add_draft(draft)) + render.__form_type__ = form.__name__ return render def wait_for_submission(): def wait(obj, eng): user_id = obj.data['user_id'] uuid = eng.uuid from invenio.webdeposit_utils import CFG_DRAFT_STATUS, get_form_status status = get_form_status(user_id, uuid) if status == CFG_DRAFT_STATUS['unfinished']: # If form is unfinished stop the workflow eng.halt('Waiting for form submission.') else: # If form is completed, continue with next step eng.jumpCallForward(1) return wait def export_marc_from_json(): + """ Exports marc from json using BibField """ def export(obj, eng): user_id = obj.data['user_id'] uuid = eng.uuid steps_num = obj.data['steps_num'] from invenio.webdeposit_utils import get_form json_reader = JsonReader() + + try: + pop_obj = Workflow.get_extra_data(user_id=user_id, uuid=uuid, + key='pop_obj') + except KeyError: + pop_obj = None + + form_data = {} + if 'form_values' in obj.data or pop_obj is not None: + + # copy the form values to be able to + # delete the fields in the workflow object during iteration + form_data = pop_obj or obj.data['form_values'] + + # Populate the form with data for step in range(steps_num): form = get_form(user_id, uuid, step) + # Insert the fields' values in bibfield's rec_json dictionary if form is not None: # some steps don't have any form ... + # Populate preingested data + for field in form: + if field.name in form_data: + field.data = form_data.pop(field.name) json_reader = form.cook_json(json_reader) deposition_type = \ db.session.query(Workflow.name).\ filter(Workflow.user_id == user_id, Workflow.uuid == uuid).\ one()[0] # Get the collection from configuration - deposition_conf = WebDepositConfiguration(deposition_type=deposition_type) - # or if it's not there, name the collection after the deposition type - json_reader['collection.primary'] = \ - deposition_conf.get_collection() or deposition_type - - if 'recid' in json_reader or 'record ID' in json_reader: - obj.data['update_record'] = True + # FIXME: Collection should be fully configurable. + json_reader['collection.primary'] = deposition_type + + if 'recid' not in json_reader or 'record ID' not in json_reader: + # Record is new, reserve record id + recid = run_sql("INSERT INTO bibrec (creation_date, modification_date) VALUES (NOW(), NOW())") + json_reader['recid'] = recid + obj.data['recid'] = recid else: - obj.data['update_record'] = False + obj.data['recid'] = json_reader['recid'] + obj.data['title'] = json_reader['title.title'] + + workflow = Workflow.query.filter(Workflow.uuid == uuid).one() + workflow.extra_data['recid'] = obj.data['recid'] + Workflow.query.\ + filter(Workflow.uuid == uuid).\ + update({'extra_data': workflow.extra_data}) + marc = json_reader.legacy_export_as_marc() obj.data['marc'] = marc return export def create_record_from_marc(): + """ Generates the record from marc. + The function requires the marc to be generated, + so the function export_marc_from_json must have been called successfully + before + """ def create(obj, dummy_eng): marc = obj.data['marc'] tmp_file_fd, tmp_file_name = mkstemp(suffix='.marcxml', prefix="webdeposit_%s" % time.strftime("%Y-%m-%d_%H:%M:%S"), dir=CFG_TMPSHAREDDIR) os.write(tmp_file_fd, marc) os.close(tmp_file_fd) os.chmod(tmp_file_name, 0644) - if obj.data['update_record']: - obj.data['task_id'] = task_low_level_submission('bibupload', - 'webdeposit', '-r', - tmp_file_name) - else: - obj.data['task_id'] = task_low_level_submission('bibupload', - 'webdeposit', '-i', - tmp_file_name) + obj.data['task_id'] = task_low_level_submission('bibupload', + 'webdeposit', '-r', + tmp_file_name) return create + + +def bibindex(): + """ Runs BibIndex """ + def bibindex_task(obj, eng): + cmd = "%s/bin/bibindex -u admin" % CFG_PREFIX + if os.system(cmd): + eng.log.error("BibIndex task failed.") + + return bibindex_task + + +def webcoll(): + """ Runs WebColl """ + def webcoll_task(obj, eng): + cmd = "%s/bin/webcoll -u admin" % CFG_PREFIX + if os.system(cmd): + eng.log.error("WebColl task failed.") + + return webcoll_task