class NestedGroupModel(ChangeLoggingMixin, CustomFieldsMixin, CustomValidationMixin, BigIDModel, 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 clean(self): super().clean() # An MPTT model cannot be its own parent if self.pk and self.parent_id == self.pk: raise ValidationError({"parent": "Cannot assign self as parent."})
class Region(MPTTModel, ChangeLoggedModel): """ Sites can be grouped within geographic Regions. """ parent = TreeForeignKey(to='self', on_delete=models.CASCADE, related_name='children', blank=True, null=True, db_index=True) name = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True) description = models.CharField(max_length=200, blank=True) objects = TreeManager() csv_headers = ['name', 'slug', 'parent', 'description'] class MPTTMeta: order_insertion_by = ['name'] def __str__(self): return self.name def get_absolute_url(self): return "{}?region={}".format(reverse('dcim:site_list'), self.slug) def to_csv(self): return ( self.name, self.slug, self.parent.name if self.parent else None, self.description, ) def get_site_count(self): return Site.objects.filter( Q(region=self) | Q(region__in=self.get_descendants())).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'])) def clean(self): super().clean() # An MPTT model cannot be its own parent if self.pk and self.parent_id == self.pk: raise ValidationError({"parent": "Cannot assign self as parent."})
class InventoryItem(MPTTModel, ComponentModel): """ An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. InventoryItems are used only for inventory purposes. """ parent = TreeForeignKey( to='self', on_delete=models.CASCADE, related_name='child_items', blank=True, null=True, db_index=True ) manufacturer = models.ForeignKey( to='dcim.Manufacturer', on_delete=models.PROTECT, related_name='inventory_items', blank=True, null=True ) part_id = models.CharField( max_length=50, verbose_name='Part ID', blank=True, help_text='Manufacturer-assigned part identifier' ) serial = models.CharField( max_length=50, verbose_name='Serial number', blank=True ) asset_tag = models.CharField( max_length=50, unique=True, blank=True, null=True, verbose_name='Asset tag', help_text='A unique tag used to identify this item' ) discovered = models.BooleanField( default=False, help_text='This item was automatically discovered' ) objects = TreeManager() clone_fields = ['device', 'parent', 'manufacturer', 'part_id'] class Meta: ordering = ('device__id', 'parent__id', '_name') unique_together = ('device', 'parent', 'name') def get_absolute_url(self): return reverse('dcim:inventoryitem', kwargs={'pk': self.pk})
class TenantGroup(MPTTModel, ChangeLoggedModel): """ An arbitrary collection of Tenants. """ name = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True) parent = TreeForeignKey(to='self', on_delete=models.CASCADE, related_name='children', blank=True, null=True, db_index=True) description = models.CharField(max_length=200, blank=True) objects = TreeManager() csv_headers = ['name', 'slug', 'parent', 'description'] class Meta: ordering = ['name'] class MPTTMeta: order_insertion_by = ['name'] def __str__(self): return self.name def get_absolute_url(self): return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug) def to_csv(self): return ( self.name, self.slug, self.parent.name if self.parent else '', 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): super().clean() # An MPTT model cannot be its own parent if self.pk and self.parent_id == self.pk: raise ValidationError({"parent": "Cannot assign self as parent."})
class RackGroup(MPTTModel, ChangeLoggedModel): """ 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 campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor. """ name = models.CharField( max_length=100 ) slug = models.SlugField( max_length=100 ) site = models.ForeignKey( to='dcim.Site', on_delete=models.CASCADE, related_name='rack_groups' ) parent = TreeForeignKey( to='self', on_delete=models.CASCADE, related_name='children', blank=True, null=True, db_index=True ) description = models.CharField( max_length=200, blank=True ) objects = TreeManager() csv_headers = ['site', 'parent', 'name', 'slug', 'description'] class Meta: ordering = ['site', 'name'] unique_together = [ ['site', 'name'], ['site', 'slug'], ] class MPTTMeta: order_insertion_by = ['name'] def __str__(self): return self.name def get_absolute_url(self): return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk) def to_csv(self): return ( self.site, self.parent.name if self.parent else '', self.name, self.slug, 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): super().clean() # Parent RackGroup (if any) must belong to the same Site if self.parent and self.parent.site != self.site: raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})")
class Region(MPTTModel, ChangeLoggedModel): """ Sites can be grouped within geographic Regions. """ parent = TreeForeignKey( to='self', on_delete=models.CASCADE, related_name='children', blank=True, null=True, db_index=True, verbose_name='родитель' ) name = models.CharField( max_length=50, unique=True, verbose_name='название' ) slug = models.SlugField( unique=True ) description = models.CharField( max_length=200, blank=True, verbose_name='описание' ) objects = TreeManager() csv_headers = ['name', 'slug', 'parent', 'description'] class MPTTMeta: order_insertion_by = ['name'] class Meta: verbose_name = 'регион' verbose_name_plural = 'регионы' def __str__(self): return self.name def get_absolute_url(self): return "{}?region={}".format(reverse('dcim:site_list'), self.slug) def to_csv(self): return ( self.name, self.slug, self.parent.name if self.parent else None, self.description, ) def get_site_count(self): return Site.objects.filter( Q(region=self) | Q(region__in=self.get_descendants()) ).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']) )