diff --git a/app/admin/views.py b/app/admin/views.py index 15981da91aa997195f91fe3c343f8ab45d8705ee..a84d9b9b54be13a84c7913d76f716d538acb8314 100644 --- a/app/admin/views.py +++ b/app/admin/views.py @@ -43,6 +43,11 @@ class AppAdminModelConverter(sqla.form.AdminModelConverter): class AdminModelView(sqla.ModelView): model_form_converter = AppAdminModelConverter + # Replace TextAreaField (default for Text) with StringField + form_overrides = { + 'name': fields.StringField, + } + def is_accessible(self): return current_user.is_authenticated and current_user.is_admin @@ -59,14 +64,6 @@ class UserAdmin(AdminModelView): can_delete = False -class QRCodeAdmin(AdminModelView): - - # Replace TextAreaField (default for CIText) with StringField - form_overrides = { - 'name': fields.StringField - } - - class ItemAdmin(AdminModelView): # Replace TextAreaField (default for Text) with StringField @@ -86,15 +83,7 @@ class ItemAdmin(AdminModelView): class NetworkAdmin(AdminModelView): - # Replace TextAreaField (default for Text) with StringField - form_overrides = { - 'label': fields.StringField, - } - form_args = { - 'label': { - 'filters': [lambda x: x or None], - }, 'gateway': { 'filters': [lambda x: x or None], }, @@ -103,11 +92,6 @@ class NetworkAdmin(AdminModelView): class HostAdmin(AdminModelView): - # Replace TextAreaField (default for Text) with StringField - form_overrides = { - 'name': fields.StringField, - } - form_args = { 'name': { 'filters': [lambda x: x or None], diff --git a/app/api/main.py b/app/api/main.py index 5355639c6b9184b329783d16563a72cf5e693d00..c8295ae4b677e1034eb078081f1fcb14543e4b5e 100644 --- a/app/api/main.py +++ b/app/api/main.py @@ -244,7 +244,7 @@ def create_status(): def get_networks(): # TODO: add pagination query = utils.get_query(Network.query, request.args) - networks = query.order_by(Network.id) + networks = query.order_by(Network.prefix) data = [network.to_dict() for network in networks] return jsonify(data) @@ -254,7 +254,7 @@ def get_networks(): @jwt_groups_accepted('admin') def create_network(): """Create a new network""" - return create_generic_model(Network, mandatory_fields=('prefix', 'first', 'last')) + return create_generic_model(Network, mandatory_fields=('name', 'prefix', 'first', 'last')) @bp.route('/hosts') diff --git a/app/factory.py b/app/factory.py index 3e40240b1740ac3d31334492c0fa80ade5d8f4e8..37461e229c5f068767c0072a8f454e0b3d6946ef 100644 --- a/app/factory.py +++ b/app/factory.py @@ -14,7 +14,8 @@ from flask import Flask from whitenoise import WhiteNoise 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, QRCodeAdmin, NetworkAdmin, HostAdmin +from .admin.views import (AdminModelView, ItemAdmin, UserAdmin, GroupAdmin, + NetworkAdmin, HostAdmin) from .main.views import bp as main from .users.views import bp as users from .api.main import bp as api @@ -94,11 +95,11 @@ def create_app(config=None): admin.init_app(app) admin.add_view(GroupAdmin(models.Group, db.session)) admin.add_view(UserAdmin(models.User, db.session)) - admin.add_view(QRCodeAdmin(models.Action, db.session)) - admin.add_view(QRCodeAdmin(models.Manufacturer, db.session)) - admin.add_view(QRCodeAdmin(models.Model, db.session)) - admin.add_view(QRCodeAdmin(models.Location, db.session)) - admin.add_view(QRCodeAdmin(models.Status, db.session)) + admin.add_view(AdminModelView(models.Action, db.session)) + admin.add_view(AdminModelView(models.Manufacturer, db.session)) + admin.add_view(AdminModelView(models.Model, db.session)) + admin.add_view(AdminModelView(models.Location, db.session)) + admin.add_view(AdminModelView(models.Status, db.session)) admin.add_view(ItemAdmin(models.Item, db.session)) admin.add_view(NetworkAdmin(models.Network, db.session)) admin.add_view(HostAdmin(models.Host, db.session)) diff --git a/app/models.py b/app/models.py index c5b0969baeaf535a227a2f43d6e7d58a86158f03..775989c2c3db932d7bd3a2ba1a1a8750b66f4c49 100644 --- a/app/models.py +++ b/app/models.py @@ -271,7 +271,7 @@ class Item(db.Model): class Network(db.Model): id = db.Column(db.Integer, primary_key=True) - label = db.Column(db.Text) + name = db.Column(db.Text, nullable=False, unique=True) prefix = db.Column(postgresql.CIDR, nullable=False, unique=True) first = db.Column(postgresql.INET, nullable=False, unique=True) last = db.Column(postgresql.INET, nullable=False, unique=True) @@ -361,7 +361,7 @@ class Network(db.Model): def to_dict(self): return { 'id': self.id, - 'label': self.label, + 'name': self.name, 'prefix': self.prefix, 'first': self.first, 'last': self.last, diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py index fb755d29314d2f55a57d1c0405d6545b64f7c627..7f9862e4f341f51e082f9ef98327324a55857eae 100644 --- a/tests/functional/test_api.py +++ b/tests/functional/test_api.py @@ -443,9 +443,9 @@ def test_get_networks(client, session, readonly_token): location = models.Location(name='G02') session.add(location) session.flush() - network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.1', last='172.16.1.254', label='network1') - network2 = models.Network(prefix='172.16.20.0/22', first='172.16.20.11', last='172.16.20.250') - network3 = models.Network(prefix='172.16.5.0/24', first='172.16.5.10', last='172.16.5.254', location_id=location.id) + network1 = models.Network(name='network1', prefix='172.16.1.0/24', first='172.16.1.1', last='172.16.1.254') + network2 = models.Network(name='network2', prefix='172.16.20.0/22', first='172.16.20.11', last='172.16.20.250') + network3 = models.Network(name='network3', prefix='172.16.5.0/24', first='172.16.5.10', last='172.16.5.254', location_id=location.id) for network in (network1, network2, network3): session.add(network) session.commit() @@ -478,35 +478,49 @@ def test_create_network(client, session, admin_token): location = models.Location(name='G02') session.add(location) session.commit() - # check that prefix, first and last are mandatory + # check that name, prefix, first and last are mandatory response = post(client, '/api/networks', data={}, token=admin_token) - check_response_message(response, "Missing mandatory field 'prefix'", 422) + check_response_message(response, "Missing mandatory field 'name'", 422) response = post(client, '/api/networks', data={'first': '172.16.1.10', 'last': '172.16.1.250'}, token=admin_token) - check_response_message(response, "Missing mandatory field 'prefix'", 422) + check_response_message(response, "Missing mandatory field 'name'", 422) response = post(client, '/api/networks', data={'prefix': '172.16.1.0/24'}, token=admin_token) - check_response_message(response, "Missing mandatory field 'first'", 422) - response = post(client, '/api/networks', data={'prefix': '172.16.1.0/24', 'first': '172.16.1.10'}, token=admin_token) + check_response_message(response, "Missing mandatory field 'name'", 422) + response = post(client, '/api/networks', data={'name': 'network1'}, token=admin_token) + check_response_message(response, "Missing mandatory field 'prefix'", 422) + response = post(client, '/api/networks', data={'name': 'network1', 'prefix': '172.16.1.0/24', 'first': '172.16.1.10'}, token=admin_token) check_response_message(response, "Missing mandatory field 'last'", 422) - data = {'prefix': '172.16.1.0/24', + data = {'name': 'network1', + 'prefix': '172.16.1.0/24', 'first': '172.16.1.10', 'last': '172.16.1.250'} response = post(client, '/api/networks', data=data, token=admin_token) assert response.status_code == 201 - assert {'id', 'prefix', 'first', 'last', 'label', 'vlanid', 'gateway', 'location'} == set(response.json.keys()) + assert {'id', 'name', 'prefix', 'first', 'last', 'vlanid', 'gateway', 'location'} == set(response.json.keys()) + assert response.json['name'] == 'network1' assert response.json['prefix'] == '172.16.1.0/24' assert response.json['first'] == '172.16.1.10' assert response.json['last'] == '172.16.1.250' - # Check that prefix shall be unique + # Check that prefix and name shall be unique response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, '(psycopg2.IntegrityError) duplicate key value violates unique constraint', 422) + data_same_prefix = data.copy() + data_same_prefix['name'] = 'networkX' + response = post(client, '/api/networks', data=data_same_prefix, token=admin_token) + check_response_message(response, '(psycopg2.IntegrityError) duplicate key value violates unique constraint', 422) + data_same_name = {'name': 'network1', + 'prefix': '172.16.2.0/24', + 'first': '172.16.2.10', + 'last': '172.16.2.250'} + response = post(client, '/api/networks', data=data_same_name, token=admin_token) + check_response_message(response, '(psycopg2.IntegrityError) duplicate key value violates unique constraint', 422) # Check that all parameters can be passed - data2 = {'prefix': '172.16.5.0/24', + data2 = {'name': 'network2', + 'prefix': '172.16.5.0/24', 'first': '172.16.5.11', 'last': '172.16.5.250', - 'label': 'G02', 'gateway': '172.16.5.10', 'vlanid': 1601, 'location_id': location.id} @@ -520,20 +534,17 @@ def test_create_network(client, session, admin_token): def test_create_network_invalid_prefix(client, session, admin_token): # invalid network address - data = {'prefix': 'foo', + data = {'name': 'network1', + 'prefix': 'foo', 'first': '172.16.1.10', 'last': '172.16.1.250'} response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, "'foo' does not appear to be an IPv4 or IPv6 network", 422) - data = {'prefix': '172.16.1', - 'first': '172.16.1.10', - 'last': '172.16.1.250'} + data['prefix'] = '172.16.1' response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, "'172.16.1' does not appear to be an IPv4 or IPv6 network", 422) # prefix address has host bits set - data = {'prefix': '172.16.1.1/24', - 'first': '172.16.1.10', - 'last': '172.16.1.250'} + data['prefix'] = '172.16.1.1/24' response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, '172.16.1.1/24 has host bits set', 422) @@ -541,13 +552,15 @@ def test_create_network_invalid_prefix(client, session, admin_token): @pytest.mark.parametrize('address', ('', 'foo', '192.168')) def test_create_network_invalid_ip(address, client, session, admin_token): # invalid first IP address - data = {'prefix': '192.168.0.0/24', + data = {'name': 'network1', + 'prefix': '192.168.0.0/24', 'first': address, 'last': '192.168.0.250'} response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, f"'{address}' does not appear to be an IPv4 or IPv6 address", 422) # invalid last IP address - data = {'prefix': '192.168.0.0/24', + data = {'name': 'network1', + 'prefix': '192.168.0.0/24', 'first': '192.168.0.250', 'last': address} response = post(client, '/api/networks', data=data, token=admin_token) @@ -556,19 +569,22 @@ def test_create_network_invalid_ip(address, client, session, admin_token): def test_create_network_invalid_range(client, session, admin_token): # first not in prefix - data = {'prefix': '172.16.1.0/24', + data = {'name': 'network1', + 'prefix': '172.16.1.0/24', 'first': '172.16.2.10', 'last': '172.16.1.250'} response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, 'IP address 172.16.2.10 is not in network 172.16.1.0/24', 422) # last not in prefix - data = {'prefix': '172.16.1.0/24', + data = {'name': 'network1', + 'prefix': '172.16.1.0/24', 'first': '172.16.1.10', 'last': '172.16.5.250'} response = post(client, '/api/networks', data=data, token=admin_token) check_response_message(response, 'IP address 172.16.5.250 is not in network 172.16.1.0/24', 422) # first > last - data = {'prefix': '172.16.1.0/24', + data = {'name': 'network1', + 'prefix': '172.16.1.0/24', 'first': '172.16.1.10', 'last': '172.16.1.9'} response = post(client, '/api/networks', data=data, token=admin_token) @@ -577,8 +593,8 @@ def test_create_network_invalid_range(client, session, admin_token): def test_get_hosts(client, session, readonly_token): # Create some hosts - network1 = models.Network(prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') - network2 = models.Network(prefix='192.168.2.0/24', first='192.168.2.10', last='192.168.2.250') + network1 = models.Network(name='network1', prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') + network2 = models.Network(name='network2', prefix='192.168.2.0/24', first='192.168.2.10', last='192.168.2.250') session.add(network1) session.add(network2) session.flush() @@ -602,7 +618,7 @@ def test_get_hosts(client, session, readonly_token): def test_create_host(client, session, user_token): - network = models.Network(prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') + network = models.Network(name='network1', prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') session.add(network) session.commit() # check that network_id and ip are mandatory @@ -639,7 +655,7 @@ def test_create_host(client, session, user_token): @pytest.mark.parametrize('ip', ('', 'foo', '192.168')) def test_create_host_invalid_ip(ip, client, session, user_token): - network = models.Network(prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') + network = models.Network(name='network1', prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') session.add(network) session.commit() # invalid IP address @@ -650,7 +666,7 @@ def test_create_host_invalid_ip(ip, client, session, user_token): def test_create_host_ip_not_in_network(client, session, user_token): - network = models.Network(prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') + network = models.Network(name='network1', prefix='192.168.1.0/24', first='192.168.1.10', last='192.168.1.250') session.add(network) session.commit() # IP address not in range diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py index 673c239bda95697de9e41656e16d48fe9bef2584..d9b36cab9eb0e3bffe9a366fd388fafac5668808 100644 --- a/tests/functional/test_models.py +++ b/tests/functional/test_models.py @@ -15,8 +15,8 @@ from app import models def test_network_ip_properties(session): # Create some networks - network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') - network2 = models.Network(prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') + network1 = models.Network(name='network1', prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') + network2 = models.Network(name='network2', prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') for network in (network1, network2): session.add(network) session.commit() @@ -40,8 +40,8 @@ def test_network_ip_properties(session): def test_network_available_and_used_ips(session): # Create some networks and hosts - network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') - network2 = models.Network(prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') + network1 = models.Network(name='network1', prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') + network2 = models.Network(name='network2', prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') for network in (network1, network2): session.add(network) session.flush()