diff --git a/app/main/forms.py b/app/main/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..e542e5b207f26b4dcc0561928f57efc28d864f5e --- /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 fecefe0ddd656d750727048a9acb03c79c46eadd..f5f77c65cb5b683a6771d623781094296cd26ced 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 0000000000000000000000000000000000000000..0102a00c83e739993659de4d2d21aeb59a645241 --- /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 006674ca94419e067c552db75d674855acc57636..9580474f148e848c957a12da7e69ad50d059551f 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 127344de748e6242be5df6fd12b5942803abaebf..50c2ff0290a6e3d2dc1cfc7c5fa76d61210e835b 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 0000000000000000000000000000000000000000..3fc61b6a4b430bbfc6e0c6c88363831d72a51719 --- /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 1ea13faa0fbad4e49cbeb8290779cd24df97cdc8..8365bcf318415c4949ba9ee03b1cafd3e3868f2e 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