mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Standardize model types based on function
This commit is contained in:
parent
0a6ebdee48
commit
bec7ea7072
37
netbox/circuits/migrations/0025_standardize_models.py
Normal file
37
netbox/circuits/migrations/0025_standardize_models.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('circuits', '0024_standardize_name_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='circuittype',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='circuit',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='circuittermination',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='circuittype',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='provider',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -4,8 +4,9 @@ from taggit.managers import TaggableManager
|
|||||||
|
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from dcim.models import CableTermination, PathEndpoint
|
from dcim.models import CableTermination, PathEndpoint
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import BigIDModel, OrganizationalModel, PrimaryModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -21,7 +22,7 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
class Provider(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -93,7 +94,8 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CircuitType(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class CircuitType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
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".
|
||||||
@ -133,7 +135,7 @@ class CircuitType(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
class Circuit(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||||
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||||
@ -233,7 +235,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return self._get_termination('Z')
|
return self._get_termination('Z')
|
||||||
|
|
||||||
|
|
||||||
class CircuitTermination(PathEndpoint, CableTermination):
|
class CircuitTermination(BigIDModel, PathEndpoint, CableTermination):
|
||||||
circuit = models.ForeignKey(
|
circuit = models.ForeignKey(
|
||||||
to='circuits.Circuit',
|
to='circuits.Circuit',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
462
netbox/dcim/migrations/0123_standardize_models.py
Normal file
462
netbox/dcim/migrations/0123_standardize_models.py
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0122_standardize_name_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleporttemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverporttemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebay',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebay',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebay',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicerole',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontport',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontport',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontport',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontporttemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='frontporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='manufacturer',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platform',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlettemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlettemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlettemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerporttemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rackgroup',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rackrole',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearport',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearport',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearport',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearporttemplate',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rearporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='region',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cable',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cablepath',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='consoleporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='consoleserverporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='device',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='devicebay',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='devicerole',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='devicetype',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='frontport',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='frontporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='interface',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='manufacturer',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='powerfeed',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='poweroutlettemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='powerpanel',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='powerporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rack',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rackgroup',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rackreservation',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rackrole',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rearport',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rearporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='region',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='site',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='virtualchassis',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -12,8 +12,9 @@ from dcim.choices import *
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import PathField
|
from dcim.fields import PathField
|
||||||
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
|
from extras.models import TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import BigIDModel, PrimaryModel
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import to_meters
|
from utilities.utils import to_meters
|
||||||
@ -32,7 +33,7 @@ __all__ = (
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Cable(ChangeLoggedModel, CustomFieldModel):
|
class Cable(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A physical connection between two endpoints.
|
A physical connection between two endpoints.
|
||||||
"""
|
"""
|
||||||
@ -305,7 +306,7 @@ class Cable(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
|
return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
|
||||||
|
|
||||||
|
|
||||||
class CablePath(models.Model):
|
class CablePath(BigIDModel):
|
||||||
"""
|
"""
|
||||||
A CablePath instance represents the physical path from an origin to a destination, including all intermediate
|
A CablePath instance represents the physical path from an origin to a destination, including all intermediate
|
||||||
elements in the path. Every instance must specify an `origin`, whereas `destination` may be null (for paths which do
|
elements in the path. Every instance must specify an `origin`, whereas `destination` may be null (for paths which do
|
||||||
|
@ -5,6 +5,8 @@ from django.db import models
|
|||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.models import ObjectChange
|
from extras.models import ObjectChange
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
@ -26,7 +28,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateModel(models.Model):
|
class ComponentTemplateModel(PrimaryModel):
|
||||||
device_type = models.ForeignKey(
|
device_type = models.ForeignKey(
|
||||||
to='dcim.DeviceType',
|
to='dcim.DeviceType',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -82,6 +84,7 @@ class ComponentTemplateModel(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class ConsolePortTemplate(ComponentTemplateModel):
|
class ConsolePortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a ConsolePort to be created for a new Device.
|
A template for a ConsolePort to be created for a new Device.
|
||||||
@ -105,6 +108,7 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a ConsoleServerPort to be created for a new Device.
|
A template for a ConsoleServerPort to be created for a new Device.
|
||||||
@ -128,6 +132,7 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class PowerPortTemplate(ComponentTemplateModel):
|
class PowerPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a PowerPort to be created for a new Device.
|
A template for a PowerPort to be created for a new Device.
|
||||||
@ -174,6 +179,7 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class PowerOutletTemplate(ComponentTemplateModel):
|
class PowerOutletTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a PowerOutlet to be created for a new Device.
|
A template for a PowerOutlet to be created for a new Device.
|
||||||
@ -225,6 +231,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class InterfaceTemplate(ComponentTemplateModel):
|
class InterfaceTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a physical data interface on a new Device.
|
A template for a physical data interface on a new Device.
|
||||||
@ -259,6 +266,7 @@ class InterfaceTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class FrontPortTemplate(ComponentTemplateModel):
|
class FrontPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
Template for a pass-through port on the front of a new Device.
|
Template for a pass-through port on the front of a new Device.
|
||||||
@ -319,6 +327,7 @@ class FrontPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class RearPortTemplate(ComponentTemplateModel):
|
class RearPortTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
Template for a pass-through port on the rear of a new Device.
|
Template for a pass-through port on the rear of a new Device.
|
||||||
@ -349,6 +358,7 @@ class RearPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class DeviceBayTemplate(ComponentTemplateModel):
|
class DeviceBayTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
@ -13,6 +13,7 @@ from dcim.constants import *
|
|||||||
from dcim.fields import MACAddressField
|
from dcim.fields import MACAddressField
|
||||||
from extras.models import ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
@ -37,7 +38,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentModel(models.Model):
|
class ComponentModel(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
An abstract model inherited by any model which has a parent Device.
|
An abstract model inherited by any model which has a parent Device.
|
||||||
"""
|
"""
|
||||||
@ -198,7 +199,7 @@ class PathEndpoint(models.Model):
|
|||||||
# Console ports
|
# Console ports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
|
class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||||
@ -234,7 +235,7 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
|
|||||||
# Console server ports
|
# Console server ports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
|
class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -270,7 +271,7 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
|
|||||||
# Power ports
|
# Power ports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class PowerPort(CableTermination, PathEndpoint, ComponentModel):
|
class PowerPort(CableTermination, PathEndpoint, ComponentModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -379,7 +380,7 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
|
|||||||
# Power outlets
|
# Power outlets
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class PowerOutlet(CableTermination, PathEndpoint, ComponentModel):
|
class PowerOutlet(CableTermination, PathEndpoint, ComponentModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -479,7 +480,7 @@ class BaseInterface(models.Model):
|
|||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
||||||
"""
|
"""
|
||||||
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
||||||
@ -624,7 +625,7 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
|||||||
# Pass-through ports
|
# Pass-through ports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class FrontPort(CableTermination, ComponentModel):
|
class FrontPort(CableTermination, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the front of a Device.
|
A pass-through port on the front of a Device.
|
||||||
@ -687,7 +688,7 @@ class FrontPort(CableTermination, ComponentModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class RearPort(CableTermination, ComponentModel):
|
class RearPort(CableTermination, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the rear of a Device.
|
A pass-through port on the rear of a Device.
|
||||||
@ -740,7 +741,7 @@ class RearPort(CableTermination, ComponentModel):
|
|||||||
# Device bays
|
# Device bays
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class DeviceBay(ComponentModel):
|
class DeviceBay(ComponentModel):
|
||||||
"""
|
"""
|
||||||
An empty space within a Device which can house a child device
|
An empty space within a Device which can house a child device
|
||||||
@ -800,7 +801,7 @@ class DeviceBay(ComponentModel):
|
|||||||
# Inventory items
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class InventoryItem(MPTTModel, ComponentModel):
|
class InventoryItem(MPTTModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
@ -13,9 +13,10 @@ from taggit.managers import TaggableManager
|
|||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, TaggedItem
|
from extras.models import ConfigContextModel, TaggedItem
|
||||||
from extras.querysets import ConfigContextModelQuerySet
|
from extras.querysets import ConfigContextModelQuerySet
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
@ -36,8 +37,8 @@ __all__ = (
|
|||||||
# Device Types
|
# Device Types
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class Manufacturer(ChangeLoggedModel):
|
class Manufacturer(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
@ -76,7 +77,7 @@ class Manufacturer(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
class DeviceType(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
|
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
|
||||||
well as high-level functional role(s).
|
well as high-level functional role(s).
|
||||||
@ -338,7 +339,8 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
|||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceRole(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class DeviceRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
||||||
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
|
||||||
@ -385,7 +387,8 @@ class DeviceRole(ChangeLoggedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Platform(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class Platform(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
||||||
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
|
||||||
@ -449,7 +452,7 @@ class Platform(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
class Device(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
||||||
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
||||||
@ -882,7 +885,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class VirtualChassis(ChangeLoggedModel, CustomFieldModel):
|
class VirtualChassis(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A collection of Devices which operate with a shared control plane (e.g. a switch stack).
|
A collection of Devices which operate with a shared control plane (e.g. a switch stack).
|
||||||
"""
|
"""
|
||||||
|
@ -6,8 +6,9 @@ from taggit.managers import TaggableManager
|
|||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
|
from extras.models import TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import ExclusionValidator
|
from utilities.validators import ExclusionValidator
|
||||||
from .device_components import CableTermination, PathEndpoint
|
from .device_components import CableTermination, PathEndpoint
|
||||||
@ -23,7 +24,7 @@ __all__ = (
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class PowerPanel(ChangeLoggedModel, CustomFieldModel):
|
class PowerPanel(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A distribution point for electrical power; e.g. a data center RPP.
|
A distribution point for electrical power; e.g. a data center RPP.
|
||||||
"""
|
"""
|
||||||
@ -74,7 +75,7 @@ class PowerPanel(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class PowerFeed(ChangeLoggedModel, PathEndpoint, CableTermination, CustomFieldModel):
|
class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
|
||||||
"""
|
"""
|
||||||
An electrical circuit delivered from a PowerPanel.
|
An electrical circuit delivered from a PowerPanel.
|
||||||
"""
|
"""
|
||||||
|
@ -10,14 +10,15 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count, Sum
|
from django.db.models import Count, Sum
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.elevations import RackElevationSVG
|
from dcim.elevations import RackElevationSVG
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
@ -39,8 +40,8 @@ __all__ = (
|
|||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class RackGroup(MPTTModel, ChangeLoggedModel):
|
class RackGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
|
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
|
||||||
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
|
||||||
@ -70,8 +71,6 @@ class RackGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = TreeManager()
|
|
||||||
|
|
||||||
csv_headers = ['site', 'parent', 'name', 'slug', 'description']
|
csv_headers = ['site', 'parent', 'name', 'slug', 'description']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -81,12 +80,6 @@ class RackGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
['site', 'slug'],
|
['site', 'slug'],
|
||||||
]
|
]
|
||||||
|
|
||||||
class MPTTMeta:
|
|
||||||
order_insertion_by = ['name']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
||||||
|
|
||||||
@ -99,15 +92,6 @@ class RackGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
self.description,
|
self.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
|
||||||
# Remove MPTT-internal fields
|
|
||||||
return ObjectChange(
|
|
||||||
changed_object=self,
|
|
||||||
object_repr=str(self),
|
|
||||||
action=action,
|
|
||||||
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
@ -116,7 +100,8 @@ class RackGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})")
|
raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})")
|
||||||
|
|
||||||
|
|
||||||
class RackRole(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class RackRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Racks can be organized by functional role, similar to Devices.
|
Racks can be organized by functional role, similar to Devices.
|
||||||
"""
|
"""
|
||||||
@ -159,7 +144,7 @@ class RackRole(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Rack(ChangeLoggedModel, CustomFieldModel):
|
class Rack(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -550,7 +535,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class RackReservation(ChangeLoggedModel, CustomFieldModel):
|
class RackReservation(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
One or more reserved units within a Rack.
|
One or more reserved units within a Rack.
|
||||||
"""
|
"""
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
from timezone_field import TimeZoneField
|
from timezone_field import TimeZoneField
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import NestedGroupModel, PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
@ -25,8 +26,8 @@ __all__ = (
|
|||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class Region(MPTTModel, ChangeLoggedModel):
|
class Region(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
Sites can be grouped within geographic Regions.
|
Sites can be grouped within geographic Regions.
|
||||||
"""
|
"""
|
||||||
@ -51,16 +52,8 @@ class Region(MPTTModel, ChangeLoggedModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = TreeManager()
|
|
||||||
|
|
||||||
csv_headers = ['name', 'slug', 'parent', 'description']
|
csv_headers = ['name', 'slug', 'parent', 'description']
|
||||||
|
|
||||||
class MPTTMeta:
|
|
||||||
order_insertion_by = ['name']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?region={}".format(reverse('dcim:site_list'), self.slug)
|
return "{}?region={}".format(reverse('dcim:site_list'), self.slug)
|
||||||
|
|
||||||
@ -78,22 +71,13 @@ class Region(MPTTModel, ChangeLoggedModel):
|
|||||||
Q(region__in=self.get_descendants())
|
Q(region__in=self.get_descendants())
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
|
||||||
# Remove MPTT-internal fields
|
|
||||||
return ObjectChange(
|
|
||||||
changed_object=self,
|
|
||||||
object_repr=str(self),
|
|
||||||
action=action,
|
|
||||||
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Sites
|
# Sites
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Site(ChangeLoggedModel, CustomFieldModel):
|
class Site(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
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).
|
||||||
|
67
netbox/extras/migrations/0054_standardize_models.py
Normal file
67
netbox/extras/migrations/0054_standardize_models.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0053_rename_webhook_obj_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='configcontext',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='configcontext',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customfield',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customlink',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='imageattachment',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobresult',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='objectchange',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='taggeditem',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webhook',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,4 @@
|
|||||||
from .change_logging import ChangeLoggedModel, ObjectChange
|
from .change_logging import ObjectChange
|
||||||
from .customfields import CustomField, CustomFieldModel
|
from .customfields import CustomField, CustomFieldModel
|
||||||
from .models import (
|
from .models import (
|
||||||
ConfigContext, ConfigContextModel, CustomLink, ExportTemplate, ImageAttachment, JobResult, Report, Script,
|
ConfigContext, ConfigContextModel, CustomLink, ExportTemplate, ImageAttachment, JobResult, Report, Script,
|
||||||
@ -7,7 +7,6 @@ from .models import (
|
|||||||
from .tags import Tag, TaggedItem
|
from .tags import Tag, TaggedItem
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ChangeLoggedModel',
|
|
||||||
'ConfigContext',
|
'ConfigContext',
|
||||||
'ConfigContextModel',
|
'ConfigContextModel',
|
||||||
'CustomField',
|
'CustomField',
|
||||||
|
@ -4,48 +4,12 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from utilities.querysets import RestrictedQuerySet
|
|
||||||
from utilities.utils import serialize_object
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
|
from netbox.models import BigIDModel
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
|
|
||||||
#
|
class ObjectChange(BigIDModel):
|
||||||
# Change logging
|
|
||||||
#
|
|
||||||
|
|
||||||
class ChangeLoggedModel(models.Model):
|
|
||||||
"""
|
|
||||||
An abstract model which adds fields to store the creation and last-updated times for an object. Both fields can be
|
|
||||||
null to facilitate adding these fields to existing instances via a database migration.
|
|
||||||
"""
|
|
||||||
created = models.DateField(
|
|
||||||
auto_now_add=True,
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
last_updated = models.DateTimeField(
|
|
||||||
auto_now=True,
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
|
||||||
"""
|
|
||||||
Return a new ObjectChange representing a change made to this object. This will typically be called automatically
|
|
||||||
by ChangeLoggingMiddleware.
|
|
||||||
"""
|
|
||||||
return ObjectChange(
|
|
||||||
changed_object=self,
|
|
||||||
object_repr=str(self),
|
|
||||||
action=action,
|
|
||||||
object_data=serialize_object(self)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectChange(models.Model):
|
|
||||||
"""
|
"""
|
||||||
Record a change to an object and the user account associated with that change. A change record may optionally
|
Record a change to an object and the user account associated with that change. A change record may optionally
|
||||||
indicate an object related to the one being changed. For example, a change to an interface may also indicate the
|
indicate an object related to the one being changed. For example, a change to an interface may also indicate the
|
||||||
|
@ -12,12 +12,13 @@ from django.utils.safestring import mark_safe
|
|||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
|
from netbox.models import BigIDModel
|
||||||
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import validate_regex
|
from utilities.validators import validate_regex
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModel(models.Model):
|
class CustomFieldModel(BigIDModel):
|
||||||
"""
|
"""
|
||||||
Abstract class for any model which may have custom fields associated with it.
|
Abstract class for any model which may have custom fields associated with it.
|
||||||
"""
|
"""
|
||||||
@ -77,7 +78,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|||||||
return self.get_queryset().filter(content_types=content_type)
|
return self.get_queryset().filter(content_types=content_type)
|
||||||
|
|
||||||
|
|
||||||
class CustomField(models.Model):
|
class CustomField(BigIDModel):
|
||||||
content_types = models.ManyToManyField(
|
content_types = models.ManyToManyField(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
related_name='custom_fields',
|
related_name='custom_fields',
|
||||||
|
@ -14,9 +14,9 @@ from rest_framework.utils.encoders import JSONEncoder
|
|||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.models import ChangeLoggedModel
|
|
||||||
from extras.querysets import ConfigContextQuerySet
|
from extras.querysets import ConfigContextQuerySet
|
||||||
from extras.utils import extras_features, FeatureQuery, image_upload
|
from extras.utils import extras_features, FeatureQuery, image_upload
|
||||||
|
from netbox.models import BigIDModel, PrimaryModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import deepmerge, render_jinja2
|
from utilities.utils import deepmerge, render_jinja2
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ from utilities.utils import deepmerge, render_jinja2
|
|||||||
# Webhooks
|
# Webhooks
|
||||||
#
|
#
|
||||||
|
|
||||||
class Webhook(models.Model):
|
class Webhook(BigIDModel):
|
||||||
"""
|
"""
|
||||||
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
||||||
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
||||||
@ -158,7 +158,7 @@ class Webhook(models.Model):
|
|||||||
# Custom links
|
# Custom links
|
||||||
#
|
#
|
||||||
|
|
||||||
class CustomLink(models.Model):
|
class CustomLink(BigIDModel):
|
||||||
"""
|
"""
|
||||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||||
code to be rendered with an object as context.
|
code to be rendered with an object as context.
|
||||||
@ -210,7 +210,7 @@ class CustomLink(models.Model):
|
|||||||
# Export templates
|
# Export templates
|
||||||
#
|
#
|
||||||
|
|
||||||
class ExportTemplate(models.Model):
|
class ExportTemplate(BigIDModel):
|
||||||
content_type = models.ForeignKey(
|
content_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -285,7 +285,7 @@ class ExportTemplate(models.Model):
|
|||||||
# Image attachments
|
# Image attachments
|
||||||
#
|
#
|
||||||
|
|
||||||
class ImageAttachment(models.Model):
|
class ImageAttachment(BigIDModel):
|
||||||
"""
|
"""
|
||||||
An uploaded image which is associated with an object.
|
An uploaded image which is associated with an object.
|
||||||
"""
|
"""
|
||||||
@ -361,7 +361,7 @@ class ImageAttachment(models.Model):
|
|||||||
# Config contexts
|
# Config contexts
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConfigContext(ChangeLoggedModel):
|
class ConfigContext(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
||||||
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
||||||
@ -526,7 +526,7 @@ class Report(models.Model):
|
|||||||
# Job results
|
# Job results
|
||||||
#
|
#
|
||||||
|
|
||||||
class JobResult(models.Model):
|
class JobResult(BigIDModel):
|
||||||
"""
|
"""
|
||||||
This model stores the results from running a user-defined report.
|
This model stores the results from running a user-defined report.
|
||||||
"""
|
"""
|
||||||
|
@ -2,7 +2,7 @@ from django.db import models
|
|||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from taggit.models import TagBase, GenericTaggedItemBase
|
from taggit.models import TagBase, GenericTaggedItemBase
|
||||||
|
|
||||||
from extras.models import ChangeLoggedModel
|
from netbox.models import BigIDModel, CoreModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
@ -12,7 +12,7 @@ from utilities.querysets import RestrictedQuerySet
|
|||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
|
|
||||||
class Tag(TagBase, ChangeLoggedModel):
|
class Tag(TagBase, CoreModel):
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
default=ColorChoices.COLOR_GREY
|
default=ColorChoices.COLOR_GREY
|
||||||
)
|
)
|
||||||
@ -44,7 +44,7 @@ class Tag(TagBase, ChangeLoggedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaggedItem(GenericTaggedItemBase):
|
class TaggedItem(BigIDModel, GenericTaggedItemBase):
|
||||||
tag = models.ForeignKey(
|
tag = models.ForeignKey(
|
||||||
to=Tag,
|
to=Tag,
|
||||||
related_name="%(app_label)s_%(class)s_items",
|
related_name="%(app_label)s_%(class)s_items",
|
||||||
|
77
netbox/ipam/migrations/0044_standardize_models.py
Normal file
77
netbox/ipam/migrations/0044_standardize_models.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0043_add_tenancy_to_aggregates'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rir',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='role',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlangroup',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='aggregate',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ipaddress',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='prefix',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rir',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='role',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='routetarget',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlangroup',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vrf',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
17
netbox/ipam/models/__init__.py
Normal file
17
netbox/ipam/models/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from .ip import *
|
||||||
|
from .services import *
|
||||||
|
from .vlans import *
|
||||||
|
from .vrfs import *
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'Aggregate',
|
||||||
|
'IPAddress',
|
||||||
|
'Prefix',
|
||||||
|
'RIR',
|
||||||
|
'Role',
|
||||||
|
'RouteTarget',
|
||||||
|
'Service',
|
||||||
|
'VLAN',
|
||||||
|
'VLANGroup',
|
||||||
|
'VRF',
|
||||||
|
)
|
@ -2,26 +2,25 @@ import netaddr
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import ArrayField
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
|
from ipam.choices import *
|
||||||
|
from ipam.constants import *
|
||||||
|
from ipam.fields import IPNetworkField, IPAddressField
|
||||||
|
from ipam.managers import IPAddressManager
|
||||||
|
from ipam.querysets import PrefixQuerySet
|
||||||
|
from ipam.validators import DNSValidator
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import array_to_string, serialize_object
|
from utilities.utils import serialize_object
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine
|
||||||
from .choices import *
|
|
||||||
from .constants import *
|
|
||||||
from .fields import IPNetworkField, IPAddressField
|
|
||||||
from .managers import IPAddressManager
|
|
||||||
from .querysets import PrefixQuerySet
|
|
||||||
from .validators import DNSValidator
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -30,139 +29,11 @@ __all__ = (
|
|||||||
'Prefix',
|
'Prefix',
|
||||||
'RIR',
|
'RIR',
|
||||||
'Role',
|
'Role',
|
||||||
'RouteTarget',
|
|
||||||
'Service',
|
|
||||||
'VLAN',
|
|
||||||
'VLANGroup',
|
|
||||||
'VRF',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class VRF(ChangeLoggedModel, CustomFieldModel):
|
class RIR(OrganizationalModel):
|
||||||
"""
|
|
||||||
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
|
||||||
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.)
|
|
||||||
"""
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
rd = models.CharField(
|
|
||||||
max_length=VRF_RD_MAX_LENGTH,
|
|
||||||
unique=True,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name='Route distinguisher',
|
|
||||||
help_text='Unique route distinguisher (as defined in RFC 4364)'
|
|
||||||
)
|
|
||||||
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=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
import_targets = models.ManyToManyField(
|
|
||||||
to='ipam.RouteTarget',
|
|
||||||
related_name='importing_vrfs',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
export_targets = models.ManyToManyField(
|
|
||||||
to='ipam.RouteTarget',
|
|
||||||
related_name='exporting_vrfs',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tags = TaggableManager(through=TaggedItem)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
|
||||||
clone_fields = [
|
|
||||||
'tenant', 'enforce_unique', 'description',
|
|
||||||
]
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique
|
|
||||||
verbose_name = 'VRF'
|
|
||||||
verbose_name_plural = 'VRFs'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.display_name or super().__str__()
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ipam:vrf', args=[self.pk])
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.name,
|
|
||||||
self.rd,
|
|
||||||
self.tenant.name if self.tenant else None,
|
|
||||||
self.enforce_unique,
|
|
||||||
self.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
if self.rd:
|
|
||||||
return f'{self.name} ({self.rd})'
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
|
||||||
class RouteTarget(ChangeLoggedModel, CustomFieldModel):
|
|
||||||
"""
|
|
||||||
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
|
||||||
"""
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
|
|
||||||
unique=True,
|
|
||||||
help_text='Route target value (formatted in accordance with RFC 4360)'
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tenant = models.ForeignKey(
|
|
||||||
to='tenancy.Tenant',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='route_targets',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
tags = TaggableManager(through=TaggedItem)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
csv_headers = ['name', 'description', 'tenant']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['name']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ipam:routetarget', args=[self.pk])
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.name,
|
|
||||||
self.description,
|
|
||||||
self.tenant.name if self.tenant else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RIR(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -210,7 +81,7 @@ class RIR(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Aggregate(ChangeLoggedModel, CustomFieldModel):
|
class Aggregate(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -317,7 +188,8 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
||||||
|
|
||||||
|
|
||||||
class Role(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class Role(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
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."
|
||||||
@ -358,7 +230,7 @@ class Role(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Prefix(ChangeLoggedModel, CustomFieldModel):
|
class Prefix(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
|
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
|
||||||
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
|
||||||
@ -616,7 +488,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
class IPAddress(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
||||||
configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
|
configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
|
||||||
@ -831,274 +703,3 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
def get_role_class(self):
|
def get_role_class(self):
|
||||||
return IPAddressRoleChoices.CSS_CLASSES.get(self.role)
|
return IPAddressRoleChoices.CSS_CLASSES.get(self.role)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroup(ChangeLoggedModel):
|
|
||||||
"""
|
|
||||||
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
|
||||||
"""
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
slug = models.SlugField(
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
site = models.ForeignKey(
|
|
||||||
to='dcim.Site',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='vlan_groups',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
csv_headers = ['name', 'slug', 'site', 'description']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('site', 'name', 'pk') # (site, name) may be non-unique
|
|
||||||
unique_together = [
|
|
||||||
['site', 'name'],
|
|
||||||
['site', 'slug'],
|
|
||||||
]
|
|
||||||
verbose_name = 'VLAN group'
|
|
||||||
verbose_name_plural = 'VLAN groups'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ipam:vlangroup_vlans', args=[self.pk])
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.name,
|
|
||||||
self.slug,
|
|
||||||
self.site.name if self.site else None,
|
|
||||||
self.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_next_available_vid(self):
|
|
||||||
"""
|
|
||||||
Return the first available VLAN ID (1-4094) in the group.
|
|
||||||
"""
|
|
||||||
vlan_ids = VLAN.objects.filter(group=self).values_list('vid', flat=True)
|
|
||||||
for i in range(1, 4095):
|
|
||||||
if i not in vlan_ids:
|
|
||||||
return i
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
|
||||||
class VLAN(ChangeLoggedModel, CustomFieldModel):
|
|
||||||
"""
|
|
||||||
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
|
|
||||||
to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
|
|
||||||
within which all VLAN IDs and names but be unique.
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
site = models.ForeignKey(
|
|
||||||
to='dcim.Site',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='vlans',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
group = models.ForeignKey(
|
|
||||||
to='ipam.VLANGroup',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='vlans',
|
|
||||||
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.CharField(
|
|
||||||
max_length=50,
|
|
||||||
choices=VLANStatusChoices,
|
|
||||||
default=VLANStatusChoices.STATUS_ACTIVE
|
|
||||||
)
|
|
||||||
role = models.ForeignKey(
|
|
||||||
to='ipam.Role',
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='vlans',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tags = TaggableManager(through=TaggedItem)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
csv_headers = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
|
||||||
clone_fields = [
|
|
||||||
'site', 'group', 'tenant', 'status', 'role', 'description',
|
|
||||||
]
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('site', 'group', 'vid', 'pk') # (site, group, vid) may be non-unique
|
|
||||||
unique_together = [
|
|
||||||
['group', 'vid'],
|
|
||||||
['group', 'name'],
|
|
||||||
]
|
|
||||||
verbose_name = 'VLAN'
|
|
||||||
verbose_name_plural = 'VLANs'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.display_name or super().__str__()
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ipam:vlan', args=[self.pk])
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
# Validate VLAN group
|
|
||||||
if self.group and self.group.site != self.site:
|
|
||||||
raise ValidationError({
|
|
||||||
'group': "VLAN group must belong to the assigned site ({}).".format(self.site)
|
|
||||||
})
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.site.name if self.site else None,
|
|
||||||
self.group.name if self.group else None,
|
|
||||||
self.vid,
|
|
||||||
self.name,
|
|
||||||
self.tenant.name if self.tenant else None,
|
|
||||||
self.get_status_display(),
|
|
||||||
self.role.name if self.role else None,
|
|
||||||
self.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
return f'{self.name} ({self.vid})'
|
|
||||||
|
|
||||||
def get_status_class(self):
|
|
||||||
return VLANStatusChoices.CSS_CLASSES.get(self.status)
|
|
||||||
|
|
||||||
def get_interfaces(self):
|
|
||||||
# Return all device interfaces assigned to this VLAN
|
|
||||||
return Interface.objects.filter(
|
|
||||||
Q(untagged_vlan_id=self.pk) |
|
|
||||||
Q(tagged_vlans=self.pk)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def get_vminterfaces(self):
|
|
||||||
# Return all VM interfaces assigned to this VLAN
|
|
||||||
return VMInterface.objects.filter(
|
|
||||||
Q(untagged_vlan_id=self.pk) |
|
|
||||||
Q(tagged_vlans=self.pk)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
|
||||||
class Service(ChangeLoggedModel, CustomFieldModel):
|
|
||||||
"""
|
|
||||||
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
|
|
||||||
optionally be tied to one or more specific IPAddresses belonging to its parent.
|
|
||||||
"""
|
|
||||||
device = models.ForeignKey(
|
|
||||||
to='dcim.Device',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='services',
|
|
||||||
verbose_name='device',
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
virtual_machine = models.ForeignKey(
|
|
||||||
to='virtualization.VirtualMachine',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='services',
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
protocol = models.CharField(
|
|
||||||
max_length=50,
|
|
||||||
choices=ServiceProtocolChoices
|
|
||||||
)
|
|
||||||
ports = ArrayField(
|
|
||||||
base_field=models.PositiveIntegerField(
|
|
||||||
validators=[
|
|
||||||
MinValueValidator(SERVICE_PORT_MIN),
|
|
||||||
MaxValueValidator(SERVICE_PORT_MAX)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
verbose_name='Port numbers'
|
|
||||||
)
|
|
||||||
ipaddresses = models.ManyToManyField(
|
|
||||||
to='ipam.IPAddress',
|
|
||||||
related_name='services',
|
|
||||||
blank=True,
|
|
||||||
verbose_name='IP addresses'
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
tags = TaggableManager(through=TaggedItem)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
csv_headers = ['device', 'virtual_machine', 'name', 'protocol', 'ports', 'description']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.name} ({self.get_protocol_display()}/{self.port_list})'
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ipam:service', args=[self.pk])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def parent(self):
|
|
||||||
return self.device or self.virtual_machine
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
# A Service must belong to a Device *or* to a VirtualMachine
|
|
||||||
if self.device and self.virtual_machine:
|
|
||||||
raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
|
|
||||||
if not self.device and not self.virtual_machine:
|
|
||||||
raise ValidationError("A service must be associated with either a device or a virtual machine.")
|
|
||||||
|
|
||||||
def to_csv(self):
|
|
||||||
return (
|
|
||||||
self.device.name if self.device else None,
|
|
||||||
self.virtual_machine.name if self.virtual_machine else None,
|
|
||||||
self.name,
|
|
||||||
self.get_protocol_display(),
|
|
||||||
self.ports,
|
|
||||||
self.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def port_list(self):
|
|
||||||
return array_to_string(self.ports)
|
|
109
netbox/ipam/models/services.py
Normal file
109
netbox/ipam/models/services.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
from extras.models import TaggedItem
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from ipam.choices import *
|
||||||
|
from ipam.constants import *
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
from utilities.utils import array_to_string
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'Service',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
|
class Service(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
|
||||||
|
optionally be tied to one or more specific IPAddresses belonging to its parent.
|
||||||
|
"""
|
||||||
|
device = models.ForeignKey(
|
||||||
|
to='dcim.Device',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='services',
|
||||||
|
verbose_name='device',
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
virtual_machine = models.ForeignKey(
|
||||||
|
to='virtualization.VirtualMachine',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='services',
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
protocol = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=ServiceProtocolChoices
|
||||||
|
)
|
||||||
|
ports = ArrayField(
|
||||||
|
base_field=models.PositiveIntegerField(
|
||||||
|
validators=[
|
||||||
|
MinValueValidator(SERVICE_PORT_MIN),
|
||||||
|
MaxValueValidator(SERVICE_PORT_MAX)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
verbose_name='Port numbers'
|
||||||
|
)
|
||||||
|
ipaddresses = models.ManyToManyField(
|
||||||
|
to='ipam.IPAddress',
|
||||||
|
related_name='services',
|
||||||
|
blank=True,
|
||||||
|
verbose_name='IP addresses'
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tags = TaggableManager(through=TaggedItem)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['device', 'virtual_machine', 'name', 'protocol', 'ports', 'description']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} ({self.get_protocol_display()}/{self.port_list})'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:service', args=[self.pk])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
return self.device or self.virtual_machine
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# A Service must belong to a Device *or* to a VirtualMachine
|
||||||
|
if self.device and self.virtual_machine:
|
||||||
|
raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
|
||||||
|
if not self.device and not self.virtual_machine:
|
||||||
|
raise ValidationError("A service must be associated with either a device or a virtual machine.")
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.device.name if self.device else None,
|
||||||
|
self.virtual_machine.name if self.virtual_machine else None,
|
||||||
|
self.name,
|
||||||
|
self.get_protocol_display(),
|
||||||
|
self.ports,
|
||||||
|
self.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_list(self):
|
||||||
|
return array_to_string(self.ports)
|
202
netbox/ipam/models/vlans.py
Normal file
202
netbox/ipam/models/vlans.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
from dcim.models import Interface
|
||||||
|
from extras.models import TaggedItem
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from ipam.choices import *
|
||||||
|
from ipam.constants import *
|
||||||
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
from virtualization.models import VMInterface
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'VLAN',
|
||||||
|
'VLANGroup',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class VLANGroup(OrganizationalModel):
|
||||||
|
"""
|
||||||
|
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
site = models.ForeignKey(
|
||||||
|
to='dcim.Site',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='vlan_groups',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['name', 'slug', 'site', 'description']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('site', 'name', 'pk') # (site, name) may be non-unique
|
||||||
|
unique_together = [
|
||||||
|
['site', 'name'],
|
||||||
|
['site', 'slug'],
|
||||||
|
]
|
||||||
|
verbose_name = 'VLAN group'
|
||||||
|
verbose_name_plural = 'VLAN groups'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:vlangroup_vlans', args=[self.pk])
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.name,
|
||||||
|
self.slug,
|
||||||
|
self.site.name if self.site else None,
|
||||||
|
self.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_next_available_vid(self):
|
||||||
|
"""
|
||||||
|
Return the first available VLAN ID (1-4094) in the group.
|
||||||
|
"""
|
||||||
|
vlan_ids = VLAN.objects.filter(group=self).values_list('vid', flat=True)
|
||||||
|
for i in range(1, 4095):
|
||||||
|
if i not in vlan_ids:
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
|
class VLAN(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
|
||||||
|
to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
|
||||||
|
within which all VLAN IDs and names but be unique.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
site = models.ForeignKey(
|
||||||
|
to='dcim.Site',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='vlans',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
group = models.ForeignKey(
|
||||||
|
to='ipam.VLANGroup',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='vlans',
|
||||||
|
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.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=VLANStatusChoices,
|
||||||
|
default=VLANStatusChoices.STATUS_ACTIVE
|
||||||
|
)
|
||||||
|
role = models.ForeignKey(
|
||||||
|
to='ipam.Role',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='vlans',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tags = TaggableManager(through=TaggedItem)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
||||||
|
clone_fields = [
|
||||||
|
'site', 'group', 'tenant', 'status', 'role', 'description',
|
||||||
|
]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('site', 'group', 'vid', 'pk') # (site, group, vid) may be non-unique
|
||||||
|
unique_together = [
|
||||||
|
['group', 'vid'],
|
||||||
|
['group', 'name'],
|
||||||
|
]
|
||||||
|
verbose_name = 'VLAN'
|
||||||
|
verbose_name_plural = 'VLANs'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.display_name or super().__str__()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:vlan', args=[self.pk])
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# Validate VLAN group
|
||||||
|
if self.group and self.group.site != self.site:
|
||||||
|
raise ValidationError({
|
||||||
|
'group': "VLAN group must belong to the assigned site ({}).".format(self.site)
|
||||||
|
})
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.site.name if self.site else None,
|
||||||
|
self.group.name if self.group else None,
|
||||||
|
self.vid,
|
||||||
|
self.name,
|
||||||
|
self.tenant.name if self.tenant else None,
|
||||||
|
self.get_status_display(),
|
||||||
|
self.role.name if self.role else None,
|
||||||
|
self.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
return f'{self.name} ({self.vid})'
|
||||||
|
|
||||||
|
def get_status_class(self):
|
||||||
|
return VLANStatusChoices.CSS_CLASSES.get(self.status)
|
||||||
|
|
||||||
|
def get_interfaces(self):
|
||||||
|
# Return all device interfaces assigned to this VLAN
|
||||||
|
return Interface.objects.filter(
|
||||||
|
Q(untagged_vlan_id=self.pk) |
|
||||||
|
Q(tagged_vlans=self.pk)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def get_vminterfaces(self):
|
||||||
|
# Return all VM interfaces assigned to this VLAN
|
||||||
|
return VMInterface.objects.filter(
|
||||||
|
Q(untagged_vlan_id=self.pk) |
|
||||||
|
Q(tagged_vlans=self.pk)
|
||||||
|
).distinct()
|
139
netbox/ipam/models/vrfs.py
Normal file
139
netbox/ipam/models/vrfs.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
from extras.models import TaggedItem
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from ipam.constants import *
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'RouteTarget',
|
||||||
|
'VRF',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
|
class VRF(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
||||||
|
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.)
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
rd = models.CharField(
|
||||||
|
max_length=VRF_RD_MAX_LENGTH,
|
||||||
|
unique=True,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name='Route distinguisher',
|
||||||
|
help_text='Unique route distinguisher (as defined in RFC 4364)'
|
||||||
|
)
|
||||||
|
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=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
import_targets = models.ManyToManyField(
|
||||||
|
to='ipam.RouteTarget',
|
||||||
|
related_name='importing_vrfs',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
export_targets = models.ManyToManyField(
|
||||||
|
to='ipam.RouteTarget',
|
||||||
|
related_name='exporting_vrfs',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tags = TaggableManager(through=TaggedItem)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
||||||
|
clone_fields = [
|
||||||
|
'tenant', 'enforce_unique', 'description',
|
||||||
|
]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique
|
||||||
|
verbose_name = 'VRF'
|
||||||
|
verbose_name_plural = 'VRFs'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.display_name or super().__str__()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:vrf', args=[self.pk])
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.name,
|
||||||
|
self.rd,
|
||||||
|
self.tenant.name if self.tenant else None,
|
||||||
|
self.enforce_unique,
|
||||||
|
self.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
if self.rd:
|
||||||
|
return f'{self.name} ({self.rd})'
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
|
class RouteTarget(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
|
||||||
|
unique=True,
|
||||||
|
help_text='Route target value (formatted in accordance with RFC 4360)'
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='route_targets',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
tags = TaggableManager(through=TaggedItem)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['name', 'description', 'tenant']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:routetarget', args=[self.pk])
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.name,
|
||||||
|
self.description,
|
||||||
|
self.tenant.name if self.tenant else None,
|
||||||
|
)
|
184
netbox/netbox/models.py
Normal file
184
netbox/netbox/models.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
from django.core.validators import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
|
from utilities.mptt import TreeManager
|
||||||
|
from utilities.utils import serialize_object
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BigIDModel',
|
||||||
|
'NestedGroupModel',
|
||||||
|
'OrganizationalModel',
|
||||||
|
'PrimaryModel',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BigIDModel(models.Model):
|
||||||
|
"""
|
||||||
|
Abstract base model for all Schematic data objects. Ensures the use of a 64-bit PK.
|
||||||
|
"""
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class CoreModel(BigIDModel):
|
||||||
|
"""
|
||||||
|
Base class for all core objects. Provides the following:
|
||||||
|
- Change logging
|
||||||
|
- Custom field support
|
||||||
|
"""
|
||||||
|
created = models.DateField(
|
||||||
|
auto_now_add=True,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
last_updated = models.DateTimeField(
|
||||||
|
auto_now=True,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def to_objectchange(self, action):
|
||||||
|
"""
|
||||||
|
Return a new ObjectChange representing a change made to this object. This will typically be called automatically
|
||||||
|
by ChangeLoggingMiddleware.
|
||||||
|
"""
|
||||||
|
from extras.models import ObjectChange
|
||||||
|
return ObjectChange(
|
||||||
|
changed_object=self,
|
||||||
|
object_repr=str(self),
|
||||||
|
action=action,
|
||||||
|
object_data=serialize_object(self)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PrimaryModel(CoreModel):
|
||||||
|
"""
|
||||||
|
Primary models represent real objects within the infrastructure being modeled.
|
||||||
|
"""
|
||||||
|
custom_field_data = models.JSONField(
|
||||||
|
encoder=DjangoJSONEncoder,
|
||||||
|
blank=True,
|
||||||
|
default=dict
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cf(self):
|
||||||
|
"""
|
||||||
|
Convenience wrapper for custom field data.
|
||||||
|
"""
|
||||||
|
return self.custom_field_data
|
||||||
|
|
||||||
|
def get_custom_fields(self):
|
||||||
|
"""
|
||||||
|
Return a dictionary of custom fields for a single object in the form {<field>: value}.
|
||||||
|
"""
|
||||||
|
from extras.models import CustomField
|
||||||
|
|
||||||
|
fields = CustomField.objects.get_for_model(self)
|
||||||
|
return OrderedDict([
|
||||||
|
(field, self.custom_field_data.get(field.name)) for field in fields
|
||||||
|
])
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
from extras.models import CustomField
|
||||||
|
|
||||||
|
custom_fields = {cf.name: cf for cf in CustomField.objects.get_for_model(self)}
|
||||||
|
|
||||||
|
# Validate all field values
|
||||||
|
for field_name, value in self.custom_field_data.items():
|
||||||
|
if field_name not in custom_fields:
|
||||||
|
raise ValidationError(f"Unknown field name '{field_name}' in custom field data.")
|
||||||
|
try:
|
||||||
|
custom_fields[field_name].validate(value)
|
||||||
|
except ValidationError as e:
|
||||||
|
raise ValidationError(f"Invalid value for custom field '{field_name}': {e.message}")
|
||||||
|
|
||||||
|
# Check for missing required values
|
||||||
|
for cf in custom_fields.values():
|
||||||
|
if cf.required and cf.name not in self.custom_field_data:
|
||||||
|
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
||||||
|
|
||||||
|
|
||||||
|
class NestedGroupModel(PrimaryModel, MPTTModel):
|
||||||
|
"""
|
||||||
|
Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
|
||||||
|
recursively using MPTT. Within each parent, each child instance must have a unique name.
|
||||||
|
"""
|
||||||
|
parent = TreeForeignKey(
|
||||||
|
to='self',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='children',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = TreeManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class MPTTMeta:
|
||||||
|
order_insertion_by = ('name',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def to_objectchange(self, action):
|
||||||
|
# Remove MPTT-internal fields
|
||||||
|
from extras.models import ObjectChange
|
||||||
|
return ObjectChange(
|
||||||
|
changed_object=self,
|
||||||
|
object_repr=str(self),
|
||||||
|
action=action,
|
||||||
|
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationalModel(PrimaryModel):
|
||||||
|
"""
|
||||||
|
Organizational models are those which are used solely to categorize and qualify other objects, and do not convey
|
||||||
|
any real information about the infrastructure being modeled (for example, functional device roles). Organizational
|
||||||
|
models provide the following standard attributes:
|
||||||
|
- Unique name
|
||||||
|
- Unique slug (automatically derived from name)
|
||||||
|
- Optional description
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ('name',)
|
37
netbox/secrets/migrations/0013_standardize_models.py
Normal file
37
netbox/secrets/migrations/0013_standardize_models.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('secrets', '0012_standardize_name_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='secretrole',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='secret',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='secretrole',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sessionkey',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userkey',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -14,8 +14,9 @@ from django.urls import reverse
|
|||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
|
from extras.models import TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import BigIDModel, OrganizationalModel, PrimaryModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from .exceptions import InvalidKey
|
from .exceptions import InvalidKey
|
||||||
from .hashers import SecretValidationHasher
|
from .hashers import SecretValidationHasher
|
||||||
@ -31,7 +32,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserKey(models.Model):
|
class UserKey(BigIDModel):
|
||||||
"""
|
"""
|
||||||
A UserKey stores a user's personal RSA (public) encryption key, which is used to generate their unique encrypted
|
A UserKey stores a user's personal RSA (public) encryption key, which is used to generate their unique encrypted
|
||||||
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
|
||||||
@ -164,7 +165,7 @@ class UserKey(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
class SessionKey(models.Model):
|
class SessionKey(BigIDModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
@ -234,7 +235,8 @@ class SessionKey(models.Model):
|
|||||||
return session_key
|
return session_key
|
||||||
|
|
||||||
|
|
||||||
class SecretRole(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class SecretRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
|
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
|
||||||
such as "Login Credentials" or "SNMP Communities."
|
such as "Login Credentials" or "SNMP Communities."
|
||||||
@ -274,7 +276,7 @@ class SecretRole(ChangeLoggedModel):
|
|||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Secret(ChangeLoggedModel, CustomFieldModel):
|
class Secret(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible
|
A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible
|
||||||
SHA-256 hash is stored along with the ciphertext for validation upon decryption. Each Secret is assigned to exactly
|
SHA-256 hash is stored along with the ciphertext for validation upon decryption. Each Secret is assigned to exactly
|
||||||
|
27
netbox/tenancy/migrations/0012_standardize_models.py
Normal file
27
netbox/tenancy/migrations/0012_standardize_models.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0011_standardize_name_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tenantgroup',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tenant',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tenantgroup',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -3,11 +3,11 @@ from django.urls import reverse
|
|||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ObjectChange, TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import NestedGroupModel, PrimaryModel
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import serialize_object
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -16,7 +16,8 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TenantGroup(MPTTModel, ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class TenantGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
An arbitrary collection of Tenants.
|
An arbitrary collection of Tenants.
|
||||||
"""
|
"""
|
||||||
@ -48,12 +49,6 @@ class TenantGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
class MPTTMeta:
|
|
||||||
order_insertion_by = ['name']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug)
|
return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug)
|
||||||
|
|
||||||
@ -65,18 +60,9 @@ class TenantGroup(MPTTModel, ChangeLoggedModel):
|
|||||||
self.description,
|
self.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
|
||||||
# Remove MPTT-internal fields
|
|
||||||
return ObjectChange(
|
|
||||||
changed_object=self,
|
|
||||||
object_repr=str(self),
|
|
||||||
action=action,
|
|
||||||
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Tenant(ChangeLoggedModel, CustomFieldModel):
|
class Tenant(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
26
netbox/users/migrations/0011_standardize_models.py
Normal file
26
netbox/users/migrations/0011_standardize_models.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0010_update_jsonfield'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='objectpermission',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='token',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userconfig',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -11,6 +11,7 @@ from django.db.models.signals import post_save
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from netbox.models import BigIDModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import flatten_dict
|
from utilities.utils import flatten_dict
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ class AdminUser(User):
|
|||||||
# User preferences
|
# User preferences
|
||||||
#
|
#
|
||||||
|
|
||||||
class UserConfig(models.Model):
|
class UserConfig(BigIDModel):
|
||||||
"""
|
"""
|
||||||
This model stores arbitrary user-specific preferences in a JSON data structure.
|
This model stores arbitrary user-specific preferences in a JSON data structure.
|
||||||
"""
|
"""
|
||||||
@ -175,7 +176,7 @@ def create_userconfig(instance, created, **kwargs):
|
|||||||
# REST API
|
# REST API
|
||||||
#
|
#
|
||||||
|
|
||||||
class Token(models.Model):
|
class Token(BigIDModel):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -233,7 +234,7 @@ class Token(models.Model):
|
|||||||
# Permissions
|
# Permissions
|
||||||
#
|
#
|
||||||
|
|
||||||
class ObjectPermission(models.Model):
|
class ObjectPermission(BigIDModel):
|
||||||
"""
|
"""
|
||||||
A mapping of view, add, change, and/or delete permission for users and/or groups to an arbitrary set of objects
|
A mapping of view, add, change, and/or delete permission for users and/or groups to an arbitrary set of objects
|
||||||
identified by ORM query parameters.
|
identified by ORM query parameters.
|
||||||
|
62
netbox/virtualization/migrations/0020_standardize_models.py
Normal file
62
netbox/virtualization/migrations/0020_standardize_models.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('virtualization', '0019_standardize_name_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='clustergroup',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='clustertype',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vminterface',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vminterface',
|
||||||
|
name='custom_field_data',
|
||||||
|
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vminterface',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cluster',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='clustergroup',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='clustertype',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='virtualmachine',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vminterface',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
@ -6,9 +6,10 @@ from django.urls import reverse
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.models import BaseInterface, Device
|
from dcim.models import BaseInterface, Device
|
||||||
from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import ConfigContextModel, ObjectChange, TaggedItem
|
||||||
from extras.querysets import ConfigContextModelQuerySet
|
from extras.querysets import ConfigContextModelQuerySet
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
from utilities.query_functions import CollateAsChar
|
from utilities.query_functions import CollateAsChar
|
||||||
@ -30,7 +31,8 @@ __all__ = (
|
|||||||
# Cluster types
|
# Cluster types
|
||||||
#
|
#
|
||||||
|
|
||||||
class ClusterType(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class ClusterType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A type of Cluster.
|
A type of Cluster.
|
||||||
"""
|
"""
|
||||||
@ -72,7 +74,8 @@ class ClusterType(ChangeLoggedModel):
|
|||||||
# Cluster groups
|
# Cluster groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class ClusterGroup(ChangeLoggedModel):
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
|
class ClusterGroup(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
An organizational group of Clusters.
|
An organizational group of Clusters.
|
||||||
"""
|
"""
|
||||||
@ -115,7 +118,7 @@ class ClusterGroup(ChangeLoggedModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Cluster(ChangeLoggedModel, CustomFieldModel):
|
class Cluster(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
|
A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
|
||||||
"""
|
"""
|
||||||
@ -199,7 +202,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
class VirtualMachine(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A virtual machine which runs inside a Cluster.
|
A virtual machine which runs inside a Cluster.
|
||||||
"""
|
"""
|
||||||
@ -371,7 +374,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('export_templates', 'webhooks')
|
||||||
class VMInterface(BaseInterface):
|
class VMInterface(PrimaryModel, BaseInterface):
|
||||||
virtual_machine = models.ForeignKey(
|
virtual_machine = models.ForeignKey(
|
||||||
to='virtualization.VirtualMachine',
|
to='virtualization.VirtualMachine',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
Loading…
Reference in New Issue
Block a user