diff --git a/app/api/main.py b/app/api/main.py index 0738634a024a5356923bc50b7f08ef24ca2b582e..494c83df9f5bb8d983b6b49f992c17eeea600067 100644 --- a/app/api/main.py +++ b/app/api/main.py @@ -273,4 +273,4 @@ def get_hosts(): @jwt_groups_accepted('admin', 'create') def create_host(): """Create a new host""" - return create_generic_model(Host, mandatory_fields=('network', 'ip')) + return create_generic_model(Host, mandatory_fields=('network', 'ip', 'name')) diff --git a/app/models.py b/app/models.py index 56e01def8bdec93cff8b6e1b7c7e914d884c66cd..9e94ca0ce63f8d144820384eb2f068a8b2599a7e 100644 --- a/app/models.py +++ b/app/models.py @@ -385,10 +385,10 @@ class Host(db.Model): id = db.Column(db.Integer, primary_key=True) network_id = db.Column(db.Integer, db.ForeignKey('network.id'), nullable=False) ip = db.Column(postgresql.INET, nullable=False, unique=True) - name = db.Column(db.Text, unique=True) + name = db.Column(db.Text, nullable=False, unique=True) + description = db.Column(db.Text) + mac_id = db.Column(db.Integer, db.ForeignKey('mac.id')) - # This is a One To One relationship (set uselist to False) - mac = db.relationship('Mac', backref='host', uselist=False) cnames = db.relationship('Cname', backref='host') def __init__(self, **kwargs): @@ -414,13 +414,14 @@ class Host(db.Model): return str(self.ip) def __repr__(self): - return f'Host(id={self.id}, network_id={self.network_id}, ip={self.ip}, name={self.name}, mac={self.mac})' + return f'Host(id={self.id}, network_id={self.network_id}, ip={self.ip}, name={self.name}, description={self.description}, mac={self.mac})' def to_dict(self, long=False): d = { 'id': self.id, 'ip': self.ip, 'name': self.name, + 'description': self.description, 'network_id': self.network_id, } if long: @@ -431,9 +432,10 @@ class Host(db.Model): class Mac(db.Model): id = db.Column(db.Integer, primary_key=True) address = db.Column(postgresql.MACADDR, nullable=False, unique=True) - host_id = db.Column(db.Integer, db.ForeignKey('host.id'), unique=True) item_id = db.Column(db.Integer, db.ForeignKey('item.id'), nullable=False) + hosts = db.relationship('Host', backref='mac') + def __str__(self): return str(self.address) @@ -441,15 +443,11 @@ class Mac(db.Model): d = { 'id': self.id, 'address': self.address, - 'host_id': self.host_id, 'item_id': self.item_id, } if long: d['item_ics_id'] = self.item.ics_id - try: - d['host'] = self.host.to_dict() - except AttributeError: - d['host'] = None + d['hosts'] = [host.to_dict() for host in self.hosts] return d diff --git a/app/templates/view_item.html b/app/templates/view_item.html index 05ff5e0fd1f7e116827dcb34b5228b28dc0a918a..7eed7149ab3ee138d03ec52ceba2482eca7cb203 100644 --- a/app/templates/view_item.html +++ b/app/templates/view_item.html @@ -8,29 +8,34 @@ <dl class="row"> <dt class="col-sm-3">ICS id</dt> - <dd class="col-sm-9">{{ item['ics_id'] }}</dt> + <dd class="col-sm-9">{{ item['ics_id'] }}</dd> <dt class="col-sm-3">Created</dt> - <dd class="col-sm-9">{{ item['created'] }}</dt> + <dd class="col-sm-9">{{ item['created'] }}</dd> <dt class="col-sm-3">Updated</dt> - <dd class="col-sm-9">{{ item['updated'] }}</dt> + <dd class="col-sm-9">{{ item['updated'] }}</dd> <dt class="col-sm-3">Serial number</dt> - <dd class="col-sm-9">{{ item['serial_number'] }}</dt> + <dd class="col-sm-9">{{ item['serial_number'] }}</dd> <dt class="col-sm-3">Manufacturer</dt> - <dd class="col-sm-9">{{ item['manufacturer'] }}</dt> + <dd class="col-sm-9">{{ item['manufacturer'] }}</dd> <dt class="col-sm-3">Model</dt> - <dd class="col-sm-9">{{ item['model'] }}</dt> + <dd class="col-sm-9">{{ item['model'] }}</dd> <dt class="col-sm-3">Location</dt> - <dd class="col-sm-9">{{ item['location'] }}</dt> + <dd class="col-sm-9">{{ item['location'] }}</dd> <dt class="col-sm-3">Status</dt> - <dd class="col-sm-9">{{ item['status'] }}</dt> + <dd class="col-sm-9">{{ item['status'] }}</dd> {% for mac in item['macs'] %} - <dt class="col-sm-3">Address{{ loop.index }}</dt> - <dd class="col-sm-9">{{ mac.address }} / {{ mac.host.ip }} / {{ mac.host.name }}</dt> + {% set macloop = loop %} + <dt class="col-sm-3">MAC Address{{ loop.index }}</dt> + <dd class="col-sm-9">{{ mac.address }}</dd> + {% for host in mac.hosts %} + <dt class="col-sm-3">Host{{ macloop.index }}-{{ loop.index }}</dt> + <dd class="col-sm-9">IP: {{ host.ip }} / name: {{ host.name }}</dd> + {% endfor %} {% endfor %} <dt class="col-sm-3">Parent</dt> - <dd class="col-sm-9">{{ link_to_item(item['parent']) }}</dt> + <dd class="col-sm-9">{{ link_to_item(item['parent']) }}</dd> <dt class="col-sm-3">Children</dt> - <dd class="col-sm-9">{{ link_to_items(item['children']) }}</dt> + <dd class="col-sm-9">{{ link_to_items(item['children']) }}</dd> </dl> <h4>History</h4> diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py index 96de4f78f6a43193a91ddf2c1d2b539d56ec8310..75111e658a07a9abd05f8ece5a4ff3b2dfd3e698 100644 --- a/tests/functional/test_api.py +++ b/tests/functional/test_api.py @@ -622,24 +622,27 @@ def test_create_host(client, network_factory, user_token): check_response_message(response, "Missing mandatory field 'ip'", 422) data = {'network': network.address, - 'ip': '192.168.1.20'} + 'ip': '192.168.1.20', + 'name': 'hostname1'} response = post(client, '/api/hosts', data=data, token=user_token) assert response.status_code == 201 - assert {'id', 'network_id', 'ip', 'name'} == set(response.json.keys()) + assert {'id', 'network_id', 'ip', 'name', 'description'} == set(response.json.keys()) assert response.json['network_id'] == network.id assert response.json['ip'] == '192.168.1.20' + assert response.json['name'] == 'hostname1' - # Check that IP shall be unique + # Check that IP and name shall be unique response = post(client, '/api/hosts', data=data, token=user_token) check_response_message(response, '(psycopg2.IntegrityError) duplicate key value violates unique constraint', 422) # Check that all parameters can be passed data2 = {'network': network.address, 'ip': '192.168.1.21', - 'name': 'myhostname'} + 'name': 'myhostname', + 'description': 'my description'} response = post(client, '/api/hosts', data=data2, token=user_token) assert response.status_code == 201 - assert response.json['name'] == 'myhostname' + assert response.json['description'] == 'my description' # check all items that were created assert models.Host.query.count() == 2 @@ -650,7 +653,8 @@ def test_create_host_invalid_ip(ip, client, network_factory, user_token): network = network_factory(address='192.168.1.0/24', first_ip='192.168.1.10', last_ip='192.168.1.250') # invalid IP address data = {'network': network.address, - 'ip': ip} + 'ip': ip, + 'name': 'hostname'} response = post(client, '/api/hosts', data=data, token=user_token) check_response_message(response, f"'{ip}' does not appear to be an IPv4 or IPv6 address", 422) @@ -659,6 +663,7 @@ def test_create_host_ip_not_in_network(client, network_factory, user_token): network = network_factory(address='192.168.1.0/24', first_ip='192.168.1.10', last_ip='192.168.1.250') # IP address not in range data = {'network': network.address, - 'ip': '192.168.2.4'} + 'ip': '192.168.2.4', + 'name': 'hostname'} response = post(client, '/api/hosts', data=data, token=user_token) check_response_message(response, 'IP address 192.168.2.4 is not in network 192.168.1.0/24', 422)