diff --git a/netbox/dcim/migrations/0203_devicerolegroup_devicerole_group.py b/netbox/dcim/migrations/0203_devicerolegroup_devicerole_group.py new file mode 100644 index 000000000..090ff04e6 --- /dev/null +++ b/netbox/dcim/migrations/0203_devicerolegroup_devicerole_group.py @@ -0,0 +1,65 @@ +# Generated by Django 5.2b1 on 2025-03-20 21:31 + +import django.db.models.deletion +import mptt.fields +import taggit.managers +import utilities.json +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0202_location_comments_region_comments_sitegroup_comments'), + ('extras', '0125_exporttemplate_file_name'), + ] + + operations = [ + migrations.CreateModel( + name='DeviceRoleGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ( + 'custom_field_data', + models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('name', models.CharField(db_collation='natural_sort', max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ( + 'parent', + mptt.fields.TreeForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='children', + to='dcim.devicerolegroup', + ), + ), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name': 'device role group', + 'verbose_name_plural': 'device role groups', + 'ordering': ['name'], + }, + ), + migrations.AddField( + model_name='devicerole', + name='group', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='device_roles', + to='dcim.devicerolegroup', + ), + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 2acd98801..547de4927 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -23,7 +23,8 @@ from extras.models import ConfigContextModel, CustomField from extras.querysets import ConfigContextModelQuerySet from netbox.choices import ColorChoices from netbox.config import ConfigItem -from netbox.models import OrganizationalModel, PrimaryModel +from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel + from netbox.models.mixins import WeightMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.fields import ColorField, CounterCacheField @@ -468,6 +469,28 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): # Devices # +class DeviceRoleGroup(NestedGroupModel): + """ + An arbitrary collection of Tenants. + """ + name = models.CharField( + verbose_name=_('name'), + max_length=100, + unique=True, + db_collation="natural_sort" + ) + slug = models.SlugField( + verbose_name=_('slug'), + max_length=100, + unique=True + ) + + class Meta: + ordering = ['name'] + verbose_name = _('device role group') + verbose_name_plural = _('device role groups') + + class DeviceRole(OrganizationalModel): """ Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a @@ -490,6 +513,13 @@ class DeviceRole(OrganizationalModel): blank=True, null=True ) + group = models.ForeignKey( + to='dcim.DeviceRoleGroup', + on_delete=models.SET_NULL, + related_name='device_roles', + blank=True, + null=True + ) class Meta: ordering = ('name',)