From 99536bddfd5f0e686669e3dacc4b3a06830eed4b Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Tue, 5 Sep 2017 16:59:20 +0200 Subject: [PATCH] Add page to create QR codes This can be done via the Admin interface but this latter shall be restricted to admin users. This simple page allows the group "create" to easily add new Manufacturer, Model or Location. --- app/main/forms.py | 25 +++++++++++++++++++++ app/main/views.py | 36 ++++++++++++++++++++++++++++++- app/static/js/qrcodes.js | 19 ++++++++++++++++ app/templates/_helpers.html | 4 ++-- app/templates/base.html | 16 ++++++++++---- app/templates/create_qrcodes.html | 34 +++++++++++++++++++++++++++++ app/utils.py | 11 ++++++++++ 7 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 app/main/forms.py create mode 100644 app/static/js/qrcodes.js create mode 100644 app/templates/create_qrcodes.html diff --git a/app/main/forms.py b/app/main/forms.py new file mode 100644 index 0000000..e542e5b --- /dev/null +++ b/app/main/forms.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +""" +app.main.forms +~~~~~~~~~~~~~~ + +This module defines the main forms. + +:copyright: (c) 2017 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" +from flask_wtf import FlaskForm +from wtforms import SelectField, StringField, validators +from .. import utils + + +class QRCodeForm(FlaskForm): + kind = SelectField('Kind') + name = StringField('name', validators=[validators.DataRequired()]) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.kind.choices = utils.get_choices( + ('Manufacturer', 'Model', 'Location') + ) diff --git a/app/main/views.py b/app/main/views.py index fecefe0..f5f77c6 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -9,10 +9,14 @@ This module implements the main blueprint. :license: BSD 2-Clause, see LICENSE for more details. """ -from flask import Blueprint, render_template, jsonify +import sqlalchemy as sa +from flask import (Blueprint, render_template, jsonify, + redirect, url_for, request, flash) from flask_login import login_required +from .forms import QRCodeForm from ..extensions import db from ..models import Action, Manufacturer, Model, Location, Status, Item +from ..decorators import login_groups_accepted from .. import utils bp = Blueprint('main', __name__) @@ -70,3 +74,33 @@ def qrcodes(): for item in items] codes[model.__name__] = images return render_template('qrcodes.html', codes=codes) + + +@bp.route('/qrcodes/create', methods=('GET', 'POST')) +@login_groups_accepted('admin', 'create') +def create_qrcodes(): + kind = request.args.get('kind', 'Manufacturer') + form = QRCodeForm(request.form, kind=kind) + if form.validate_on_submit(): + model = globals()[form.kind.data] + new_model = model(name=form.name.data) + db.session.add(new_model) + try: + db.session.commit() + except sa.exc.IntegrityError as e: + db.session.rollback() + flash(f'{form.name.data} already exists! Item not created.', 'error') + return redirect(url_for('main.create_qrcodes', kind=form.kind.data)) + return render_template('create_qrcodes.html', form=form) + + +@bp.route('/_retrieve_qrcodes_name/<kind>') +@login_required +def retrieve_qrcodes_name(kind): + try: + model = globals()[kind] + except KeyError: + raise utils.InventoryError(f"Unknown model '{kind}'", status_code=422) + items = db.session.query(model).order_by(model.name) + data = [[item.name] for item in items] + return jsonify(data=data) diff --git a/app/static/js/qrcodes.js b/app/static/js/qrcodes.js new file mode 100644 index 0000000..0102a00 --- /dev/null +++ b/app/static/js/qrcodes.js @@ -0,0 +1,19 @@ +$(document).ready(function() { + + var qrcodes_table = $("#qrcodes_table").DataTable({ + "ajax": function(data, callback, settings) { + var kind = $("#kind").val(); + $.getJSON( + $SCRIPT_ROOT + "/_retrieve_qrcodes_name/" + kind, + function(json) { + callback(json); + }); + }, + "paging": false + }); + + $("#kind").on('change', function() { + qrcodes_table.ajax.reload(); + }); + +}); diff --git a/app/templates/_helpers.html b/app/templates/_helpers.html index 006674c..9580474 100644 --- a/app/templates/_helpers.html +++ b/app/templates/_helpers.html @@ -1,3 +1,3 @@ -{% macro nav_a(active) -%} -<a class="nav-item nav-link{% if active %} active{% endif %}" +{% macro is_active(active) -%} +{% if active %}active{% endif %} {%- endmacro %} diff --git a/app/templates/base.html b/app/templates/base.html index 127344d..50c2ff0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,6 +1,6 @@ {%- extends "bootstrap/base.html" %} {% import "bootstrap/utils.html" as utils %} -{% from "_helpers.html" import nav_a %} +{% from "_helpers.html" import is_active %} {% block styles %} {{super()}} @@ -23,15 +23,23 @@ <div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="navbar-nav mr-auto"> {% set path = request.path %} - {{ nav_a(path in ("/", "/index", "/index/")) }} href="{{ url_for('main.index') }}">Index</a> - {{ nav_a(path == "/qrcodes") }} href="{{ url_for('main.qrcodes') }}">QR Codes</a> + <a class="nav-item nav-link {{ is_active(path in ("/", "/index", "/index/")) }}" href="{{ url_for('main.index') }}">Index</a> + <div class="dropdown {{ is_active(path.startswith("/qrcodes")) }}"> + <a class="nav-link dropdown-toggle" href="#" id="qrcodesDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + QR Codes + </a> + <div class="dropdown-menu" aria-labelledby="qrcodesDropdownMenuLink"> + <a class="dropdown-item" href="{{ url_for('main.qrcodes') }}">View</a> + <a class="dropdown-item" href="{{ url_for('main.create_qrcodes') }}">Create</a> + </div> + </div> {% if current_user.is_authenticated and current_user.is_admin %} <a class="nav-item nav-link" href="{{ url_for('admin.index') }}">Admin</a> {% endif %} </div> <div class="navbar-nav"> {% if current_user.is_authenticated %} - <div class="dropdown"> + <div class="dropdown {{ is_active(path == "/profile") }}"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {{current_user}} </a> diff --git a/app/templates/create_qrcodes.html b/app/templates/create_qrcodes.html new file mode 100644 index 0000000..3fc61b6 --- /dev/null +++ b/app/templates/create_qrcodes.html @@ -0,0 +1,34 @@ +{%- extends "base.html" %} +{% import "bootstrap/wtf.html" as wtf %} + +{% block title %}Create QR Codes{% endblock %} + +{% block main %} + <h2>Create QR Codes</h2> + + <form method="POST" class="form-inline"> + {{ form.csrf_token }} + + {{ form.kind.label(class="sr-only") | safe }} + {{ form.kind(class="form-control mb-2 mr-sm-2 mb-sm-0") }} + + {{ form.name.label(class="sr-only") | safe }} + {{ form.name(class="form-control mb-2 mr-sm-2 mb-sm-0", placeholder="name", required=True) }} + + <button type="submit" class="btn btn-primary">Create</button> + </form> + + <hr class="separator"> + + <table id="qrcodes_table" class="table table-bordered table-hover table-sm"> + <thead> + <tr> + <th>Name</th> + </tr> + </thead> + </table> +{%- endblock %} + +{% block inventory_scripts %} + <script src="{{ url_for('static', filename='js/qrcodes.js') }}"></script> +{% endblock %} diff --git a/app/utils.py b/app/utils.py index 1ea13fa..8365bcf 100644 --- a/app/utils.py +++ b/app/utils.py @@ -92,3 +92,14 @@ def attribute_to_string(value): return value[0] else: return value + + +def get_choices(iterable, allow_blank=False, allow_null=False): + """Return a list of (value, label)""" + choices = [] + if allow_blank: + choices = [('', '')] + if allow_null: + choices.append(('null', 'not set')) + choices.extend([(val, val) for val in iterable]) + return choices -- GitLab