1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Code formatting cleanup

This commit is contained in:
Jeremy Stretch
2018-03-30 13:57:26 -04:00
parent 0bb632c642
commit 9725f19bae
8 changed files with 1252 additions and 333 deletions

View File

@ -7,8 +7,7 @@ from django.utils.encoding import python_2_unicode_compatible
from dcim.constants import STATUS_CLASSES from dcim.constants import STATUS_CLASSES
from dcim.fields import ASNField from dcim.fields import ASNField
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel
from tenancy.models import Tenant
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
@ -19,15 +18,43 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
stores information pertinent to the user's relationship with the Provider. stores information pertinent to the user's relationship with the Provider.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
asn = ASNField(blank=True, null=True, verbose_name='ASN') unique=True
account = models.CharField(max_length=30, blank=True, verbose_name='Account number') )
portal_url = models.URLField(blank=True, verbose_name='Portal') slug = models.SlugField(
noc_contact = models.TextField(blank=True, verbose_name='NOC contact') unique=True
admin_contact = models.TextField(blank=True, verbose_name='Admin contact') )
comments = models.TextField(blank=True) asn = ASNField(
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') blank=True,
null=True,
verbose_name='ASN'
)
account = models.CharField(
max_length=30,
blank=True,
verbose_name='Account number'
)
portal_url = models.URLField(
blank=True,
verbose_name='Portal'
)
noc_contact = models.TextField(
blank=True,
verbose_name='NOC contact'
)
admin_contact = models.TextField(
blank=True,
verbose_name='Admin contact'
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments'] csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
@ -59,8 +86,13 @@ class CircuitType(models.Model):
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
"Long Haul," "Metro," or "Out-of-Band". "Long Haul," "Metro," or "Out-of-Band".
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
csv_headers = ['name', 'slug'] csv_headers = ['name', 'slug']
@ -87,16 +119,52 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device
interface, but this is not required. Circuit port speed and commit rate are measured in Kbps. interface, but this is not required. Circuit port speed and commit rate are measured in Kbps.
""" """
cid = models.CharField(max_length=50, verbose_name='Circuit ID') cid = models.CharField(
provider = models.ForeignKey('Provider', related_name='circuits', on_delete=models.PROTECT) max_length=50,
type = models.ForeignKey('CircuitType', related_name='circuits', on_delete=models.PROTECT) verbose_name='Circuit ID'
status = models.PositiveSmallIntegerField(choices=CIRCUIT_STATUS_CHOICES, default=CIRCUIT_STATUS_ACTIVE) )
tenant = models.ForeignKey(Tenant, related_name='circuits', blank=True, null=True, on_delete=models.PROTECT) provider = models.ForeignKey(
install_date = models.DateField(blank=True, null=True, verbose_name='Date installed') to='circuits.Provider',
commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)') on_delete=models.PROTECT,
description = models.CharField(max_length=100, blank=True) related_name='circuits'
comments = models.TextField(blank=True) )
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') type = models.ForeignKey(
to='CircuitType',
on_delete=models.PROTECT,
related_name='circuits'
)
status = models.PositiveSmallIntegerField(
choices=CIRCUIT_STATUS_CHOICES,
default=CIRCUIT_STATUS_ACTIVE
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='circuits',
blank=True,
null=True
)
install_date = models.DateField(
blank=True,
null=True,
verbose_name='Date installed'
)
commit_rate = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name='Commit rate (Kbps)')
description = models.CharField(
max_length=100,
blank=True
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = [ csv_headers = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
@ -145,19 +213,47 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
@python_2_unicode_compatible @python_2_unicode_compatible
class CircuitTermination(models.Model): class CircuitTermination(models.Model):
circuit = models.ForeignKey('Circuit', related_name='terminations', on_delete=models.CASCADE) circuit = models.ForeignKey(
term_side = models.CharField(max_length=1, choices=TERM_SIDE_CHOICES, verbose_name='Termination') to='circuits.Circuit',
site = models.ForeignKey('dcim.Site', related_name='circuit_terminations', on_delete=models.PROTECT) on_delete=models.CASCADE,
interface = models.OneToOneField( related_name='terminations'
'dcim.Interface', related_name='circuit_termination', blank=True, null=True, on_delete=models.PROTECT )
term_side = models.CharField(
max_length=1,
choices=TERM_SIDE_CHOICES,
verbose_name='Termination'
)
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
related_name='circuit_terminations'
)
interface = models.OneToOneField(
to='dcim.Interface',
on_delete=models.PROTECT,
related_name='circuit_termination',
blank=True,
null=True
)
port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)'
) )
port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
upstream_speed = models.PositiveIntegerField( upstream_speed = models.PositiveIntegerField(
blank=True, null=True, verbose_name='Upstream speed (Kbps)', blank=True,
null=True,
verbose_name='Upstream speed (Kbps)',
help_text='Upstream speed, if different from port speed' help_text='Upstream speed, if different from port speed'
) )
xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID') xconnect_id = models.CharField(
pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)') max_length=50,
blank=True,
verbose_name='Cross-connect ID'
)
pp_info = models.CharField(
max_length=100,
blank=True,
verbose_name='Patch panel/port(s)'
)
class Meta: class Meta:
ordering = ['circuit', 'term_side'] ordering = ['circuit', 'term_side']

View File

@ -17,9 +17,8 @@ from mptt.models import MPTTModel, TreeForeignKey
from timezone_field import TimeZoneField from timezone_field import TimeZoneField
from circuits.models import Circuit from circuits.models import Circuit
from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment from extras.models import CustomFieldModel
from extras.rpc import RPC_CLIENTS from extras.rpc import RPC_CLIENTS
from tenancy.models import Tenant
from utilities.fields import ColorField, NullableCharField from utilities.fields import ColorField, NullableCharField
from utilities.managers import NaturalOrderByManager from utilities.managers import NaturalOrderByManager
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
@ -38,10 +37,20 @@ class Region(MPTTModel):
Sites can be grouped within geographic Regions. Sites can be grouped within geographic Regions.
""" """
parent = TreeForeignKey( parent = TreeForeignKey(
'self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.CASCADE to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
db_index=True
)
name = models.CharField(
max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
) )
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
csv_headers = ['name', 'slug', 'parent'] csv_headers = ['name', 'slug', 'parent']
@ -78,23 +87,78 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
A Site represents a geographic location within a network; typically a building or campus. The optional facility A Site represents a geographic location within a network; typically a building or campus. The optional facility
field can be used to include an external designation, such as a data center name (e.g. Equinix SV6). field can be used to include an external designation, such as a data center name (e.g. Equinix SV6).
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
status = models.PositiveSmallIntegerField(choices=SITE_STATUS_CHOICES, default=SITE_STATUS_ACTIVE) unique=True
region = models.ForeignKey('Region', related_name='sites', blank=True, null=True, on_delete=models.SET_NULL) )
tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT) slug = models.SlugField(
facility = models.CharField(max_length=50, blank=True) unique=True
asn = ASNField(blank=True, null=True, verbose_name='ASN') )
time_zone = TimeZoneField(blank=True) status = models.PositiveSmallIntegerField(
description = models.CharField(max_length=100, blank=True) choices=SITE_STATUS_CHOICES,
physical_address = models.CharField(max_length=200, blank=True) default=SITE_STATUS_ACTIVE
shipping_address = models.CharField(max_length=200, blank=True) )
contact_name = models.CharField(max_length=50, blank=True) region = models.ForeignKey(
contact_phone = models.CharField(max_length=20, blank=True) to='dcim.Region',
contact_email = models.EmailField(blank=True, verbose_name="Contact E-mail") on_delete=models.SET_NULL,
comments = models.TextField(blank=True) related_name='sites',
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') blank=True,
images = GenericRelation(ImageAttachment) null=True
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='sites',
blank=True,
null=True
)
facility = models.CharField(
max_length=50,
blank=True
)
asn = ASNField(
blank=True,
null=True,
verbose_name='ASN'
)
time_zone = TimeZoneField(
blank=True
)
description = models.CharField(
max_length=100,
blank=True
)
physical_address = models.CharField(
max_length=200,
blank=True
)
shipping_address = models.CharField(
max_length=200,
blank=True
)
contact_name = models.CharField(
max_length=50,
blank=True
)
contact_phone = models.CharField(
max_length=20,
blank=True
)
contact_email = models.EmailField(
blank=True,
verbose_name='Contact E-mail'
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
images = GenericRelation(
to='extras.ImageAttachment'
)
objects = SiteManager() objects = SiteManager()
@ -171,9 +235,15 @@ class RackGroup(models.Model):
example, if a Site spans a corporate campus, a RackGroup might be defined to represent each building within that example, if a Site spans a corporate campus, a RackGroup might be defined to represent each building within that
campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor. campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor.
""" """
name = models.CharField(max_length=50) name = models.CharField(
max_length=50
)
slug = models.SlugField() slug = models.SlugField()
site = models.ForeignKey('Site', related_name='rack_groups', on_delete=models.CASCADE) site = models.ForeignKey(
to='dcim.Site',
on_delete=models.CASCADE,
related_name='rack_groups'
)
csv_headers = ['site', 'name', 'slug'] csv_headers = ['site', 'name', 'slug']
@ -203,8 +273,13 @@ class RackRole(models.Model):
""" """
Racks can be organized by functional role, similar to Devices. Racks can be organized by functional role, similar to Devices.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
color = ColorField() color = ColorField()
csv_headers = ['name', 'slug', 'color'] csv_headers = ['name', 'slug', 'color']
@ -238,23 +313,79 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
Each Rack is assigned to a Site and (optionally) a RackGroup. Each Rack is assigned to a Site and (optionally) a RackGroup.
""" """
name = models.CharField(max_length=50) name = models.CharField(
facility_id = NullableCharField(max_length=50, blank=True, null=True, verbose_name='Facility ID') max_length=50
site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT) )
group = models.ForeignKey('RackGroup', related_name='racks', blank=True, null=True, on_delete=models.SET_NULL) facility_id = NullableCharField(
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='racks', on_delete=models.PROTECT) max_length=50,
role = models.ForeignKey('RackRole', related_name='racks', blank=True, null=True, on_delete=models.PROTECT) blank=True,
serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number') null=True,
type = models.PositiveSmallIntegerField(choices=RACK_TYPE_CHOICES, blank=True, null=True, verbose_name='Type') verbose_name='Facility ID'
width = models.PositiveSmallIntegerField(choices=RACK_WIDTH_CHOICES, default=RACK_WIDTH_19IN, verbose_name='Width', )
help_text='Rail-to-rail width') site = models.ForeignKey(
u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)', to='dcim.Site',
validators=[MinValueValidator(1), MaxValueValidator(100)]) on_delete=models.PROTECT,
desc_units = models.BooleanField(default=False, verbose_name='Descending units', related_name='racks'
help_text='Units are numbered top-to-bottom') )
comments = models.TextField(blank=True) group = models.ForeignKey(
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') to='dcim.RackGroup',
images = GenericRelation(ImageAttachment) on_delete=models.SET_NULL,
related_name='racks',
blank=True,
null=True
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='racks',
blank=True,
null=True
)
role = models.ForeignKey(
to='dcim.RackRole',
on_delete=models.PROTECT,
related_name='racks',
blank=True,
null=True
)
serial = models.CharField(
max_length=50,
blank=True,
verbose_name='Serial number'
)
type = models.PositiveSmallIntegerField(
choices=RACK_TYPE_CHOICES,
blank=True,
null=True,
verbose_name='Type'
)
width = models.PositiveSmallIntegerField(
choices=RACK_WIDTH_CHOICES,
default=RACK_WIDTH_19IN,
verbose_name='Width',
help_text='Rail-to-rail width'
)
u_height = models.PositiveSmallIntegerField(
default=42,
verbose_name='Height (U)',
validators=[MinValueValidator(1), MaxValueValidator(100)]
)
desc_units = models.BooleanField(
default=False,
verbose_name='Descending units',
help_text='Units are numbered top-to-bottom'
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
images = GenericRelation(
to='extras.ImageAttachment'
)
objects = RackManager() objects = RackManager()
@ -438,12 +569,31 @@ class RackReservation(models.Model):
""" """
One or more reserved units within a Rack. One or more reserved units within a Rack.
""" """
rack = models.ForeignKey('Rack', related_name='reservations', on_delete=models.CASCADE) rack = models.ForeignKey(
units = ArrayField(models.PositiveSmallIntegerField()) to='dcim.Rack',
created = models.DateTimeField(auto_now_add=True) on_delete=models.CASCADE,
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='rackreservations', on_delete=models.PROTECT) related_name='reservations'
user = models.ForeignKey(User, on_delete=models.PROTECT) )
description = models.CharField(max_length=100) units = ArrayField(
base_field=models.PositiveSmallIntegerField()
)
created = models.DateTimeField(
auto_now_add=True
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='rackreservations',
blank=True,
null=True
)
user = models.ForeignKey(
to=User,
on_delete=models.PROTECT
)
description = models.CharField(
max_length=100
)
class Meta: class Meta:
ordering = ['created'] ordering = ['created']
@ -496,8 +646,13 @@ class Manufacturer(models.Model):
""" """
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell. A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
csv_headers = ['name', 'slug'] csv_headers = ['name', 'slug']
@ -533,27 +688,63 @@ class DeviceType(models.Model, CustomFieldModel):
When a new Device of this type is created, the appropriate console, power, and interface objects (as defined by the When a new Device of this type is created, the appropriate console, power, and interface objects (as defined by the
DeviceType) are automatically created as well. DeviceType) are automatically created as well.
""" """
manufacturer = models.ForeignKey('Manufacturer', related_name='device_types', on_delete=models.PROTECT) manufacturer = models.ForeignKey(
model = models.CharField(max_length=50) to='dcim.Manufacturer',
on_delete=models.PROTECT,
related_name='device_types'
)
model = models.CharField(
max_length=50
)
slug = models.SlugField() slug = models.SlugField()
part_number = models.CharField(max_length=50, blank=True, help_text="Discrete part number (optional)") part_number = models.CharField(
u_height = models.PositiveSmallIntegerField(verbose_name='Height (U)', default=1) max_length=50,
is_full_depth = models.BooleanField(default=True, verbose_name="Is full depth", blank=True,
help_text="Device consumes both front and rear rack faces") help_text='Discrete part number (optional)'
interface_ordering = models.PositiveSmallIntegerField(choices=IFACE_ORDERING_CHOICES, )
default=IFACE_ORDERING_POSITION) u_height = models.PositiveSmallIntegerField(
is_console_server = models.BooleanField(default=False, verbose_name='Is a console server', default=1,
help_text="This type of device has console server ports") verbose_name='Height (U)'
is_pdu = models.BooleanField(default=False, verbose_name='Is a PDU', )
help_text="This type of device has power outlets") is_full_depth = models.BooleanField(
is_network_device = models.BooleanField(default=True, verbose_name='Is a network device', default=True,
help_text="This type of device has network interfaces") verbose_name='Is full depth',
subdevice_role = models.NullBooleanField(default=None, verbose_name='Parent/child status', help_text='Device consumes both front and rear rack faces'
)
interface_ordering = models.PositiveSmallIntegerField(
choices=IFACE_ORDERING_CHOICES,
default=IFACE_ORDERING_POSITION
)
is_console_server = models.BooleanField(
default=False,
verbose_name='Is a console server',
help_text='This type of device has console server ports'
)
is_pdu = models.BooleanField(
default=False,
verbose_name='Is a PDU',
help_text='This type of device has power outlets'
)
is_network_device = models.BooleanField(
default=True,
verbose_name='Is a network device',
help_text='This type of device has network interfaces'
)
subdevice_role = models.NullBooleanField(
default=None,
verbose_name='Parent/child status',
choices=SUBDEVICE_ROLE_CHOICES, choices=SUBDEVICE_ROLE_CHOICES,
help_text="Parent devices house child devices in device bays. Select " help_text='Parent devices house child devices in device bays. Select '
"\"None\" if this device type is neither a parent nor a child.") '"None" if this device type is neither a parent nor a child.'
comments = models.TextField(blank=True) )
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = [ csv_headers = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
@ -658,8 +849,14 @@ class ConsolePortTemplate(models.Model):
""" """
A template for a ConsolePort to be created for a new Device. A template for a ConsolePort to be created for a new Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='console_port_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='console_port_templates'
)
name = models.CharField(
max_length=50
)
class Meta: class Meta:
ordering = ['device_type', 'name'] ordering = ['device_type', 'name']
@ -674,8 +871,14 @@ class ConsoleServerPortTemplate(models.Model):
""" """
A template for a ConsoleServerPort to be created for a new Device. A template for a ConsoleServerPort to be created for a new Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='cs_port_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='cs_port_templates'
)
name = models.CharField(
max_length=50
)
class Meta: class Meta:
ordering = ['device_type', 'name'] ordering = ['device_type', 'name']
@ -690,8 +893,14 @@ class PowerPortTemplate(models.Model):
""" """
A template for a PowerPort to be created for a new Device. A template for a PowerPort to be created for a new Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='power_port_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='power_port_templates'
)
name = models.CharField(
max_length=50
)
class Meta: class Meta:
ordering = ['device_type', 'name'] ordering = ['device_type', 'name']
@ -706,8 +915,14 @@ class PowerOutletTemplate(models.Model):
""" """
A template for a PowerOutlet to be created for a new Device. A template for a PowerOutlet to be created for a new Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='power_outlet_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='power_outlet_templates'
)
name = models.CharField(
max_length=50
)
class Meta: class Meta:
ordering = ['device_type', 'name'] ordering = ['device_type', 'name']
@ -722,10 +937,22 @@ class InterfaceTemplate(models.Model):
""" """
A template for a physical data interface on a new Device. A template for a physical data interface on a new Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='interface_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=64) to='dcim.DeviceType',
form_factor = models.PositiveSmallIntegerField(choices=IFACE_FF_CHOICES, default=IFACE_FF_10GE_SFP_PLUS) on_delete=models.CASCADE,
mgmt_only = models.BooleanField(default=False, verbose_name='Management only') related_name='interface_templates'
)
name = models.CharField(
max_length=64
)
form_factor = models.PositiveSmallIntegerField(
choices=IFACE_FF_CHOICES,
default=IFACE_FF_10GE_SFP_PLUS
)
mgmt_only = models.BooleanField(
default=False,
verbose_name='Management only'
)
objects = InterfaceQuerySet.as_manager() objects = InterfaceQuerySet.as_manager()
@ -742,8 +969,14 @@ class DeviceBayTemplate(models.Model):
""" """
A template for a DeviceBay to be created for a new parent Device. A template for a DeviceBay to be created for a new parent Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='device_bay_templates', on_delete=models.CASCADE) device_type = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='device_bay_templates'
)
name = models.CharField(
max_length=50
)
class Meta: class Meta:
ordering = ['device_type', 'name'] ordering = ['device_type', 'name']
@ -764,13 +997,18 @@ class DeviceRole(models.Model):
color to be used when displaying rack elevations. The vm_role field determines whether the role is applicable to color to be used when displaying rack elevations. The vm_role field determines whether the role is applicable to
virtual machines as well. virtual machines as well.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
color = ColorField() color = ColorField()
vm_role = models.BooleanField( vm_role = models.BooleanField(
default=True, default=True,
verbose_name="VM Role", verbose_name='VM Role',
help_text="Virtual machines may be assigned to this role" help_text='Virtual machines may be assigned to this role'
) )
csv_headers = ['name', 'slug', 'color', 'vm_role'] csv_headers = ['name', 'slug', 'color', 'vm_role']
@ -800,27 +1038,32 @@ class Platform(models.Model):
NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by
specifying a NAPALM driver. specifying a NAPALM driver.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
to='Manufacturer', to='dcim.Manufacturer',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='platforms', related_name='platforms',
blank=True, blank=True,
null=True, null=True,
help_text="Optionally limit this platform to devices of a certain manufacturer" help_text='Optionally limit this platform to devices of a certain manufacturer'
) )
napalm_driver = models.CharField( napalm_driver = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='NAPALM driver', verbose_name='NAPALM driver',
help_text="The name of the NAPALM driver to use when interacting with devices" help_text='The name of the NAPALM driver to use when interacting with devices'
) )
rpc_client = models.CharField( rpc_client = models.CharField(
max_length=30, max_length=30,
choices=RPC_CLIENT_CHOICES, choices=RPC_CLIENT_CHOICES,
blank=True, blank=True,
verbose_name="Legacy RPC client" verbose_name='Legacy RPC client'
) )
csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver'] csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver']
@ -862,30 +1105,93 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
by the component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the by the component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the
creation of a Device. creation of a Device.
""" """
device_type = models.ForeignKey('DeviceType', related_name='instances', on_delete=models.PROTECT) device_type = models.ForeignKey(
device_role = models.ForeignKey('DeviceRole', related_name='devices', on_delete=models.PROTECT) to='dcim.DeviceType',
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='devices', on_delete=models.PROTECT) on_delete=models.PROTECT,
platform = models.ForeignKey('Platform', related_name='devices', blank=True, null=True, on_delete=models.SET_NULL) related_name='instances'
name = NullableCharField(max_length=64, blank=True, null=True, unique=True) )
serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number') device_role = models.ForeignKey(
to='dcim.DeviceRole',
on_delete=models.PROTECT,
related_name='devices'
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='devices',
blank=True,
null=True
)
platform = models.ForeignKey(
to='dcim.Platform',
on_delete=models.SET_NULL,
related_name='devices',
blank=True,
null=True
)
name = NullableCharField(
max_length=64,
blank=True,
null=True,
unique=True
)
serial = models.CharField(
max_length=50,
blank=True,
verbose_name='Serial number'
)
asset_tag = NullableCharField( asset_tag = NullableCharField(
max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag', max_length=50,
blank=True,
null=True,
unique=True,
verbose_name='Asset tag',
help_text='A unique tag used to identify this device' help_text='A unique tag used to identify this device'
) )
site = models.ForeignKey('Site', related_name='devices', on_delete=models.PROTECT) site = models.ForeignKey(
rack = models.ForeignKey('Rack', related_name='devices', blank=True, null=True, on_delete=models.PROTECT) to='dcim.Site',
on_delete=models.PROTECT,
related_name='devices'
)
rack = models.ForeignKey(
to='dcim.Rack',
on_delete=models.PROTECT,
related_name='devices',
blank=True,
null=True
)
position = models.PositiveSmallIntegerField( position = models.PositiveSmallIntegerField(
blank=True, null=True, validators=[MinValueValidator(1)], verbose_name='Position (U)', blank=True,
null=True,
validators=[MinValueValidator(1)],
verbose_name='Position (U)',
help_text='The lowest-numbered unit occupied by the device' help_text='The lowest-numbered unit occupied by the device'
) )
face = models.PositiveSmallIntegerField(blank=True, null=True, choices=RACK_FACE_CHOICES, verbose_name='Rack face') face = models.PositiveSmallIntegerField(
status = models.PositiveSmallIntegerField(choices=DEVICE_STATUS_CHOICES, default=DEVICE_STATUS_ACTIVE, verbose_name='Status') blank=True,
null=True,
choices=RACK_FACE_CHOICES,
verbose_name='Rack face'
)
status = models.PositiveSmallIntegerField(
choices=DEVICE_STATUS_CHOICES,
default=DEVICE_STATUS_ACTIVE,
verbose_name='Status'
)
primary_ip4 = models.OneToOneField( primary_ip4 = models.OneToOneField(
'ipam.IPAddress', related_name='primary_ip4_for', on_delete=models.SET_NULL, blank=True, null=True, to='ipam.IPAddress',
on_delete=models.SET_NULL,
related_name='primary_ip4_for',
blank=True,
null=True,
verbose_name='Primary IPv4' verbose_name='Primary IPv4'
) )
primary_ip6 = models.OneToOneField( primary_ip6 = models.OneToOneField(
'ipam.IPAddress', related_name='primary_ip6_for', on_delete=models.SET_NULL, blank=True, null=True, to='ipam.IPAddress',
on_delete=models.SET_NULL,
related_name='primary_ip6_for',
blank=True,
null=True,
verbose_name='Primary IPv6' verbose_name='Primary IPv6'
) )
cluster = models.ForeignKey( cluster = models.ForeignKey(
@ -912,9 +1218,17 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
null=True, null=True,
validators=[MaxValueValidator(255)] validators=[MaxValueValidator(255)]
) )
comments = models.TextField(blank=True) comments = models.TextField(
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') blank=True
images = GenericRelation(ImageAttachment) )
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
images = GenericRelation(
to='extras.ImageAttachment'
)
objects = DeviceManager() objects = DeviceManager()
@ -1169,11 +1483,26 @@ class ConsolePort(models.Model):
""" """
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
""" """
device = models.ForeignKey('Device', related_name='console_ports', on_delete=models.CASCADE) device = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.Device',
cs_port = models.OneToOneField('ConsoleServerPort', related_name='connected_console', on_delete=models.SET_NULL, on_delete=models.CASCADE,
verbose_name='Console server port', blank=True, null=True) related_name='console_ports'
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED) )
name = models.CharField(
max_length=50
)
cs_port = models.OneToOneField(
to='dcim.ConsoleServerPort',
on_delete=models.SET_NULL,
related_name='connected_console',
verbose_name='Console server port',
blank=True,
null=True
)
connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES,
default=CONNECTION_STATUS_CONNECTED
)
csv_headers = ['console_server', 'cs_port', 'device', 'console_port', 'connection_status'] csv_headers = ['console_server', 'cs_port', 'device', 'console_port', 'connection_status']
@ -1216,8 +1545,14 @@ class ConsoleServerPort(models.Model):
""" """
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
""" """
device = models.ForeignKey('Device', related_name='cs_ports', on_delete=models.CASCADE) device = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.Device',
on_delete=models.CASCADE,
related_name='cs_ports'
)
name = models.CharField(
max_length=50
)
objects = ConsoleServerPortManager() objects = ConsoleServerPortManager()
@ -1251,11 +1586,25 @@ class PowerPort(models.Model):
""" """
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
""" """
device = models.ForeignKey('Device', related_name='power_ports', on_delete=models.CASCADE) device = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.Device',
power_outlet = models.OneToOneField('PowerOutlet', related_name='connected_port', on_delete=models.SET_NULL, on_delete=models.CASCADE,
blank=True, null=True) related_name='power_ports'
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED) )
name = models.CharField(
max_length=50
)
power_outlet = models.OneToOneField(
to='dcim.PowerOutlet',
on_delete=models.SET_NULL,
related_name='connected_port',
blank=True,
null=True
)
connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES,
default=CONNECTION_STATUS_CONNECTED
)
csv_headers = ['pdu', 'power_outlet', 'device', 'power_port', 'connection_status'] csv_headers = ['pdu', 'power_outlet', 'device', 'power_port', 'connection_status']
@ -1298,8 +1647,14 @@ class PowerOutlet(models.Model):
""" """
A physical power outlet (output) within a Device which provides power to a PowerPort. A physical power outlet (output) within a Device which provides power to a PowerPort.
""" """
device = models.ForeignKey('Device', related_name='power_outlets', on_delete=models.CASCADE) device = models.ForeignKey(
name = models.CharField(max_length=50) to='dcim.Device',
on_delete=models.CASCADE,
related_name='power_outlets'
)
name = models.CharField(
max_length=50
)
objects = PowerOutletManager() objects = PowerOutletManager()
@ -1356,17 +1711,35 @@ class Interface(models.Model):
blank=True, blank=True,
verbose_name='Parent LAG' verbose_name='Parent LAG'
) )
name = models.CharField(max_length=64) name = models.CharField(
form_factor = models.PositiveSmallIntegerField(choices=IFACE_FF_CHOICES, default=IFACE_FF_10GE_SFP_PLUS) max_length=64
enabled = models.BooleanField(default=True) )
mac_address = MACAddressField(null=True, blank=True, verbose_name='MAC Address') form_factor = models.PositiveSmallIntegerField(
mtu = models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='MTU') choices=IFACE_FF_CHOICES,
default=IFACE_FF_10GE_SFP_PLUS
)
enabled = models.BooleanField(
default=True
)
mac_address = MACAddressField(
null=True,
blank=True,
verbose_name='MAC Address'
)
mtu = models.PositiveSmallIntegerField(
blank=True,
null=True,
verbose_name='MTU'
)
mgmt_only = models.BooleanField( mgmt_only = models.BooleanField(
default=False, default=False,
verbose_name='OOB Management', verbose_name='OOB Management',
help_text="This interface is used only for out-of-band management" help_text='This interface is used only for out-of-band management'
)
description = models.CharField(
max_length=100,
blank=True
) )
description = models.CharField(max_length=100, blank=True)
mode = models.PositiveSmallIntegerField( mode = models.PositiveSmallIntegerField(
choices=IFACE_MODE_CHOICES, choices=IFACE_MODE_CHOICES,
blank=True, blank=True,
@ -1375,16 +1748,16 @@ class Interface(models.Model):
untagged_vlan = models.ForeignKey( untagged_vlan = models.ForeignKey(
to='ipam.VLAN', to='ipam.VLAN',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name='interfaces_as_untagged',
null=True, null=True,
blank=True, blank=True,
verbose_name='Untagged VLAN', verbose_name='Untagged VLAN'
related_name='interfaces_as_untagged'
) )
tagged_vlans = models.ManyToManyField( tagged_vlans = models.ManyToManyField(
to='ipam.VLAN', to='ipam.VLAN',
related_name='interfaces_as_tagged',
blank=True, blank=True,
verbose_name='Tagged VLANs', verbose_name='Tagged VLANs'
related_name='interfaces_as_tagged'
) )
objects = InterfaceQuerySet.as_manager() objects = InterfaceQuerySet.as_manager()
@ -1525,10 +1898,21 @@ class InterfaceConnection(models.Model):
An InterfaceConnection represents a symmetrical, one-to-one connection between two Interfaces. There is no An InterfaceConnection represents a symmetrical, one-to-one connection between two Interfaces. There is no
significant difference between the interface_a and interface_b fields. significant difference between the interface_a and interface_b fields.
""" """
interface_a = models.OneToOneField('Interface', related_name='connected_as_a', on_delete=models.CASCADE) interface_a = models.OneToOneField(
interface_b = models.OneToOneField('Interface', related_name='connected_as_b', on_delete=models.CASCADE) to='dcim.Interface',
connection_status = models.BooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED, on_delete=models.CASCADE,
verbose_name='Status') related_name='connected_as_a'
)
interface_b = models.OneToOneField(
to='dcim.Interface',
on_delete=models.CASCADE,
related_name='connected_as_b'
)
connection_status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES,
default=CONNECTION_STATUS_CONNECTED,
verbose_name='Status'
)
csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'] csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status']
@ -1560,10 +1944,22 @@ class DeviceBay(models.Model):
""" """
An empty space within a Device which can house a child device An empty space within a Device which can house a child device
""" """
device = models.ForeignKey('Device', related_name='device_bays', on_delete=models.CASCADE) device = models.ForeignKey(
name = models.CharField(max_length=50, verbose_name='Name') to='dcim.Device',
installed_device = models.OneToOneField('Device', related_name='parent_bay', on_delete=models.SET_NULL, blank=True, on_delete=models.CASCADE,
null=True) related_name='device_bays'
)
name = models.CharField(
max_length=50,
verbose_name='Name'
)
installed_device = models.OneToOneField(
to='dcim.Device',
on_delete=models.SET_NULL,
related_name='parent_bay',
blank=True,
null=True
)
class Meta: class Meta:
ordering = ['device', 'name'] ordering = ['device', 'name']
@ -1598,20 +1994,55 @@ class InventoryItem(models.Model):
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
InventoryItems are used only for inventory purposes. InventoryItems are used only for inventory purposes.
""" """
device = models.ForeignKey('Device', related_name='inventory_items', on_delete=models.CASCADE) device = models.ForeignKey(
parent = models.ForeignKey('self', related_name='child_items', blank=True, null=True, on_delete=models.CASCADE) to='dcim.Device',
name = models.CharField(max_length=50, verbose_name='Name') on_delete=models.CASCADE,
manufacturer = models.ForeignKey( related_name='inventory_items'
'Manufacturer', models.PROTECT, related_name='inventory_items', blank=True, null=True )
parent = models.ForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='child_items',
blank=True,
null=True
)
name = models.CharField(
max_length=50,
verbose_name='Name'
)
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
on_delete=models.PROTECT,
related_name='inventory_items',
blank=True,
null=True
)
part_id = models.CharField(
max_length=50,
verbose_name='Part ID',
blank=True
)
serial = models.CharField(
max_length=50,
verbose_name='Serial number',
blank=True
) )
part_id = models.CharField(max_length=50, verbose_name='Part ID', blank=True)
serial = models.CharField(max_length=50, verbose_name='Serial number', blank=True)
asset_tag = NullableCharField( asset_tag = NullableCharField(
max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag', max_length=50,
unique=True,
blank=True,
null=True,
verbose_name='Asset tag',
help_text='A unique tag used to identify this item' help_text='A unique tag used to identify this item'
) )
discovered = models.BooleanField(default=False, verbose_name='Discovered') discovered = models.BooleanField(
description = models.CharField(max_length=100, blank=True) default=False,
verbose_name='Discovered'
)
description = models.CharField(
max_length=100,
blank=True
)
csv_headers = [ csv_headers = [
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',

View File

@ -73,7 +73,8 @@ class CustomField(models.Model):
label = models.CharField( label = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
help_text='Name of the field as displayed to users (if not provided, the field\'s name will be used)' help_text='Name of the field as displayed to users (if not provided, '
'the field\'s name will be used)'
) )
description = models.CharField( description = models.CharField(
max_length=100, max_length=100,
@ -81,17 +82,20 @@ class CustomField(models.Model):
) )
required = models.BooleanField( required = models.BooleanField(
default=False, default=False,
help_text='If true, this field is required when creating new objects or editing an existing object.' help_text='If true, this field is required when creating new objects '
'or editing an existing object.'
) )
filter_logic = models.PositiveSmallIntegerField( filter_logic = models.PositiveSmallIntegerField(
choices=CF_FILTER_CHOICES, choices=CF_FILTER_CHOICES,
default=CF_FILTER_LOOSE, default=CF_FILTER_LOOSE,
help_text="Loose matches any instance of a given string; exact matches the entire field." help_text='Loose matches any instance of a given string; exact '
'matches the entire field.'
) )
default = models.CharField( default = models.CharField(
max_length=100, max_length=100,
blank=True, blank=True,
help_text='Default value for the field. Use "true" or "false" for booleans. N/A for selection fields.' help_text='Default value for the field. Use "true" or "false" for '
'booleans. N/A for selection fields.'
) )
weight = models.PositiveSmallIntegerField( weight = models.PositiveSmallIntegerField(
default=100, default=100,
@ -143,11 +147,24 @@ class CustomField(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class CustomFieldValue(models.Model): class CustomFieldValue(models.Model):
field = models.ForeignKey('CustomField', related_name='values', on_delete=models.CASCADE) field = models.ForeignKey(
obj_type = models.ForeignKey(ContentType, related_name='+', on_delete=models.PROTECT) to='extras.CustomField',
on_delete=models.CASCADE,
related_name='values'
)
obj_type = models.ForeignKey(
to=ContentType,
on_delete=models.PROTECT,
related_name='+'
)
obj_id = models.PositiveIntegerField() obj_id = models.PositiveIntegerField()
obj = GenericForeignKey('obj_type', 'obj_id') obj = GenericForeignKey(
serialized_value = models.CharField(max_length=255) ct_field='obj_type',
fk_field='obj_id'
)
serialized_value = models.CharField(
max_length=255
)
class Meta: class Meta:
ordering = ['obj_type', 'obj_id'] ordering = ['obj_type', 'obj_id']
@ -174,10 +191,19 @@ class CustomFieldValue(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class CustomFieldChoice(models.Model): class CustomFieldChoice(models.Model):
field = models.ForeignKey('CustomField', related_name='choices', limit_choices_to={'type': CF_TYPE_SELECT}, field = models.ForeignKey(
on_delete=models.CASCADE) to='extras.CustomField',
value = models.CharField(max_length=100) on_delete=models.CASCADE,
weight = models.PositiveSmallIntegerField(default=100, help_text="Higher weights appear lower in the list") related_name='choices',
limit_choices_to={'type': CF_TYPE_SELECT}
)
value = models.CharField(
max_length=100
)
weight = models.PositiveSmallIntegerField(
default=100,
help_text='Higher weights appear lower in the list'
)
class Meta: class Meta:
ordering = ['field', 'weight', 'value'] ordering = ['field', 'weight', 'value']
@ -203,11 +229,24 @@ class CustomFieldChoice(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Graph(models.Model): class Graph(models.Model):
type = models.PositiveSmallIntegerField(choices=GRAPH_TYPE_CHOICES) type = models.PositiveSmallIntegerField(
weight = models.PositiveSmallIntegerField(default=1000) choices=GRAPH_TYPE_CHOICES
name = models.CharField(max_length=100, verbose_name='Name') )
source = models.CharField(max_length=500, verbose_name='Source URL') weight = models.PositiveSmallIntegerField(
link = models.URLField(verbose_name='Link URL', blank=True) default=1000
)
name = models.CharField(
max_length=100,
verbose_name='Name'
)
source = models.CharField(
max_length=500,
verbose_name='Source URL'
)
link = models.URLField(
blank=True,
verbose_name='Link URL'
)
class Meta: class Meta:
ordering = ['type', 'weight', 'name'] ordering = ['type', 'weight', 'name']
@ -233,13 +272,26 @@ class Graph(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class ExportTemplate(models.Model): class ExportTemplate(models.Model):
content_type = models.ForeignKey( content_type = models.ForeignKey(
ContentType, limit_choices_to={'model__in': EXPORTTEMPLATE_MODELS}, on_delete=models.CASCADE to=ContentType,
on_delete=models.CASCADE,
limit_choices_to={'model__in': EXPORTTEMPLATE_MODELS}
)
name = models.CharField(
max_length=100
)
description = models.CharField(
max_length=200,
blank=True
) )
name = models.CharField(max_length=100)
description = models.CharField(max_length=200, blank=True)
template_code = models.TextField() template_code = models.TextField()
mime_type = models.CharField(max_length=15, blank=True) mime_type = models.CharField(
file_extension = models.CharField(max_length=15, blank=True) max_length=15,
blank=True
)
file_extension = models.CharField(
max_length=15,
blank=True
)
class Meta: class Meta:
ordering = ['content_type', 'name'] ordering = ['content_type', 'name']
@ -278,25 +330,35 @@ class ExportTemplate(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class TopologyMap(models.Model): class TopologyMap(models.Model):
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
type = models.PositiveSmallIntegerField( type = models.PositiveSmallIntegerField(
choices=TOPOLOGYMAP_TYPE_CHOICES, choices=TOPOLOGYMAP_TYPE_CHOICES,
default=TOPOLOGYMAP_TYPE_NETWORK default=TOPOLOGYMAP_TYPE_NETWORK
) )
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
on_delete=models.CASCADE,
related_name='topology_maps', related_name='topology_maps',
blank=True, blank=True,
null=True, null=True
on_delete=models.CASCADE
) )
device_patterns = models.TextField( device_patterns = models.TextField(
help_text="Identify devices to include in the diagram using regular expressions, one per line. Each line will " help_text='Identify devices to include in the diagram using regular '
"result in a new tier of the drawing. Separate multiple regexes within a line using semicolons. " 'expressions, one per line. Each line will result in a new '
"Devices will be rendered in the order they are defined." 'tier of the drawing. Separate multiple regexes within a '
'line using semicolons. Devices will be rendered in the '
'order they are defined.'
)
description = models.CharField(
max_length=100,
blank=True
) )
description = models.CharField(max_length=100, blank=True)
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@ -432,14 +494,29 @@ class ImageAttachment(models.Model):
""" """
An uploaded image which is associated with an object. An uploaded image which is associated with an object.
""" """
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(
to=ContentType,
on_delete=models.CASCADE
)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
parent = GenericForeignKey('content_type', 'object_id') parent = GenericForeignKey(
image = models.ImageField(upload_to=image_upload, height_field='image_height', width_field='image_width') ct_field='content_type',
fk_field='object_id'
)
image = models.ImageField(
upload_to=image_upload,
height_field='image_height',
width_field='image_width'
)
image_height = models.PositiveSmallIntegerField() image_height = models.PositiveSmallIntegerField()
image_width = models.PositiveSmallIntegerField() image_width = models.PositiveSmallIntegerField()
name = models.CharField(max_length=50, blank=True) name = models.CharField(
created = models.DateTimeField(auto_now_add=True) max_length=50,
blank=True
)
created = models.DateTimeField(
auto_now_add=True
)
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@ -482,9 +559,20 @@ class ReportResult(models.Model):
""" """
This model stores the results from running a user-defined report. This model stores the results from running a user-defined report.
""" """
report = models.CharField(max_length=255, unique=True) report = models.CharField(
created = models.DateTimeField(auto_now_add=True) max_length=255,
user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='+', blank=True, null=True) unique=True
)
created = models.DateTimeField(
auto_now_add=True
)
user = models.ForeignKey(
to=User,
on_delete=models.SET_NULL,
related_name='+',
blank=True,
null=True
)
failed = models.BooleanField() failed = models.BooleanField()
data = JSONField() data = JSONField()
@ -544,12 +632,29 @@ class UserAction(models.Model):
""" """
A record of an action (add, edit, or delete) performed on an object by a User. A record of an action (add, edit, or delete) performed on an object by a User.
""" """
time = models.DateTimeField(auto_now_add=True, editable=False) time = models.DateTimeField(
user = models.ForeignKey(User, related_name='actions', on_delete=models.CASCADE) auto_now_add=True,
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) editable=False
object_id = models.PositiveIntegerField(blank=True, null=True) )
action = models.PositiveSmallIntegerField(choices=ACTION_CHOICES) user = models.ForeignKey(
message = models.TextField(blank=True) to=User,
on_delete=models.CASCADE,
related_name='actions'
)
content_type = models.ForeignKey(
to=ContentType,
on_delete=models.CASCADE
)
object_id = models.PositiveIntegerField(
blank=True,
null=True
)
action = models.PositiveSmallIntegerField(
choices=ACTION_CHOICES
)
message = models.TextField(
blank=True
)
objects = UserActionManager() objects = UserActionManager()

View File

@ -12,8 +12,7 @@ from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from dcim.models import Interface from dcim.models import Interface
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel
from tenancy.models import Tenant
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .constants import * from .constants import *
from .fields import IPNetworkField, IPAddressField from .fields import IPNetworkField, IPAddressField
@ -27,13 +26,35 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
table). Prefixes and IPAddresses can optionally be assigned to VRFs. (Prefixes and IPAddresses not assigned to a VRF table). Prefixes and IPAddresses can optionally be assigned to VRFs. (Prefixes and IPAddresses not assigned to a VRF
are said to exist in the "global" table.) are said to exist in the "global" table.)
""" """
name = models.CharField(max_length=50) name = models.CharField(
rd = models.CharField(max_length=21, unique=True, verbose_name='Route distinguisher') max_length=50
tenant = models.ForeignKey(Tenant, related_name='vrfs', blank=True, null=True, on_delete=models.PROTECT) )
enforce_unique = models.BooleanField(default=True, verbose_name='Enforce unique space', rd = models.CharField(
help_text="Prevent duplicate prefixes/IP addresses within this VRF") max_length=21,
description = models.CharField(max_length=100, blank=True) unique=True,
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') verbose_name='Route distinguisher'
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='vrfs',
blank=True,
null=True
)
enforce_unique = models.BooleanField(
default=True,
verbose_name='Enforce unique space',
help_text='Prevent duplicate prefixes/IP addresses within this VRF'
)
description = models.CharField(
max_length=100,
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description'] csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
@ -70,10 +91,18 @@ class RIR(models.Model):
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
space. This can be an organization like ARIN or RIPE, or a governing standard such as RFC 1918. space. This can be an organization like ARIN or RIPE, or a governing standard such as RFC 1918.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
is_private = models.BooleanField(default=False, verbose_name='Private', unique=True
help_text='IP space managed by this RIR is considered private') )
slug = models.SlugField(
unique=True
)
is_private = models.BooleanField(
default=False,
verbose_name='Private',
help_text='IP space managed by this RIR is considered private'
)
csv_headers = ['name', 'slug', 'is_private'] csv_headers = ['name', 'slug', 'is_private']
@ -102,12 +131,29 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR. the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR.
""" """
family = models.PositiveSmallIntegerField(choices=AF_CHOICES) family = models.PositiveSmallIntegerField(
choices=AF_CHOICES
)
prefix = IPNetworkField() prefix = IPNetworkField()
rir = models.ForeignKey('RIR', related_name='aggregates', on_delete=models.PROTECT, verbose_name='RIR') rir = models.ForeignKey(
date_added = models.DateField(blank=True, null=True) to='ipam.RIR',
description = models.CharField(max_length=100, blank=True) on_delete=models.PROTECT,
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') related_name='aggregates',
verbose_name='RIR'
)
date_added = models.DateField(
blank=True,
null=True
)
description = models.CharField(
max_length=100,
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = ['prefix', 'rir', 'date_added', 'description'] csv_headers = ['prefix', 'rir', 'date_added', 'description']
@ -178,9 +224,16 @@ class Role(models.Model):
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
"Management." "Management."
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
weight = models.PositiveSmallIntegerField(default=1000) unique=True
)
slug = models.SlugField(
unique=True
)
weight = models.PositiveSmallIntegerField(
default=1000
)
csv_headers = ['name', 'slug', 'weight'] csv_headers = ['name', 'slug', 'weight']
@ -205,22 +258,71 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
assigned to a VLAN where appropriate. assigned to a VLAN where appropriate.
""" """
family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False) family = models.PositiveSmallIntegerField(
prefix = IPNetworkField(help_text="IPv4 or IPv6 network with mask") choices=AF_CHOICES,
site = models.ForeignKey('dcim.Site', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True) editable=False
vrf = models.ForeignKey('VRF', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True, )
verbose_name='VRF') prefix = IPNetworkField(
tenant = models.ForeignKey(Tenant, related_name='prefixes', blank=True, null=True, on_delete=models.PROTECT) help_text='IPv4 or IPv6 network with mask'
vlan = models.ForeignKey('VLAN', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True, )
verbose_name='VLAN') site = models.ForeignKey(
status = models.PositiveSmallIntegerField('Status', choices=PREFIX_STATUS_CHOICES, default=PREFIX_STATUS_ACTIVE, to='dcim.Site',
help_text="Operational status of this prefix") on_delete=models.PROTECT,
role = models.ForeignKey('Role', related_name='prefixes', on_delete=models.SET_NULL, blank=True, null=True, related_name='prefixes',
help_text="The primary function of this prefix") blank=True,
is_pool = models.BooleanField(verbose_name='Is a pool', default=False, null=True
help_text="All IP addresses within this prefix are considered usable") )
description = models.CharField(max_length=100, blank=True) vrf = models.ForeignKey(
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') to='ipam.VRF',
on_delete=models.PROTECT,
related_name='prefixes',
blank=True,
null=True,
verbose_name='VRF'
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='prefixes',
blank=True,
null=True
)
vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.PROTECT,
related_name='prefixes',
blank=True,
null=True,
verbose_name='VLAN'
)
status = models.PositiveSmallIntegerField(
choices=PREFIX_STATUS_CHOICES,
default=PREFIX_STATUS_ACTIVE,
verbose_name='Status',
help_text='Operational status of this prefix'
)
role = models.ForeignKey(
to='ipam.Role',
on_delete=models.SET_NULL,
related_name='prefixes',
blank=True,
null=True,
help_text='The primary function of this prefix'
)
is_pool = models.BooleanField(
verbose_name='Is a pool',
default=False,
help_text='All IP addresses within this prefix are considered usable'
)
description = models.CharField(
max_length=100,
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
objects = PrefixQuerySet.as_manager() objects = PrefixQuerySet.as_manager()
@ -400,25 +502,66 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP. which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
""" """
family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False) family = models.PositiveSmallIntegerField(
address = IPAddressField(help_text="IPv4 or IPv6 address (with mask)") choices=AF_CHOICES,
vrf = models.ForeignKey('VRF', related_name='ip_addresses', on_delete=models.PROTECT, blank=True, null=True, editable=False
verbose_name='VRF') )
tenant = models.ForeignKey(Tenant, related_name='ip_addresses', blank=True, null=True, on_delete=models.PROTECT) address = IPAddressField(
help_text='IPv4 or IPv6 address (with mask)'
)
vrf = models.ForeignKey(
to='ipam.VRF',
on_delete=models.PROTECT,
related_name='ip_addresses',
blank=True,
null=True,
verbose_name='VRF'
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='ip_addresses',
blank=True,
null=True
)
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
'Status', choices=IPADDRESS_STATUS_CHOICES, default=IPADDRESS_STATUS_ACTIVE, choices=IPADDRESS_STATUS_CHOICES,
default=IPADDRESS_STATUS_ACTIVE,
verbose_name='Status',
help_text='The operational status of this IP' help_text='The operational status of this IP'
) )
role = models.PositiveSmallIntegerField( role = models.PositiveSmallIntegerField(
'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP' verbose_name='Role',
choices=IPADDRESS_ROLE_CHOICES,
blank=True,
null=True,
help_text='The functional role of this IP'
)
interface = models.ForeignKey(
to='dcim.Interface',
on_delete=models.CASCADE,
related_name='ip_addresses',
blank=True,
null=True
)
nat_inside = models.OneToOneField(
to='self',
on_delete=models.SET_NULL,
related_name='nat_outside',
blank=True,
null=True,
verbose_name='NAT (Inside)',
help_text='The IP for which this address is the "outside" IP'
)
description = models.CharField(
max_length=100,
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
) )
interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True,
null=True)
nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
null=True, verbose_name='NAT (Inside)',
help_text="The IP for which this address is the \"outside\" IP")
description = models.CharField(max_length=100, blank=True)
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
objects = IPAddressManager() objects = IPAddressManager()
@ -509,9 +652,17 @@ class VLANGroup(models.Model):
""" """
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique. A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
""" """
name = models.CharField(max_length=50) name = models.CharField(
max_length=50
)
slug = models.SlugField() slug = models.SlugField()
site = models.ForeignKey('dcim.Site', related_name='vlan_groups', on_delete=models.PROTECT, blank=True, null=True) site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
related_name='vlan_groups',
blank=True,
null=True
)
csv_headers = ['name', 'slug', 'site'] csv_headers = ['name', 'slug', 'site']
@ -558,18 +709,55 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero
or more Prefixes assigned to it. or more Prefixes assigned to it.
""" """
site = models.ForeignKey('dcim.Site', related_name='vlans', on_delete=models.PROTECT, blank=True, null=True) site = models.ForeignKey(
group = models.ForeignKey('VLANGroup', related_name='vlans', blank=True, null=True, on_delete=models.PROTECT) to='dcim.Site',
vid = models.PositiveSmallIntegerField(verbose_name='ID', validators=[ on_delete=models.PROTECT,
MinValueValidator(1), related_name='vlans',
MaxValueValidator(4094) blank=True,
]) null=True
name = models.CharField(max_length=64) )
tenant = models.ForeignKey(Tenant, related_name='vlans', blank=True, null=True, on_delete=models.PROTECT) group = models.ForeignKey(
status = models.PositiveSmallIntegerField('Status', choices=VLAN_STATUS_CHOICES, default=1) to='ipam.VLANGroup',
role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True) on_delete=models.PROTECT,
description = models.CharField(max_length=100, blank=True) related_name='vlans',
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') blank=True,
null=True
)
vid = models.PositiveSmallIntegerField(
verbose_name='ID',
validators=[MinValueValidator(1), MaxValueValidator(4094)]
)
name = models.CharField(
max_length=64
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='vlans',
blank=True,
null=True
)
status = models.PositiveSmallIntegerField(
choices=VLAN_STATUS_CHOICES,
default=1,
verbose_name='Status'
)
role = models.ForeignKey(
to='ipam.Role',
on_delete=models.SET_NULL,
related_name='vlans',
blank=True,
null=True
)
description = models.CharField(
max_length=100,
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description'] csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']

View File

@ -13,7 +13,6 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_bytes, python_2_unicode_compatible from django.utils.encoding import force_bytes, python_2_unicode_compatible
from dcim.models import Device
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .exceptions import InvalidKey from .exceptions import InvalidKey
from .hashers import SecretValidationHasher from .hashers import SecretValidationHasher
@ -54,9 +53,21 @@ class UserKey(CreatedUpdatedModel):
copy of the master encryption key. The encrypted instance of the master key can be decrypted only with the user's copy of the master encryption key. The encrypted instance of the master key can be decrypted only with the user's
matching (private) decryption key. matching (private) decryption key.
""" """
user = models.OneToOneField(User, related_name='user_key', editable=False, on_delete=models.CASCADE) user = models.OneToOneField(
public_key = models.TextField(verbose_name='RSA public key') to=User,
master_key_cipher = models.BinaryField(max_length=512, blank=True, null=True, editable=False) on_delete=models.CASCADE,
related_name='user_key',
editable=False
)
public_key = models.TextField(
verbose_name='RSA public key'
)
master_key_cipher = models.BinaryField(
max_length=512,
blank=True,
null=True,
editable=False
)
objects = UserKeyQuerySet.as_manager() objects = UserKeyQuerySet.as_manager()
@ -172,10 +183,23 @@ class SessionKey(models.Model):
""" """
A SessionKey stores a User's temporary key to be used for the encryption and decryption of secrets. A SessionKey stores a User's temporary key to be used for the encryption and decryption of secrets.
""" """
userkey = models.OneToOneField(UserKey, related_name='session_key', on_delete=models.CASCADE, editable=False) userkey = models.OneToOneField(
cipher = models.BinaryField(max_length=512, editable=False) to='secrets.UserKey',
hash = models.CharField(max_length=128, editable=False) on_delete=models.CASCADE,
created = models.DateTimeField(auto_now_add=True) related_name='session_key',
editable=False
)
cipher = models.BinaryField(
max_length=512,
editable=False
)
hash = models.CharField(
max_length=128,
editable=False
)
created = models.DateTimeField(
auto_now_add=True
)
key = None key = None
@ -234,10 +258,23 @@ class SecretRole(models.Model):
By default, only superusers will have access to decrypt Secrets. To allow other users to decrypt Secrets, grant them By default, only superusers will have access to decrypt Secrets. To allow other users to decrypt Secrets, grant them
access to the appropriate SecretRoles either individually or by group. access to the appropriate SecretRoles either individually or by group.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
users = models.ManyToManyField(User, related_name='secretroles', blank=True) unique=True
groups = models.ManyToManyField(Group, related_name='secretroles', blank=True) )
slug = models.SlugField(
unique=True
)
users = models.ManyToManyField(
to=User,
related_name='secretroles',
blank=True
)
groups = models.ManyToManyField(
to=Group,
related_name='secretroles',
blank=True
)
csv_headers = ['name', 'slug'] csv_headers = ['name', 'slug']
@ -276,11 +313,28 @@ class Secret(CreatedUpdatedModel):
A Secret can be up to 65,536 bytes (64KB) in length. Each secret string will be padded with random data to a minimum A Secret can be up to 65,536 bytes (64KB) in length. Each secret string will be padded with random data to a minimum
of 64 bytes during encryption in order to protect short strings from ciphertext analysis. of 64 bytes during encryption in order to protect short strings from ciphertext analysis.
""" """
device = models.ForeignKey(Device, related_name='secrets', on_delete=models.CASCADE) device = models.ForeignKey(
role = models.ForeignKey('SecretRole', related_name='secrets', on_delete=models.PROTECT) to='dcim.Device',
name = models.CharField(max_length=100, blank=True) on_delete=models.CASCADE,
ciphertext = models.BinaryField(editable=False, max_length=65568) # 16B IV + 2B pad length + {62-65550}B padded related_name='secrets'
hash = models.CharField(max_length=128, editable=False) )
role = models.ForeignKey(
to='secrets.SecretRole',
on_delete=models.PROTECT,
related_name='secrets'
)
name = models.CharField(
max_length=100,
blank=True
)
ciphertext = models.BinaryField(
max_length=65568, # 16B IV + 2B pad length + {62-65550}B padded
editable=False
)
hash = models.CharField(
max_length=128,
editable=False
)
plaintext = None plaintext = None
csv_headers = ['device', 'role', 'name', 'plaintext'] csv_headers = ['device', 'role', 'name', 'plaintext']

View File

@ -5,7 +5,7 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
@ -14,8 +14,13 @@ class TenantGroup(models.Model):
""" """
An arbitrary collection of Tenants. An arbitrary collection of Tenants.
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
csv_headers = ['name', 'slug'] csv_headers = ['name', 'slug']
@ -41,12 +46,33 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal
department. department.
""" """
name = models.CharField(max_length=30, unique=True) name = models.CharField(
slug = models.SlugField(unique=True) max_length=30,
group = models.ForeignKey('TenantGroup', related_name='tenants', blank=True, null=True, on_delete=models.SET_NULL) unique=True
description = models.CharField(max_length=100, blank=True, help_text="Long-form name (optional)") )
comments = models.TextField(blank=True) slug = models.SlugField(
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') unique=True
)
group = models.ForeignKey(
to='tenancy.TenantGroup',
on_delete=models.SET_NULL,
related_name='tenants',
blank=True,
null=True
)
description = models.CharField(
max_length=100,
blank=True,
help_text='Long-form name (optional)'
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
csv_headers = ['name', 'slug', 'group', 'description', 'comments'] csv_headers = ['name', 'slug', 'group', 'description', 'comments']

View File

@ -16,12 +16,31 @@ class Token(models.Model):
An API token used for user authentication. This extends the stock model to allow each user to have multiple tokens. An API token used for user authentication. This extends the stock model to allow each user to have multiple tokens.
It also supports setting an expiration time and toggling write ability. It also supports setting an expiration time and toggling write ability.
""" """
user = models.ForeignKey(User, related_name='tokens', on_delete=models.CASCADE) user = models.ForeignKey(
created = models.DateTimeField(auto_now_add=True) to=User,
expires = models.DateTimeField(blank=True, null=True) on_delete=models.CASCADE,
key = models.CharField(max_length=40, unique=True, validators=[MinLengthValidator(40)]) related_name='tokens'
write_enabled = models.BooleanField(default=True, help_text="Permit create/update/delete operations using this key") )
description = models.CharField(max_length=100, blank=True) created = models.DateTimeField(
auto_now_add=True
)
expires = models.DateTimeField(
blank=True,
null=True
)
key = models.CharField(
max_length=40,
unique=True,
validators=[MinLengthValidator(40)]
)
write_enabled = models.BooleanField(
default=True,
help_text='Permit create/update/delete operations using this key'
)
description = models.CharField(
max_length=100,
blank=True
)
class Meta: class Meta:
default_permissions = [] default_permissions = []

View File

@ -8,7 +8,7 @@ from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from dcim.models import Device from dcim.models import Device
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES
@ -119,7 +119,7 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel):
blank=True blank=True
) )
custom_field_values = GenericRelation( custom_field_values = GenericRelation(
to=CustomFieldValue, to='extras.CustomFieldValue',
content_type_field='obj_type', content_type_field='obj_type',
object_id_field='obj_id' object_id_field='obj_id'
) )
@ -167,7 +167,7 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
A virtual machine which runs inside a Cluster. A virtual machine which runs inside a Cluster.
""" """
cluster = models.ForeignKey( cluster = models.ForeignKey(
to=Cluster, to='virtualization.Cluster',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='virtual_machines' related_name='virtual_machines'
) )
@ -196,9 +196,9 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
) )
role = models.ForeignKey( role = models.ForeignKey(
to='dcim.DeviceRole', to='dcim.DeviceRole',
limit_choices_to={'vm_role': True},
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='virtual_machines', related_name='virtual_machines',
limit_choices_to={'vm_role': True},
blank=True, blank=True,
null=True null=True
) )
@ -237,7 +237,7 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
blank=True blank=True
) )
custom_field_values = GenericRelation( custom_field_values = GenericRelation(
to=CustomFieldValue, to='extras.CustomFieldValue',
content_type_field='obj_type', content_type_field='obj_type',
object_id_field='obj_id' object_id_field='obj_id'
) )