contacts.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from django.contrib.contenttypes.fields import GenericForeignKey
  2. from django.core.exceptions import ValidationError
  3. from django.db import models
  4. from django.urls import reverse
  5. from django.utils.translation import gettext_lazy as _
  6. from core.models import ContentType
  7. from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
  8. from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin
  9. from tenancy.choices import *
  10. __all__ = (
  11. 'ContactAssignment',
  12. 'Contact',
  13. 'ContactGroup',
  14. 'ContactRole',
  15. )
  16. class ContactGroup(NestedGroupModel):
  17. """
  18. An arbitrary collection of Contacts.
  19. """
  20. class Meta:
  21. ordering = ['name']
  22. constraints = (
  23. models.UniqueConstraint(
  24. fields=('parent', 'name'),
  25. name='%(app_label)s_%(class)s_unique_parent_name'
  26. ),
  27. )
  28. verbose_name = _('contact group')
  29. verbose_name_plural = _('contact groups')
  30. def get_absolute_url(self):
  31. return reverse('tenancy:contactgroup', args=[self.pk])
  32. class ContactRole(OrganizationalModel):
  33. """
  34. Functional role for a Contact assigned to an object.
  35. """
  36. def get_absolute_url(self):
  37. return reverse('tenancy:contactrole', args=[self.pk])
  38. class Meta:
  39. ordering = ('name',)
  40. verbose_name = _('contact role')
  41. verbose_name_plural = _('contact roles')
  42. class Contact(PrimaryModel):
  43. """
  44. Contact information for a particular object(s) in NetBox.
  45. """
  46. group = models.ForeignKey(
  47. to='tenancy.ContactGroup',
  48. on_delete=models.SET_NULL,
  49. related_name='contacts',
  50. blank=True,
  51. null=True
  52. )
  53. name = models.CharField(
  54. verbose_name=_('name'),
  55. max_length=100
  56. )
  57. title = models.CharField(
  58. verbose_name=_('title'),
  59. max_length=100,
  60. blank=True
  61. )
  62. phone = models.CharField(
  63. verbose_name=_('phone'),
  64. max_length=50,
  65. blank=True
  66. )
  67. email = models.EmailField(
  68. verbose_name=_('email'),
  69. blank=True
  70. )
  71. address = models.CharField(
  72. verbose_name=_('address'),
  73. max_length=200,
  74. blank=True
  75. )
  76. link = models.URLField(
  77. verbose_name=_('link'),
  78. blank=True
  79. )
  80. clone_fields = (
  81. 'group', 'name', 'title', 'phone', 'email', 'address', 'link',
  82. )
  83. class Meta:
  84. ordering = ['name']
  85. constraints = (
  86. models.UniqueConstraint(
  87. fields=('group', 'name'),
  88. name='%(app_label)s_%(class)s_unique_group_name'
  89. ),
  90. )
  91. verbose_name = _('contact')
  92. verbose_name_plural = _('contacts')
  93. def __str__(self):
  94. return self.name
  95. def get_absolute_url(self):
  96. return reverse('tenancy:contact', args=[self.pk])
  97. class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
  98. content_type = models.ForeignKey(
  99. to='contenttypes.ContentType',
  100. on_delete=models.CASCADE
  101. )
  102. object_id = models.PositiveBigIntegerField()
  103. object = GenericForeignKey(
  104. ct_field='content_type',
  105. fk_field='object_id'
  106. )
  107. contact = models.ForeignKey(
  108. to='tenancy.Contact',
  109. on_delete=models.PROTECT,
  110. related_name='assignments'
  111. )
  112. role = models.ForeignKey(
  113. to='tenancy.ContactRole',
  114. on_delete=models.PROTECT,
  115. related_name='assignments'
  116. )
  117. priority = models.CharField(
  118. verbose_name=_('priority'),
  119. max_length=50,
  120. choices=ContactPriorityChoices,
  121. blank=True
  122. )
  123. clone_fields = ('content_type', 'object_id', 'role', 'priority')
  124. class Meta:
  125. ordering = ('contact', 'priority', 'role', 'pk')
  126. indexes = (
  127. models.Index(fields=('content_type', 'object_id')),
  128. )
  129. constraints = (
  130. models.UniqueConstraint(
  131. fields=('content_type', 'object_id', 'contact', 'role'),
  132. name='%(app_label)s_%(class)s_unique_object_contact_role'
  133. ),
  134. )
  135. verbose_name = _('contact assignment')
  136. verbose_name_plural = _('contact assignments')
  137. def __str__(self):
  138. if self.priority:
  139. return f"{self.contact} ({self.get_priority_display()}) -> {self.object}"
  140. return str(f"{self.contact} -> {self.object}")
  141. def get_absolute_url(self):
  142. return reverse('tenancy:contact', args=[self.contact.pk])
  143. def clean(self):
  144. super().clean()
  145. # Validate the assigned object type
  146. if self.content_type not in ContentType.objects.with_feature('contacts'):
  147. raise ValidationError(
  148. _("Contacts cannot be assigned to this object type ({type}).").format(type=self.content_type)
  149. )
  150. def to_objectchange(self, action):
  151. objectchange = super().to_objectchange(action)
  152. objectchange.related_object = self.object
  153. return objectchange