from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from mptt.models import MPTTModel, TreeForeignKey from taggit.managers import TaggableManager from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem from extras.utils import extras_features from utilities.mptt import TreeManager from utilities.querysets import RestrictedQuerySet from utilities.utils import serialize_object __all__ = ( 'Tenant', 'TenantGroup', ) 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." }) @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Tenant(ChangeLoggedModel, CustomFieldModel): """ A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal department. """ name = models.CharField( max_length=100, unique=True ) slug = models.SlugField( max_length=100, unique=True ) group = models.ForeignKey( to='tenancy.TenantGroup', on_delete=models.SET_NULL, related_name='tenants', blank=True, null=True ) description = models.CharField( max_length=200, blank=True ) comments = models.TextField( blank=True ) tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() csv_headers = ['name', 'slug', 'group', 'description', 'comments'] clone_fields = [ 'group', 'description', ] class Meta: ordering = ['group', 'name'] def __str__(self): return self.name def get_absolute_url(self): return reverse('tenancy:tenant', args=[self.slug]) def to_csv(self): return ( self.name, self.slug, self.group.name if self.group else None, self.description, self.comments, )