From 014f373486290b91f89878eaf22db65cf6e01154 Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Wed, 13 Dec 2017 13:53:34 +0100 Subject: [PATCH] Split main blueprint main blueprint splitted in: - inventory - networks --- app/factory.py | 6 +- app/{main => inventory}/__init__.py | 0 app/inventory/forms.py | 40 ++++++++ app/{main => inventory}/views.py | 93 +++---------------- app/networks/__init__.py | 0 app/{main => networks}/forms.py | 32 +------ app/networks/views.py | 86 +++++++++++++++++ app/templates/_helpers.html | 2 +- app/templates/admin/index.html | 2 +- app/templates/base.html | 10 +- app/templates/{ => inventory}/attributes.html | 2 +- .../{ => inventory}/create_item.html | 4 +- app/templates/{ => inventory}/items.html | 4 +- app/templates/{ => inventory}/qrcodes.html | 2 +- app/templates/{ => inventory}/view_item.html | 6 +- app/templates/{ => networks}/create_host.html | 4 +- app/templates/{ => networks}/hosts.html | 4 +- app/users/views.py | 2 +- 18 files changed, 168 insertions(+), 131 deletions(-) rename app/{main => inventory}/__init__.py (100%) create mode 100644 app/inventory/forms.py rename app/{main => inventory}/views.py (58%) create mode 100644 app/networks/__init__.py rename app/{main => networks}/forms.py (55%) create mode 100644 app/networks/views.py rename app/templates/{ => inventory}/attributes.html (91%) rename app/templates/{ => inventory}/create_item.html (82%) rename app/templates/{ => inventory}/items.html (79%) rename app/templates/{ => inventory}/qrcodes.html (90%) rename app/templates/{ => inventory}/view_item.html (88%) rename app/templates/{ => networks}/create_host.html (81%) rename app/templates/{ => networks}/hosts.html (76%) diff --git a/app/factory.py b/app/factory.py index a29a72b..e337294 100644 --- a/app/factory.py +++ b/app/factory.py @@ -16,7 +16,8 @@ from . import settings, models from .extensions import db, migrate, login_manager, ldap_manager, bootstrap, admin, mail, jwt, toolbar from .admin.views import (AdminModelView, ItemAdmin, UserAdmin, GroupAdmin, TokenAdmin, NetworkAdmin) -from .main.views import bp as main +from .inventory.views import bp as inventory +from .networks.views import bp as networks from .users.views import bp as users from .api.main import bp as api from .defaults import defaults @@ -108,7 +109,8 @@ def create_app(config=None): admin.add_view(AdminModelView(models.Mac, db.session)) admin.add_view(AdminModelView(models.Cname, db.session)) - app.register_blueprint(main) + app.register_blueprint(inventory) + app.register_blueprint(networks) app.register_blueprint(users) app.register_blueprint(api, url_prefix='/api') diff --git a/app/main/__init__.py b/app/inventory/__init__.py similarity index 100% rename from app/main/__init__.py rename to app/inventory/__init__.py diff --git a/app/inventory/forms.py b/app/inventory/forms.py new file mode 100644 index 0000000..ff89563 --- /dev/null +++ b/app/inventory/forms.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +app.inventory.forms +~~~~~~~~~~~~~~~~~~~ + +This module defines the inventory blueprint 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, SelectMultipleField, validators +from .. import utils, models + + +class AttributeForm(FlaskForm): + name = StringField('name', validators=[validators.DataRequired()]) + + +class ItemForm(FlaskForm): + ics_id = StringField('ICS id', + validators=[validators.InputRequired(), + validators.Regexp(models.ICS_ID_RE)]) + serial_number = StringField('Serial number', + validators=[validators.InputRequired()]) + manufacturer_id = SelectField('Manufacturer') + model_id = SelectField('Model') + location_id = SelectField('Location') + status_id = SelectField('Status') + parent_id = SelectField('Parent') + # macs = SelectMultipleField('Macs') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.manufacturer_id.choices = utils.get_model_choices(models.Manufacturer, allow_blank=True) + self.model_id.choices = utils.get_model_choices(models.Model, allow_blank=True) + self.location_id.choices = utils.get_model_choices(models.Location, allow_blank=True) + self.status_id.choices = utils.get_model_choices(models.Status, allow_blank=True) + self.parent_id.choices = utils.get_model_choices(models.Item, allow_blank=True, attr='ics_id') diff --git a/app/main/views.py b/app/inventory/views.py similarity index 58% rename from app/main/views.py rename to app/inventory/views.py index 7decb19..6347dd4 100644 --- a/app/main/views.py +++ b/app/inventory/views.py @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- """ -app.main.views -~~~~~~~~~~~~~~ +app.inventory.views +~~~~~~~~~~~~~~~~~~~ -This module implements the main blueprint. +This module implements the inventory blueprint. :copyright: (c) 2017 European Spallation Source ERIC :license: BSD 2-Clause, see LICENSE for more details. """ import sqlalchemy as sa -from flask import (Blueprint, render_template, jsonify, session, - redirect, url_for, request, flash, current_app) +from flask import (Blueprint, render_template, jsonify, + redirect, url_for, flash, current_app) from flask_login import login_required -from .forms import AttributeForm, HostForm, ItemForm +from .forms import AttributeForm, ItemForm from ..extensions import db from ..decorators import login_groups_accepted from .. import utils, models -bp = Blueprint('main', __name__) +bp = Blueprint('inventory', __name__) # Declare custom error handlers for all views @@ -66,7 +66,7 @@ def index(): @bp.route('/items') @login_required def list_items(): - return render_template('items.html') + return render_template('inventory/items.html') @bp.route('/items/create', methods=('GET', 'POST')) @@ -91,15 +91,15 @@ def create_item(): flash(f'{e}', 'error') else: flash(f'Item {item} created!', 'success') - return redirect(url_for('main.create_item')) - return render_template('create_item.html', form=form) + return redirect(url_for('inventory.create_item')) + return render_template('inventory/create_item.html', form=form) @bp.route('/items/view/<ics_id>') @login_required def view_item(ics_id): item = models.Item.query.filter_by(ics_id=ics_id).first_or_404() - return render_template('view_item.html', item=item.to_dict(long=True)) + return render_template('inventory/view_item.html', item=item.to_dict(long=True)) @bp.route('/qrcodes/<kind>') @@ -113,7 +113,7 @@ def qrcodes(kind='Action'): images = [{'name': item.name, 'data': utils.image_to_base64(item.image())} for item in items] - return render_template('qrcodes.html', kind=kind, images=images) + return render_template('inventory/qrcodes.html', kind=kind, images=images) @bp.route('/attributes/<kind>', methods=('GET', 'POST')) @@ -131,8 +131,8 @@ def attributes(kind): flash(f'{form.name.data} already exists! {kind} not created.', 'error') else: flash(f'{kind} {new_model} created!', 'success') - return redirect(url_for('main.attributes', kind=kind)) - return render_template('attributes.html', kind=kind, form=form) + return redirect(url_for('inventory.attributes', kind=kind)) + return render_template('inventory/attributes.html', kind=kind, form=form) @bp.route('/_retrieve_attributes_name/<kind>') @@ -145,68 +145,3 @@ def retrieve_attributes_name(kind): items = db.session.query(model).order_by(model.name) data = [[item.name] for item in items] return jsonify(data=data) - - -@bp.route('/hosts') -@login_required -def list_hosts(): - return render_template('hosts.html') - - -@bp.route('/hosts/create', methods=('GET', 'POST')) -@login_groups_accepted('admin', 'create') -def create_host(): - # Try to get the network_id from the session - # to pre-fill the form with the same network - try: - network_id = session['network_id'] - except KeyError: - # No need to pass request.form when no extra keywords are given - form = HostForm() - else: - form = HostForm(request.form, network_id=network_id) - if form.validate_on_submit(): - network_id = form.network_id.data - host = models.Host(ip=form.ip.data, - network_id=form.network_id.data, - name=form.name.data, - description=form.description.data or None, - mac_id=form.mac_id.data or None) - current_app.logger.debug(f'Trying to create: {host!r}') - db.session.add(host) - try: - db.session.commit() - except sa.exc.IntegrityError as e: - db.session.rollback() - current_app.logger.warning(f'{e}') - flash(f'{e}', 'error') - else: - flash(f'Host {host} created!', 'success') - # Save network_id to the session to retrieve it after the redirect - session['network_id'] = host.network_id - return redirect(url_for('main.create_host')) - return render_template('create_host.html', form=form) - - -@bp.route('/_retrieve_hosts') -@login_required -def retrieve_hosts(): - data = [(host.name, - host.ip, - host.description, - str(host.mac), - host.network.vlan_name) for host in models.Host.query.all()] - return jsonify(data=data) - - -@bp.route('/_retrieve_available_ips/<network_id>') -@login_required -def retrieve_available_ips(network_id): - try: - network = models.Network.query.get(network_id) - except sa.exc.DataError: - current_app.logger.warning(f'Invalid network_id: {network_id}') - data = [] - else: - data = [str(address) for address in network.available_ips()] - return jsonify(data=data) diff --git a/app/networks/__init__.py b/app/networks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main/forms.py b/app/networks/forms.py similarity index 55% rename from app/main/forms.py rename to app/networks/forms.py index 2b3e20b..0cbba4b 100644 --- a/app/main/forms.py +++ b/app/networks/forms.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ -app.main.forms -~~~~~~~~~~~~~~ +app.networks.forms +~~~~~~~~~~~~~~~~~~ -This module defines the main forms. +This module defines the networks blueprint forms. :copyright: (c) 2017 European Spallation Source ERIC :license: BSD 2-Clause, see LICENSE for more details. @@ -26,32 +26,6 @@ class NoValidateSelectField(SelectField): pass -class AttributeForm(FlaskForm): - name = StringField('name', validators=[validators.DataRequired()]) - - -class ItemForm(FlaskForm): - ics_id = StringField('ICS id', - validators=[validators.InputRequired(), - validators.Regexp(models.ICS_ID_RE)]) - serial_number = StringField('Serial number', - validators=[validators.InputRequired()]) - manufacturer_id = SelectField('Manufacturer') - model_id = SelectField('Model') - location_id = SelectField('Location') - status_id = SelectField('Status') - parent_id = SelectField('Parent') - # macs = SelectMultipleField('Macs') - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.manufacturer_id.choices = utils.get_model_choices(models.Manufacturer, allow_blank=True) - self.model_id.choices = utils.get_model_choices(models.Model, allow_blank=True) - self.location_id.choices = utils.get_model_choices(models.Location, allow_blank=True) - self.status_id.choices = utils.get_model_choices(models.Status, allow_blank=True) - self.parent_id.choices = utils.get_model_choices(models.Item, allow_blank=True, attr='ics_id') - - class HostForm(FlaskForm): network_id = SelectField('Network') # The list of IPs is dynamically created on the browser side diff --git a/app/networks/views.py b/app/networks/views.py new file mode 100644 index 0000000..e0fd3b3 --- /dev/null +++ b/app/networks/views.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +""" +app.networks.views +~~~~~~~~~~~~~~~~~~ + +This module implements the networks blueprint. + +:copyright: (c) 2017 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" +import sqlalchemy as sa +from flask import (Blueprint, render_template, jsonify, session, + redirect, url_for, request, flash, current_app) +from flask_login import login_required +from .forms import HostForm +from ..extensions import db +from ..decorators import login_groups_accepted +from .. import models + +bp = Blueprint('networks', __name__) + + +@bp.route('/hosts') +@login_required +def list_hosts(): + return render_template('networks/hosts.html') + + +@bp.route('/hosts/create', methods=('GET', 'POST')) +@login_groups_accepted('admin', 'create') +def create_host(): + # Try to get the network_id from the session + # to pre-fill the form with the same network + try: + network_id = session['network_id'] + except KeyError: + # No need to pass request.form when no extra keywords are given + form = HostForm() + else: + form = HostForm(request.form, network_id=network_id) + if form.validate_on_submit(): + network_id = form.network_id.data + host = models.Host(ip=form.ip.data, + network_id=form.network_id.data, + name=form.name.data, + description=form.description.data or None, + mac_id=form.mac_id.data or None) + current_app.logger.debug(f'Trying to create: {host!r}') + db.session.add(host) + try: + db.session.commit() + except sa.exc.IntegrityError as e: + db.session.rollback() + current_app.logger.warning(f'{e}') + flash(f'{e}', 'error') + else: + flash(f'Host {host} created!', 'success') + # Save network_id to the session to retrieve it after the redirect + session['network_id'] = host.network_id + return redirect(url_for('networks.create_host')) + return render_template('networks/create_host.html', form=form) + + +@bp.route('/_retrieve_hosts') +@login_required +def retrieve_hosts(): + data = [(host.name, + host.ip, + host.description, + str(host.mac), + host.network.vlan_name) for host in models.Host.query.all()] + return jsonify(data=data) + + +@bp.route('/_retrieve_available_ips/<network_id>') +@login_required +def retrieve_available_ips(network_id): + try: + network = models.Network.query.get(network_id) + except sa.exc.DataError: + current_app.logger.warning(f'Invalid network_id: {network_id}') + data = [] + else: + data = [str(address) for address in network.available_ips()] + return jsonify(data=data) diff --git a/app/templates/_helpers.html b/app/templates/_helpers.html index 22aff7a..d8c2352 100644 --- a/app/templates/_helpers.html +++ b/app/templates/_helpers.html @@ -4,7 +4,7 @@ {% macro link_to_item(ics_id) -%} {% if ics_id %} - <a href="{{ url_for('main.view_item', ics_id=ics_id) }}">{{ ics_id }}</a> + <a href="{{ url_for('inventory.view_item', ics_id=ics_id) }}">{{ ics_id }}</a> {% else %} {{ ics_id }} {% endif %} diff --git a/app/templates/admin/index.html b/app/templates/admin/index.html index 996ac40..03782d6 100644 --- a/app/templates/admin/index.html +++ b/app/templates/admin/index.html @@ -6,5 +6,5 @@ <h2>Use the admin interface with care</h2> <!-- flask-admin uses bootstrap 3 --> -<a class="btn btn-primary" href="{{ url_for('main.index') }}">CSEntry</a> +<a class="btn btn-primary" href="{{ url_for('inventory.index') }}">CSEntry</a> {% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index b05c1e3..038fc7b 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -14,7 +14,7 @@ {% block navbar %} <!-- Fixed navbar --> <div class="navbar fixed-top navbar-expand-lg navbar-light bg-light" role="navigation"> - <a class="navbar-brand" href="{{ url_for('main.index') }}">CSEntry</a> + <a class="navbar-brand" href="{{ url_for('inventory.index') }}">CSEntry</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> @@ -24,10 +24,10 @@ <div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="navbar-nav mr-auto"> {% set path = request.path %} - <a class="nav-item nav-link {{ is_active(path.startswith("/items")) }}" href="{{ url_for('main.list_items') }}">Items</a> - <a class="nav-item nav-link {{ is_active(path.startswith("/hosts")) }}" href="{{ url_for('main.list_hosts') }}">Hosts</a> - <a class="nav-item nav-link {{ is_active(path.startswith("/attributes")) }}" href="{{ url_for('main.attributes', kind='Manufacturer') }}">Attributes</a> - <a class="nav-item nav-link {{ is_active(path.startswith("/qrcodes")) }}" href="{{ url_for('main.qrcodes', kind='Action') }}">QR Codes</a> + <a class="nav-item nav-link {{ is_active(path.startswith("/items")) }}" href="{{ url_for('inventory.list_items') }}">Items</a> + <a class="nav-item nav-link {{ is_active(path.startswith("/hosts")) }}" href="{{ url_for('networks.list_hosts') }}">Hosts</a> + <a class="nav-item nav-link {{ is_active(path.startswith("/attributes")) }}" href="{{ url_for('inventory.attributes', kind='Manufacturer') }}">Attributes</a> + <a class="nav-item nav-link {{ is_active(path.startswith("/qrcodes")) }}" href="{{ url_for('inventory.qrcodes', kind='Action') }}">QR Codes</a> {% if current_user.is_authenticated and current_user.is_admin %} <a class="nav-item nav-link" href="{{ url_for('admin.index') }}">Admin</a> {% endif %} diff --git a/app/templates/attributes.html b/app/templates/inventory/attributes.html similarity index 91% rename from app/templates/attributes.html rename to app/templates/inventory/attributes.html index 362191e..cfff079 100644 --- a/app/templates/attributes.html +++ b/app/templates/inventory/attributes.html @@ -7,7 +7,7 @@ <ul class="nav nav-tabs"> {% for attribute in ('Manufacturer', 'Model', 'Location') %} <li class="nav-item"> - <a class="nav-link {% if attribute == kind %}active{% endif %}" href="{{ url_for('main.attributes', kind=attribute) }}">{{ attribute }}</a> + <a class="nav-link {% if attribute == kind %}active{% endif %}" href="{{ url_for('inventory.attributes', kind=attribute) }}">{{ attribute }}</a> </li> {% endfor %} </ul> diff --git a/app/templates/create_item.html b/app/templates/inventory/create_item.html similarity index 82% rename from app/templates/create_item.html rename to app/templates/inventory/create_item.html index d9b2931..3f0bfe0 100644 --- a/app/templates/create_item.html +++ b/app/templates/inventory/create_item.html @@ -6,10 +6,10 @@ {% block main %} <ul class="nav nav-tabs"> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.list_items') }}">List items</a> + <a class="nav-link" href="{{ url_for('inventory.list_items') }}">List items</a> </li> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('main.create_item') }}">Register new item</a> + <a class="nav-link active" href="{{ url_for('inventory.create_item') }}">Register new item</a> </li> </ul> diff --git a/app/templates/items.html b/app/templates/inventory/items.html similarity index 79% rename from app/templates/items.html rename to app/templates/inventory/items.html index 9576fc1..43df6de 100644 --- a/app/templates/items.html +++ b/app/templates/inventory/items.html @@ -5,10 +5,10 @@ {% block main %} <ul class="nav nav-tabs"> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('main.list_items') }}">List items</a> + <a class="nav-link active" href="{{ url_for('inventory.list_items') }}">List items</a> </li> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.create_item') }}">Register new item</a> + <a class="nav-link" href="{{ url_for('inventory.create_item') }}">Register new item</a> </li> </ul> diff --git a/app/templates/qrcodes.html b/app/templates/inventory/qrcodes.html similarity index 90% rename from app/templates/qrcodes.html rename to app/templates/inventory/qrcodes.html index d725bf4..eca4e46 100644 --- a/app/templates/qrcodes.html +++ b/app/templates/inventory/qrcodes.html @@ -6,7 +6,7 @@ <ul class="nav nav-tabs"> {% for item in ('Action', 'Manufacturer', 'Model', 'Location', 'Status') %} <li class="nav-item"> - <a class="nav-link {% if kind == item %}active{% endif %}" href="{{ url_for('main.qrcodes', kind=item) }}">{{ item }}</a> + <a class="nav-link {% if kind == item %}active{% endif %}" href="{{ url_for('inventory.qrcodes', kind=item) }}">{{ item }}</a> </li> {% endfor %} </ul> diff --git a/app/templates/view_item.html b/app/templates/inventory/view_item.html similarity index 88% rename from app/templates/view_item.html rename to app/templates/inventory/view_item.html index 27e75dc..c30766c 100644 --- a/app/templates/view_item.html +++ b/app/templates/inventory/view_item.html @@ -6,13 +6,13 @@ {% block main %} <ul class="nav nav-tabs"> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.list_items') }}">List items</a> + <a class="nav-link" href="{{ url_for('inventory.list_items') }}">List items</a> </li> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.create_item') }}">Register new item</a> + <a class="nav-link" href="{{ url_for('inventory.create_item') }}">Register new item</a> </li> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('main.view_item', ics_id=item['ics_id']) }}">View item</a> + <a class="nav-link active" href="{{ url_for('inventory.view_item', ics_id=item['ics_id']) }}">View item</a> </li> </ul> diff --git a/app/templates/create_host.html b/app/templates/networks/create_host.html similarity index 81% rename from app/templates/create_host.html rename to app/templates/networks/create_host.html index ad7c973..6c4f8f9 100644 --- a/app/templates/create_host.html +++ b/app/templates/networks/create_host.html @@ -6,10 +6,10 @@ {% block main %} <ul class="nav nav-tabs"> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.list_hosts') }}">List hosts</a> + <a class="nav-link" href="{{ url_for('networks.list_hosts') }}">List hosts</a> </li> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('main.create_host') }}">Register new host</a> + <a class="nav-link active" href="{{ url_for('networks.create_host') }}">Register new host</a> </li> </ul> diff --git a/app/templates/hosts.html b/app/templates/networks/hosts.html similarity index 76% rename from app/templates/hosts.html rename to app/templates/networks/hosts.html index 344106c..0d0f266 100644 --- a/app/templates/hosts.html +++ b/app/templates/networks/hosts.html @@ -5,10 +5,10 @@ {% block main %} <ul class="nav nav-tabs"> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('main.list_hosts') }}">List hosts</a> + <a class="nav-link active" href="{{ url_for('networks.list_hosts') }}">List hosts</a> </li> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('main.create_host') }}">Register new host</a> + <a class="nav-link" href="{{ url_for('networks.create_host') }}">Register new host</a> </li> </ul> diff --git a/app/users/views.py b/app/users/views.py index ea37c5e..cc23f31 100644 --- a/app/users/views.py +++ b/app/users/views.py @@ -24,7 +24,7 @@ def login(): form = LDAPLoginForm(request.form) if form.validate_on_submit(): login_user(form.user, remember=form.remember_me.data) - return redirect(request.args.get('next') or url_for('main.index')) + return redirect(request.args.get('next') or url_for('items.index')) return render_template('users/login.html', form=form) -- GitLab