From 487d67768bf6a52c9e96e8a1a3aebda17640f0fe Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 18 Oct 2021 16:20:31 -0400 Subject: [PATCH] Cleanup and documentation for #1344 --- docs/core-functionality/contacts.md | 5 +++++ docs/models/tenancy/contact.md | 31 +++++++++++++++++++++++++++++ docs/models/tenancy/contactgroup.md | 3 +++ docs/models/tenancy/contactrole.md | 3 +++ mkdocs.yml | 1 + netbox/tenancy/api/serializers.py | 7 +++++-- netbox/tenancy/filtersets.py | 9 +++++---- netbox/tenancy/forms/bulk_edit.py | 15 ++++++++++++++ netbox/tenancy/forms/models.py | 10 ++++++++-- netbox/tenancy/models.py | 3 +++ 10 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 docs/core-functionality/contacts.md create mode 100644 docs/models/tenancy/contact.md create mode 100644 docs/models/tenancy/contactgroup.md create mode 100644 docs/models/tenancy/contactrole.md diff --git a/docs/core-functionality/contacts.md b/docs/core-functionality/contacts.md new file mode 100644 index 000000000..76a005fc0 --- /dev/null +++ b/docs/core-functionality/contacts.md @@ -0,0 +1,5 @@ +# Contacts + +{!models/tenancy/contact.md!} +{!models/tenancy/contactgroup.md!} +{!models/tenancy/contactrole.md!} diff --git a/docs/models/tenancy/contact.md b/docs/models/tenancy/contact.md new file mode 100644 index 000000000..9d81e2d85 --- /dev/null +++ b/docs/models/tenancy/contact.md @@ -0,0 +1,31 @@ +# Contacts + +A contact represent an individual or group that has been associated with an object in NetBox for administrative reasons. For example, you might assign one or more operational contacts to each site. Contacts can be arranged within nested contact groups. + +Each contact must include a name, which is unique to its parent group (if any). The following optional descriptors are also available: + +* Title +* Phone +* Email +* Address + +## Contact Assignment + +Each contact can be assigned to one or more objects, allowing for the efficient reuse of contact information. When assigning a contact to an object, the user may optionally specify a role and/or priority (primary, secondary, tertiary, or inactive) to better convey the nature of the contact's relationship to the assigned object. + +The following models support the assignment of contacts: + +* circuits.Circuit +* circuits.Provider +* dcim.Device +* dcim.Location +* dcim.Manufacturer +* dcim.PowerPanel +* dcim.Rack +* dcim.Region +* dcim.Site +* dcim.SiteGroup +* tenancy.Tenant +* virtualization.Cluster +* virtualization.ClusterGroup +* virtualization.VirtualMachine diff --git a/docs/models/tenancy/contactgroup.md b/docs/models/tenancy/contactgroup.md new file mode 100644 index 000000000..ea566c58a --- /dev/null +++ b/docs/models/tenancy/contactgroup.md @@ -0,0 +1,3 @@ +# Contact Groups + +Contacts can be organized into arbitrary groups. These groups can be recursively nested for convenience. Each contact within a group must have a unique name, but other attributes can be repeated. diff --git a/docs/models/tenancy/contactrole.md b/docs/models/tenancy/contactrole.md new file mode 100644 index 000000000..23642ca03 --- /dev/null +++ b/docs/models/tenancy/contactrole.md @@ -0,0 +1,3 @@ +# Contact Roles + +Contacts can be organized by functional roles, which are fully customizable by the user. For example, you might create roles for administrative, operational, or emergency contacts. diff --git a/mkdocs.yml b/mkdocs.yml index 7244c36d6..72750d6f5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,6 +62,7 @@ nav: - Circuits: 'core-functionality/circuits.md' - Power Tracking: 'core-functionality/power.md' - Tenancy: 'core-functionality/tenancy.md' + - Contacts: 'core-functionality/contacts.md' - Customization: - Custom Fields: 'customization/custom-fields.md' - Custom Validation: 'customization/custom-validation.md' diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 2dfb59455..27a14b350 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,8 +1,9 @@ from django.contrib.auth.models import ContentType from rest_framework import serializers -from netbox.api import ContentTypeField +from netbox.api import ChoiceField, ContentTypeField from netbox.api.serializers import NestedGroupModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer +from tenancy.choices import ContactPriorityChoices from tenancy.models import * from .nested_serializers import * @@ -93,9 +94,11 @@ class ContactAssignmentSerializer(PrimaryModelSerializer): ) contact = NestedContactSerializer() role = NestedContactRoleSerializer(required=False, allow_null=True) + priority = ChoiceField(choices=ContactPriorityChoices, required=False) class Meta: model = ContactAssignment fields = [ - 'id', 'url', 'display', 'content_type', 'object_id', 'contact', 'role', 'created', 'last_updated', + 'id', 'url', 'display', 'content_type', 'object_id', 'contact', 'role', 'priority', 'created', + 'last_updated', ] diff --git a/netbox/tenancy/filtersets.py b/netbox/tenancy/filtersets.py index 75f9e351d..f6d0ac72e 100644 --- a/netbox/tenancy/filtersets.py +++ b/netbox/tenancy/filtersets.py @@ -2,8 +2,8 @@ import django_filters from django.db.models import Q from extras.filters import TagFilter -from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet -from utilities.filters import TreeNodeMultipleChoiceFilter +from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet +from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter from .models import * @@ -168,7 +168,8 @@ class ContactFilterSet(PrimaryModelFilterSet): ) -class ContactAssignmentFilterSet(OrganizationalModelFilterSet): +class ContactAssignmentFilterSet(ChangeLoggedModelFilterSet): + content_type = ContentTypeFilter() contact_id = django_filters.ModelMultipleChoiceFilter( queryset=Contact.objects.all(), label='Contact (ID)', @@ -186,4 +187,4 @@ class ContactAssignmentFilterSet(OrganizationalModelFilterSet): class Meta: model = ContactAssignment - fields = ['id', 'priority'] + fields = ['id', 'content_type_id', 'priority'] diff --git a/netbox/tenancy/forms/bulk_edit.py b/netbox/tenancy/forms/bulk_edit.py index 0d414d2a5..a34b8def1 100644 --- a/netbox/tenancy/forms/bulk_edit.py +++ b/netbox/tenancy/forms/bulk_edit.py @@ -96,6 +96,21 @@ class ContactBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul queryset=ContactGroup.objects.all(), required=False ) + title = forms.CharField( + max_length=100, + required=False + ) + phone = forms.CharField( + max_length=50, + required=False + ) + email = forms.EmailField( + required=False + ) + address = forms.CharField( + max_length=200, + required=False + ) class Meta: nullable_fields = ['group', 'title', 'phone', 'email', 'address', 'comments'] diff --git a/netbox/tenancy/forms/models.py b/netbox/tenancy/forms/models.py index c0aec0aa8..b15065705 100644 --- a/netbox/tenancy/forms/models.py +++ b/netbox/tenancy/forms/models.py @@ -109,10 +109,16 @@ class ContactForm(BootstrapMixin, CustomFieldModelForm): class ContactAssignmentForm(BootstrapMixin, forms.ModelForm): group = DynamicModelChoiceField( queryset=ContactGroup.objects.all(), - required=False + required=False, + initial_params={ + 'contacts': '$contact' + } ) contact = DynamicModelChoiceField( - queryset=Contact.objects.all() + queryset=Contact.objects.all(), + query_params={ + 'group_id': '$group' + } ) role = DynamicModelChoiceField( queryset=ContactRole.objects.all() diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index f53f7d0e6..20708f74a 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -259,3 +259,6 @@ class ContactAssignment(ChangeLoggedModel): class Meta: ordering = ('priority', 'contact') + + def __str__(self): + return f"{self.contact} ({self.get_priority_display()})" if self.priority else self.name