Skip to content
Snippets Groups Projects
Commit eb988dba authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add Network/Host/Mac tables

- A network defines an available range of IP addresses
- A host defines an IP address and name
- A host is always linked to a network
- A mac address is always linked to an item (physical equipment)
- A mac address can be linked to one host (IP)
- An item can have several mac addresses
parent 551ee199
No related branches found
No related tags found
No related merge requests found
......@@ -14,7 +14,7 @@ from flask import Flask
from whitenoise import WhiteNoise
from . import settings
from .extensions import db, migrate, login_manager, ldap_manager, bootstrap, admin, mail, jwt, toolbar
from .models import Action, Manufacturer, Model, Location, Status
from .models import Action, Manufacturer, Model, Location, Status, Network, Host, Mac
from .admin.views import AdminModelView, ItemAdmin, UserAdmin, GroupAdmin
from .main.views import bp as main
from .users.views import bp as users
......@@ -101,6 +101,9 @@ def create_app(config=None):
admin.add_view(AdminModelView(Location, db.session))
admin.add_view(AdminModelView(Status, db.session))
admin.add_view(ItemAdmin(db.session))
admin.add_view(AdminModelView(Network, db.session))
admin.add_view(AdminModelView(Host, db.session))
admin.add_view(AdminModelView(Mac, db.session))
app.register_blueprint(main)
app.register_blueprint(users)
......
......@@ -9,9 +9,11 @@ This module implements the models used in the app.
:license: BSD 2-Clause, see LICENSE for more details.
"""
import ipaddress
import re
import qrcode
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import validates
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy_continuum import make_versioned, version_class
......@@ -185,6 +187,7 @@ class Model(QRCodeMixin, db.Model):
class Location(QRCodeMixin, db.Model):
items = db.relationship('Item', back_populates='location')
networks = db.relationship('Network', backref='location')
class Status(QRCodeMixin, db.Model):
......@@ -213,6 +216,7 @@ class Item(db.Model):
location = db.relationship('Location', back_populates='items')
status = db.relationship('Status', back_populates='items')
children = db.relationship('Item', backref=db.backref('parent', remote_side=[id]))
macs = db.relationship('Mac', backref='item')
def __init__(self, ics_id=None, serial_number=None, manufacturer=None, model=None, location=None, status=None):
# All arguments must be optional for this class to work with flask-admin!
......@@ -249,6 +253,7 @@ class Item(db.Model):
}
if long:
d['children'] = [utils.format_field(child) for child in self.children]
d['macs'] = [mac.to_dict(long=True) for mac in self.macs]
d['history'] = self.history()
return d
......@@ -272,6 +277,99 @@ class Item(db.Model):
return versions
class Network(db.Model):
id = db.Column(db.Integer, primary_key=True)
label = db.Column(db.Text)
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)
gateway = db.Column(postgresql.INET)
vlanid = db.Column(db.Integer, unique=True)
location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
hosts = db.relationship('Host', backref='network')
__table_args__ = (
sa.CheckConstraint('first < last', name='first_less_than_last'),
sa.CheckConstraint('first << prefix', name='first_in_prefix'),
sa.CheckConstraint('last << prefix', name='last_in_prefix'),
)
def __str__(self):
return str(self.prefix)
@validates('hosts')
def validate_hosts(self, key, host):
"""Ensure the host IP is in the network range"""
addr = ipaddress.ip_address(host.ip)
net = ipaddress.ip_network(self.prefix)
if addr not in net:
raise utils.CSEntryError(f'IP address shall be in network {self.prefix}', status_code=422)
if addr < ipaddress.ip_address(self.first) or addr > ipaddress.ip_address(self.last):
raise utils.CSEntryError(f'IP address shall be in range {self.first} - {self.last}', status_code=422)
return host
def to_dict(self):
return {
'id': self.id,
'label': self.label,
'prefix': self.prefix,
'first': self.first,
'last': self.last,
'gateway': self.gateway,
'vlanid': self.vlanid,
'location': utils.format_field(self.location),
}
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)
mac = db.relationship('Mac', backref='host')
def __str__(self):
return str(self.ip)
def to_dict(self, long=False):
d = {
'id': self.id,
'ip': self.ip,
'name': self.name,
'network_id': self.network_id,
}
if long:
d['mac'] = getattr(self.mac, 'address', None)
return d
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)
def __str__(self):
return str(self.address)
def to_dict(self, long=False):
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
return d
# call configure_mappers after defining all the models
# required by sqlalchemy_continuum
sa.orm.configure_mappers()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment