The goal of this tutorial is to learn how we can build a new simple form to be able to deposit new records. The invenio-deposit
module is not in the scope of this exercise.
- Table of Contents
- Step 1: Bootstrap exercise
- Step 2: Create a simple form
- Step 3: Display the form
- Step 4: Create the submitted record
- Step 5: Update the entrypoints
- Step 6: Try it
- What did we learn
We now have to enable users to deposit new records. For this exercise, we won't use what invenio-records-rest
already provides out-of-the-box, but we will implement a custom view.
We will need:
- to render a simple form to create a record, where the user can input the value of each field
- a new view where to post the form, validate the input and create the new record
- a new view to display a success message or an error
If you completed the previous tutorial, you can skip this step. If instead you would like to start from a clean state run the following commands:
cd ~/src/training/
./start-from.sh 08-data-models-from-scratch
We are going to create a new module in our project which will contain all our files.
Let's create a new module folder: my-site/my_site/deposit/
. When creating a new module, do not forget to add __init__.py
:
my-site/my_site/deposit/__init__.py
"""Deposit module."""
Then, we create the form, using Flask-WTF.
my-site/my_site/deposit/forms.py
"""Simple deposit form module."""
from __future__ import absolute_import, print_function
from flask_wtf import FlaskForm
from wtforms import StringField, validators
class RecordForm(FlaskForm):
"""A simple deposit form."""
title = StringField(
'Title', [validators.DataRequired()]
)
contributor_name = StringField(
'Name of the contributor', [validators.DataRequired()]
)
We have to create a new view for the form. We will take advantage of the HTTP verbs GET
, to render the form, and POST
, to handle the submitted data. We are also going to protect the view with the login_required
decorator: in this way, Invenio will redirect to the login view if the user is anonymous and we can then set the owner of the newly created record to the id of the logged in user.
my-site/my_site/deposit/views.py
"""Views for deposit of records."""
from __future__ import absolute_import, print_function
from flask import Blueprint, redirect, render_template, url_for
from flask_login import login_required
from flask_security import current_user
from .forms import RecordForm
from .api import create_record
# define a new Flask Blueprint that is registered under the url path /deposit
blueprint = Blueprint(
'deposit',
__name__,
url_prefix='/deposit',
template_folder='templates',
static_folder='static',
)
@blueprint.route('/create', methods=('GET', 'POST'))
@login_required
def create():
"""The create view."""
form = RecordForm()
# if the form is submitted and valid
if form.validate_on_submit():
# we create one contributor object with the submitted name
contributors = [dict(name=form.contributor_name.data)]
# set the owner as the current logged in user
owner = int(current_user.get_id())
# create the record
create_record(
dict(
title=form.title.data,
contributors=contributors,
owner=owner,
)
)
# redirect to the success page
return redirect(url_for('deposit.success'))
return render_template('deposit/create.html', form=form)
@blueprint.route("/success")
@login_required
def success():
"""The success view."""
return render_template('deposit/success.html')
Templates creation: create a folder templates
and a sub folder deposit
(the name of the blueprint). Then, inside, two html files (our Jinja templates): create.html
and success.html
.
my-site/my_site/deposit/templates/deposit/create.html
{%- extends config.BASE_TEMPLATE %}
{% macro errors(field) %}
{% if field.errors %}
<span class="help-block">
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</span>
{% endmacro %}
{% block page_body %}
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Create record</h2>
</div>
<div class="col-md-offset-3 col-md-6 well">
<form action="{{ url_for('deposit.create') }}" method="POST">
<div class="form-group {{ 'has-error' if form.title.errors }}">
<label for="title">{{ form.title.label }}</label>
{{ form.title(class_="form-control")|safe }}
{{ errors(form.title) }}
</div>
<div class="form-group {{ 'has-error' if form.contributor_name.errors }}">
<label for="contributor_name">{{ form.contributor_name.label }}</label>
{{ form.contributor_name(class_="form-control")|safe }}
{{ errors(form.contributor_name) }}
</div>
{{ form.csrf_token }}
{{ errors(form.csrf_token) }}
<button type="submit" class="btn btn-default">Create</button>
</form>
</div>
</div>
</div>
{% endblock page_body %}
my-site/my_site/deposit/templates/deposit/success.html
{%- extends config.BASE_TEMPLATE %}
{% block page_body %}
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="alert alert-success">
<b>Success!</b>
</div>
<a href="{{ url_for('deposit.create') }}" class="btn btn-warning">Create more</a>
</div>
</div>
</div>
{% endblock page_body %}
We will now implement the create_record
method: its responsibilities are to create the record in the database, mint a new persistent identifier and index it to make it searchable.
my-site/my_site/deposit/api.py
"""Deposit APIs."""
from __future__ import absolute_import, print_function
import uuid
from flask import current_app
from invenio_db import db
from invenio_indexer.api import RecordIndexer
from invenio_pidstore import current_pidstore
from invenio_records.api import Record
def create_record(data):
"""Create a record.
:param dict data: The record data.
"""
with db.session.begin_nested():
# create uuid
rec_uuid = uuid.uuid4()
# create PID
current_pidstore.minters['recid'](rec_uuid, data)
# create record
created_record = Record.create(data, id_=rec_uuid)
# index the record
RecordIndexer().index(created_record)
db.session.commit()
Last step: add the new view in the app entrypoints. When the Invenio app will run, it will find the new blueprint and register its routes.
Add this in my-site/setup.py
:
'invenio_base.blueprints': [
'my_site = my_site.theme.views:blueprint',
'my_site_records = my_site.records.views:blueprint',
+ 'my_site_deposit = my_site.deposit.views:blueprint',
],
'invenio_assets.webpack': [
'my_site_theme = my_site.theme.webpack:theme',
],
Re-install the app to register the entrypoints:
pipenv run pip install -e .
Finally, let's create an user to access to the protected form.
pipenv run my-site users create [email protected] -a --password=123456
Ensure docker-compose
is running and restart the server (if not done already automatically) and visit https://127.0.0.1:5000/deposit/create:
./scripts/server
firefox https://127.0.0.1:5000/deposit/create
Login with the credentials set before: username [email protected]
and password 123456
.
Now, create a record by entering some data and submitting the form.
Hit the Create
button! You should see the Success
message.
Finally, you can visit https://127.0.0.1:5000/api/records/?prettyprint=1 to verify it is indexed correctly:
firefox https://127.0.0.1:5000/api/records/?prettyprint=1
- We have seen how to create a new view with templates
- We have seen how to protect a view requiring login
- We have built a new form with validation
- We learned how to create a record minting its PID and then index it