mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Initial work on contacts
This commit is contained in:
@ -120,6 +120,14 @@ ORGANIZATION_MENU = Menu(
|
||||
get_model_item('tenancy', 'tenantgroup', 'Tenant Groups'),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
label='Contacts',
|
||||
items=(
|
||||
get_model_item('tenancy', 'contact', 'Contacts'),
|
||||
get_model_item('tenancy', 'contactgroup', 'Contact Groups'),
|
||||
get_model_item('tenancy', 'contactrole', 'Contact Roles'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
66
netbox/templates/tenancy/contact.html
Normal file
66
netbox/templates/tenancy/contact.html
Normal file
@ -0,0 +1,66 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
{% if object.group %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'tenancy:contact_list' %}?group_id={{ object.group.pk }}">{{ object.group }}</a></li>
|
||||
{% endif %}
|
||||
{% endblock breadcrumbs %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-7">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Tenant</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<td>Group</td>
|
||||
<td>
|
||||
{% if object.group %}
|
||||
<a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Title</td>
|
||||
<td>{{ object.tile|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Phone</td>
|
||||
<td>{{ object.phone|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Email</td>
|
||||
<td>{{ object.email|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td>{{ object.address|linebreaksbr|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/comments_panel.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-5">
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='tenancy:tenant_list' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
76
netbox/templates/tenancy/contactgroup.html
Normal file
76
netbox/templates/tenancy/contactgroup.html
Normal file
@ -0,0 +1,76 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
{% for contactgroup in object.get_ancestors %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'tenancy:contactgroup_list' %}?parent_id={{ contactgroup.pk }}">{{ contactgroup }}</a></li>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
Contact Group
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Parent</th>
|
||||
<td>
|
||||
{% if object.parent %}
|
||||
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Contacts</th>
|
||||
<td>
|
||||
<a href="{% url 'tenancy:contact_list' %}?group_id={{ object.pk }}">{{ contacts_table.rows|length }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Tenants
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% include 'inc/table.html' with table=contacts_table %}
|
||||
</div>
|
||||
{% if perms.tenancy.add_contact %}
|
||||
<div class="card-footer text-end noprint">
|
||||
<a href="{% url 'tenancy:contact_add' %}?group={{ object.pk }}" class="btn btn-sm btn-primary">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Contact
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% include 'inc/paginator.html' with paginator=contacts_table.paginator page=contacts_table.page %}
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
46
netbox/templates/tenancy/contactrole.html
Normal file
46
netbox/templates/tenancy/contactrole.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'tenancy:contactrole_list' %}">Contact Roles</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Contact Role</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-12">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Assigned Contacts</h5>
|
||||
<div class="card-body">
|
||||
{% include 'inc/table.html' with table=contacts_table %}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/paginator.html' with paginator=contacts_table.paginator page=contacts_table.page %}
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,9 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from netbox.api import WritableNestedSerializer
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
|
||||
__all__ = [
|
||||
'NestedContactSerializer',
|
||||
'NestedContactGroupSerializer',
|
||||
'NestedContactRoleSerializer',
|
||||
'NestedTenantGroupSerializer',
|
||||
'NestedTenantSerializer',
|
||||
]
|
||||
@ -29,3 +32,33 @@ class NestedTenantSerializer(WritableNestedSerializer):
|
||||
class Meta:
|
||||
model = Tenant
|
||||
fields = ['id', 'url', 'display', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class NestedContactGroupSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactgroup-detail')
|
||||
contact_count = serializers.IntegerField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ContactGroup
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'contact_count', '_depth']
|
||||
|
||||
|
||||
class NestedContactRoleSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactrole-detail')
|
||||
|
||||
class Meta:
|
||||
model = ContactRole
|
||||
fields = ['id', 'url', 'display', 'name', 'slug']
|
||||
|
||||
|
||||
class NestedContactSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contact-detail')
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
@ -1,7 +1,9 @@
|
||||
from django.contrib.auth.models import ContentType
|
||||
from rest_framework import serializers
|
||||
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, PrimaryModelSerializer
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from netbox.api import ContentTypeField
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer
|
||||
from tenancy.models import *
|
||||
from .nested_serializers import *
|
||||
|
||||
|
||||
@ -43,3 +45,57 @@ class TenantSerializer(PrimaryModelSerializer):
|
||||
'created', 'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count',
|
||||
'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count', 'cluster_count',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupSerializer(NestedGroupModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactgroup-detail')
|
||||
parent = NestedContactGroupSerializer(required=False, allow_null=True)
|
||||
contact_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ContactGroup
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'contact_count', '_depth',
|
||||
]
|
||||
|
||||
|
||||
class ContactRoleSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactrole-detail')
|
||||
|
||||
class Meta:
|
||||
model = ContactRole
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class ContactSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contact-detail')
|
||||
group = NestedContactGroupSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = [
|
||||
'id', 'url', 'display', 'group', 'name', 'title', 'phone', 'email', 'address', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class ContactAssignmentSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:contactassignment-detail')
|
||||
content_type = ContentTypeField(
|
||||
queryset=ContentType.objects.all()
|
||||
)
|
||||
contact = NestedContactSerializer()
|
||||
role = NestedContactRoleSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = ContactAssignment
|
||||
fields = [
|
||||
'id', 'url', 'display', 'content_type', 'object_id', 'contact', 'role', 'created', 'last_updated',
|
||||
]
|
||||
|
@ -9,5 +9,11 @@ router.APIRootView = views.TenancyRootView
|
||||
router.register('tenant-groups', views.TenantGroupViewSet)
|
||||
router.register('tenants', views.TenantViewSet)
|
||||
|
||||
# Contacts
|
||||
router.register('contact-groups', views.ContactGroupViewSet)
|
||||
router.register('contact-roles', views.ContactRoleViewSet)
|
||||
router.register('contacts', views.ContactViewSet)
|
||||
router.register('contact-assignments', views.ContactAssignmentViewSet)
|
||||
|
||||
app_name = 'tenancy-api'
|
||||
urlpatterns = router.urls
|
||||
|
@ -5,7 +5,7 @@ from dcim.models import Device, Rack, Site
|
||||
from extras.api.views import CustomFieldModelViewSet
|
||||
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
||||
from tenancy import filtersets
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.utils import count_related
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import serializers
|
||||
@ -20,7 +20,7 @@ class TenancyRootView(APIRootView):
|
||||
|
||||
|
||||
#
|
||||
# Tenant Groups
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupViewSet(CustomFieldModelViewSet):
|
||||
@ -35,10 +35,6 @@ class TenantGroupViewSet(CustomFieldModelViewSet):
|
||||
filterset_class = filtersets.TenantGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantViewSet(CustomFieldModelViewSet):
|
||||
queryset = Tenant.objects.prefetch_related(
|
||||
'group', 'tags'
|
||||
@ -55,3 +51,41 @@ class TenantViewSet(CustomFieldModelViewSet):
|
||||
)
|
||||
serializer_class = serializers.TenantSerializer
|
||||
filterset_class = filtersets.TenantFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupViewSet(CustomFieldModelViewSet):
|
||||
queryset = ContactGroup.objects.add_related_count(
|
||||
ContactGroup.objects.all(),
|
||||
Contact,
|
||||
'group',
|
||||
'contact_count',
|
||||
cumulative=True
|
||||
)
|
||||
serializer_class = serializers.ContactGroupSerializer
|
||||
filterset_class = filtersets.ContactGroupFilterSet
|
||||
|
||||
|
||||
class ContactRoleViewSet(CustomFieldModelViewSet):
|
||||
queryset = ContactRole.objects.all()
|
||||
serializer_class = serializers.ContactRoleSerializer
|
||||
filterset_class = filtersets.ContactRoleFilterSet
|
||||
|
||||
|
||||
class ContactViewSet(CustomFieldModelViewSet):
|
||||
queryset = Contact.objects.prefetch_related(
|
||||
'group', 'tags'
|
||||
)
|
||||
serializer_class = serializers.ContactSerializer
|
||||
filterset_class = filtersets.ContactFilterSet
|
||||
|
||||
|
||||
class ContactAssignmentViewSet(CustomFieldModelViewSet):
|
||||
queryset = ContactAssignment.objects.prefetch_related(
|
||||
'contact', 'role'
|
||||
)
|
||||
serializer_class = serializers.ContactAssignmentSerializer
|
||||
filterset_class = filtersets.ContactAssignmentFilterSet
|
||||
|
19
netbox/tenancy/choices.py
Normal file
19
netbox/tenancy/choices.py
Normal file
@ -0,0 +1,19 @@
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactPriorityChoices(ChoiceSet):
|
||||
PRIORITY_PRIMARY = 'primary'
|
||||
PRIORITY_SECONDARY = 'secondary'
|
||||
PRIORITY_TERTIARY = 'tertiary'
|
||||
PRIORITY_INACTIVE = 'inactive'
|
||||
|
||||
CHOICES = (
|
||||
(PRIORITY_PRIMARY, 'Primary'),
|
||||
(PRIORITY_SECONDARY, 'Secondary'),
|
||||
(PRIORITY_TERTIARY, 'Tertiary'),
|
||||
(PRIORITY_INACTIVE, 'Inactive'),
|
||||
)
|
@ -4,16 +4,24 @@ from django.db.models import Q
|
||||
from extras.filters import TagFilter
|
||||
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
|
||||
from utilities.filters import TreeNodeMultipleChoiceFilter
|
||||
from .models import Tenant, TenantGroup
|
||||
from .models import *
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentFilterSet',
|
||||
'ContactFilterSet',
|
||||
'ContactGroupFilterSet',
|
||||
'ContactRoleFilterSet',
|
||||
'TenancyFilterSet',
|
||||
'TenantFilterSet',
|
||||
'TenantGroupFilterSet',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenancy
|
||||
#
|
||||
|
||||
class TenantGroupFilterSet(OrganizationalModelFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -23,7 +31,7 @@ class TenantGroupFilterSet(OrganizationalModelFilterSet):
|
||||
field_name='parent__slug',
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant group group (slug)',
|
||||
label='Tenant group (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -93,3 +101,89 @@ class TenancyFilterSet(django_filters.FilterSet):
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupFilterSet(OrganizationalModelFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
label='Contact group (ID)',
|
||||
)
|
||||
parent = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='parent__slug',
|
||||
queryset=ContactGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Contact group (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ContactGroup
|
||||
fields = ['id', 'name', 'slug', 'description']
|
||||
|
||||
|
||||
class ContactRoleFilterSet(OrganizationalModelFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ContactRole
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class ContactFilterSet(PrimaryModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
group_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
field_name='group',
|
||||
lookup_expr='in',
|
||||
label='Contact group (ID)',
|
||||
)
|
||||
group = TreeNodeMultipleChoiceFilter(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
field_name='group',
|
||||
lookup_expr='in',
|
||||
to_field_name='slug',
|
||||
label='Contact group (slug)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ['id', 'name', 'title', 'phone', 'email', 'address']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(title__icontains=value) |
|
||||
Q(phone__icontains=value) |
|
||||
Q(email__icontains=value) |
|
||||
Q(address__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
class ContactAssignmentFilterSet(OrganizationalModelFilterSet):
|
||||
contact_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Contact.objects.all(),
|
||||
label='Contact (ID)',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ContactRole.objects.all(),
|
||||
label='Contact role (ID)',
|
||||
)
|
||||
role = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='role__slug',
|
||||
queryset=ContactRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Contact role (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ContactAssignment
|
||||
fields = ['id', 'priority']
|
||||
|
@ -1,15 +1,22 @@
|
||||
from django import forms
|
||||
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
|
||||
|
||||
__all__ = (
|
||||
'ContactBulkEditForm',
|
||||
'ContactGroupBulkEditForm',
|
||||
'ContactRoleBulkEditForm',
|
||||
'TenantBulkEditForm',
|
||||
'TenantGroupBulkEditForm',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -42,3 +49,53 @@ class TenantBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulk
|
||||
nullable_fields = [
|
||||
'group',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
|
||||
|
||||
class ContactRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ContactRole.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
|
||||
|
||||
class ContactBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Contact.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['group', 'title', 'phone', 'email', 'address', 'comments']
|
||||
|
@ -1,13 +1,20 @@
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.forms import CSVModelChoiceField, SlugField
|
||||
|
||||
__all__ = (
|
||||
'ContactCSVForm',
|
||||
'ContactGroupCSVForm',
|
||||
'ContactRoleCSVForm',
|
||||
'TenantCSVForm',
|
||||
'TenantGroupCSVForm',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupCSVForm(CustomFieldModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -34,3 +41,43 @@ class TenantCSVForm(CustomFieldModelCSVForm):
|
||||
class Meta:
|
||||
model = Tenant
|
||||
fields = ('name', 'slug', 'group', 'description', 'comments')
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupCSVForm(CustomFieldModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
to_field_name='name',
|
||||
help_text='Parent group'
|
||||
)
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = ContactGroup
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class ContactRoleCSVForm(CustomFieldModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = ContactRole
|
||||
fields = ('name', 'slug', 'description')
|
||||
|
||||
|
||||
class ContactCSVForm(CustomFieldModelCSVForm):
|
||||
slug = SlugField()
|
||||
group = CSVModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
to_field_name='name',
|
||||
help_text='Assigned group'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ('name', 'title', 'phone', 'email', 'address', 'group', 'comments')
|
||||
|
@ -2,9 +2,21 @@ from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, TagFilterField
|
||||
|
||||
__all__ = (
|
||||
'ContactFilterForm',
|
||||
'ContactGroupFilterForm',
|
||||
'ContactRoleFilterForm',
|
||||
'TenantFilterForm',
|
||||
'TenantGroupFilterForm',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
model = TenantGroup
|
||||
@ -40,3 +52,55 @@ class TenantFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
fetch_trigger='open'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
model = ContactGroup
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
||||
label=_('Search')
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
label=_('Parent group'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
|
||||
|
||||
class ContactRoleFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
model = ContactRole
|
||||
field_groups = [
|
||||
['q'],
|
||||
]
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
||||
label=_('Search')
|
||||
)
|
||||
|
||||
|
||||
class ContactFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
model = Contact
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('group_id',),
|
||||
)
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
||||
label=_('Search')
|
||||
)
|
||||
group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
label=_('Group'),
|
||||
fetch_trigger='open'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
@ -1,16 +1,23 @@
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
|
||||
BootstrapMixin, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, SmallTextarea,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
'ContactForm',
|
||||
'ContactGroupForm',
|
||||
'ContactRoleForm',
|
||||
'TenantForm',
|
||||
'TenantGroupForm',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupForm(BootstrapMixin, CustomFieldModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -45,3 +52,51 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm):
|
||||
fieldsets = (
|
||||
('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupForm(BootstrapMixin, CustomFieldModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = ContactGroup
|
||||
fields = ['parent', 'name', 'slug', 'description']
|
||||
|
||||
|
||||
class ContactRoleForm(BootstrapMixin, CustomFieldModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = ContactRole
|
||||
fields = ['name', 'slug', 'description']
|
||||
|
||||
|
||||
class ContactForm(BootstrapMixin, CustomFieldModelForm):
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = (
|
||||
'group', 'name', 'title', 'phone', 'email', 'address', 'comments', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'tags')),
|
||||
)
|
||||
widgets = {
|
||||
'address': SmallTextarea(attrs={'rows': 3}),
|
||||
}
|
||||
|
@ -10,3 +10,15 @@ class TenancyQuery(graphene.ObjectType):
|
||||
|
||||
tenant_group = ObjectField(TenantGroupType)
|
||||
tenant_group_list = ObjectListField(TenantGroupType)
|
||||
|
||||
contact = ObjectField(ContactType)
|
||||
contact_list = ObjectListField(ContactType)
|
||||
|
||||
contact_role = ObjectField(ContactRoleType)
|
||||
contact_role_list = ObjectListField(ContactRoleType)
|
||||
|
||||
contact_group = ObjectField(ContactGroupType)
|
||||
contact_group_list = ObjectListField(ContactGroupType)
|
||||
|
||||
contact_assignment = ObjectField(ContactAssignmentType)
|
||||
contact_assignment_list = ObjectListField(ContactAssignmentType)
|
||||
|
@ -1,12 +1,29 @@
|
||||
import graphene
|
||||
|
||||
from tenancy import filtersets, models
|
||||
from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentType',
|
||||
'ContactGroupType',
|
||||
'ContactRoleType',
|
||||
'ContactType',
|
||||
'TenantType',
|
||||
'TenantGroupType',
|
||||
)
|
||||
|
||||
|
||||
class ContactAssignmentsMixin:
|
||||
assignments = graphene.List('tenancy.graphql.types.ContactAssignmentType')
|
||||
|
||||
def resolve_assignments(self, info):
|
||||
return self.assignments.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
@ -21,3 +38,39 @@ class TenantGroupType(OrganizationalObjectType):
|
||||
model = models.TenantGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TenantGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactType(ContactAssignmentsMixin, PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Contact
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactFilterSet
|
||||
|
||||
|
||||
class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ContactRole
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactRoleFilterSet
|
||||
|
||||
|
||||
class ContactGroupType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ContactGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactGroupFilterSet
|
||||
|
||||
|
||||
class ContactAssignmentType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ContactAssignment
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactAssignmentFilterSet
|
||||
|
98
netbox/tenancy/migrations/0003_contacts.py
Normal file
98
netbox/tenancy/migrations/0003_contacts.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Generated by Django 3.2.8 on 2021-10-18 16:12
|
||||
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
import taggit.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0062_clear_secrets_changelog'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('tenancy', '0002_tenant_ordering'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Contact',
|
||||
fields=[
|
||||
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('title', models.CharField(blank=True, max_length=100)),
|
||||
('phone', models.CharField(blank=True, max_length=50)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('address', models.CharField(blank=True, max_length=200)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContactRole',
|
||||
fields=[
|
||||
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContactGroup',
|
||||
fields=[
|
||||
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('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='tenancy.contactgroup')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContactAssignment',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('priority', models.CharField(blank=True, max_length=50)),
|
||||
('contact', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assignments', to='tenancy.contact')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||
('role', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assignments', to='tenancy.contactrole')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('priority', 'contact'),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contacts', to='tenancy.contactgroup'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||
),
|
||||
]
|
@ -1,19 +1,29 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from extras.utils import extras_features
|
||||
from netbox.models import NestedGroupModel, PrimaryModel
|
||||
from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from .choices import *
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignment',
|
||||
'Contact',
|
||||
'ContactGroup',
|
||||
'ContactRole',
|
||||
'Tenant',
|
||||
'TenantGroup',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class TenantGroup(NestedGroupModel):
|
||||
"""
|
||||
@ -90,3 +100,153 @@ class Tenant(PrimaryModel):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('tenancy:tenant', args=[self.pk])
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class ContactGroup(NestedGroupModel):
|
||||
"""
|
||||
An arbitrary collection of Contacts.
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('tenancy:contactgroup', args=[self.pk])
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class ContactRole(OrganizationalModel):
|
||||
"""
|
||||
Functional role for a Contact assigned to an object.
|
||||
"""
|
||||
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 = RestrictedQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('tenancy:contactrole', args=[self.pk])
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class Contact(PrimaryModel):
|
||||
"""
|
||||
Contact information for a particular object(s) in NetBox.
|
||||
"""
|
||||
group = models.ForeignKey(
|
||||
to='tenancy.ContactGroup',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='contacts',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
title = models.CharField(
|
||||
max_length=100,
|
||||
blank=True
|
||||
)
|
||||
phone = models.CharField(
|
||||
max_length=50,
|
||||
blank=True
|
||||
)
|
||||
email = models.EmailField(
|
||||
blank=True
|
||||
)
|
||||
address = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
clone_fields = [
|
||||
'group',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('tenancy:contact', args=[self.pk])
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class ContactAssignment(ChangeLoggedModel):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
object_id = models.PositiveIntegerField()
|
||||
object = GenericForeignKey(
|
||||
ct_field='content_type',
|
||||
fk_field='object_id'
|
||||
)
|
||||
contact = models.ForeignKey(
|
||||
to='tenancy.Contact',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='assignments'
|
||||
)
|
||||
role = models.ForeignKey(
|
||||
to='tenancy.ContactRole',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='assignments'
|
||||
)
|
||||
priority = models.CharField(
|
||||
max_length=50,
|
||||
choices=ContactPriorityChoices,
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('priority', 'contact')
|
||||
|
@ -3,9 +3,13 @@ import django_tables2 as tables
|
||||
from utilities.tables import (
|
||||
BaseTable, ButtonsColumn, LinkedCountColumn, MarkdownColumn, MPTTColumn, TagColumn, ToggleColumn,
|
||||
)
|
||||
from .models import Tenant, TenantGroup
|
||||
from .models import *
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentTable',
|
||||
'ContactGroupTable',
|
||||
'ContactRoleTable',
|
||||
'ContactTable',
|
||||
'TenantColumn',
|
||||
'TenantGroupTable',
|
||||
'TenantTable',
|
||||
@ -38,7 +42,7 @@ class TenantColumn(tables.TemplateColumn):
|
||||
|
||||
|
||||
#
|
||||
# Tenant groups
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupTable(BaseTable):
|
||||
@ -59,10 +63,6 @@ class TenantGroupTable(BaseTable):
|
||||
default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions')
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(
|
||||
@ -80,3 +80,69 @@ class TenantTable(BaseTable):
|
||||
model = Tenant
|
||||
fields = ('pk', 'name', 'slug', 'group', 'description', 'comments', 'tags')
|
||||
default_columns = ('pk', 'name', 'group', 'description')
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = MPTTColumn(
|
||||
linkify=True
|
||||
)
|
||||
contact_count = LinkedCountColumn(
|
||||
viewname='tenancy:contact_list',
|
||||
url_params={'role_id': 'pk'},
|
||||
verbose_name='Contacts'
|
||||
)
|
||||
actions = ButtonsColumn(ContactGroup)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ContactGroup
|
||||
fields = ('pk', 'name', 'contact_count', 'description', 'slug', 'actions')
|
||||
default_columns = ('pk', 'name', 'contact_count', 'description', 'actions')
|
||||
|
||||
|
||||
class ContactRoleTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
actions = ButtonsColumn(ContactRole)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ContactRole
|
||||
fields = ('pk', 'name', 'description', 'slug', 'actions')
|
||||
default_columns = ('pk', 'name', 'description', 'actions')
|
||||
|
||||
|
||||
class ContactTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
group = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
comments = MarkdownColumn()
|
||||
tags = TagColumn(
|
||||
url_name='tenancy:tenant_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Contact
|
||||
fields = ('pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'comments', 'tags')
|
||||
default_columns = ('pk', 'name', 'group', 'title', 'phone', 'email')
|
||||
|
||||
|
||||
class ContactAssignmentTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
contact = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ContactAssignment
|
||||
fields = ('pk', 'contact', 'role', 'priority')
|
||||
default_columns = ('pk', 'contact', 'role', 'priority')
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
|
||||
|
||||
@ -92,3 +92,112 @@ class TenantTest(APIViewTestCases.APIViewTestCase):
|
||||
'group': tenant_groups[1].pk,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class ContactGroupTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ContactGroup
|
||||
brief_fields = ['_depth', 'contact_count', 'display', 'id', 'name', 'slug', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
parent_contact_groups = (
|
||||
ContactGroup.objects.create(name='Parent Contact Group 1', slug='parent-contact-group-1'),
|
||||
ContactGroup.objects.create(name='Parent Contact Group 2', slug='parent-contact-group-2'),
|
||||
)
|
||||
|
||||
ContactGroup.objects.create(name='Contact Group 1', slug='contact-group-1', parent=parent_contact_groups[0])
|
||||
ContactGroup.objects.create(name='Contact Group 2', slug='contact-group-2', parent=parent_contact_groups[0])
|
||||
ContactGroup.objects.create(name='Contact Group 3', slug='contact-group-3', parent=parent_contact_groups[0])
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'name': 'Contact Group 4',
|
||||
'slug': 'contact-group-4',
|
||||
'parent': parent_contact_groups[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Contact Group 5',
|
||||
'slug': 'contact-group-5',
|
||||
'parent': parent_contact_groups[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Contact Group 6',
|
||||
'slug': 'contact-group-6',
|
||||
'parent': parent_contact_groups[1].pk,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class ContactRoleTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ContactRole
|
||||
brief_fields = ['display', 'id', 'name', 'slug', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'name': 'Contact Role 4',
|
||||
'slug': 'contact-role-4',
|
||||
},
|
||||
{
|
||||
'name': 'Contact Role 5',
|
||||
'slug': 'contact-role-5',
|
||||
},
|
||||
{
|
||||
'name': 'Contact Role 6',
|
||||
'slug': 'contact-role-6',
|
||||
},
|
||||
]
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_roles = (
|
||||
ContactRole(name='Contact Role 1', slug='contact-role-1'),
|
||||
ContactRole(name='Contact Role 2', slug='contact-role-2'),
|
||||
ContactRole(name='Contact Role 3', slug='contact-role-3'),
|
||||
)
|
||||
ContactRole.objects.bulk_create(contact_roles)
|
||||
|
||||
|
||||
class ContactTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Contact
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'group': None,
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_groups = (
|
||||
ContactGroup.objects.create(name='Contact Group 1', slug='contact-group-1'),
|
||||
ContactGroup.objects.create(name='Contact Group 2', slug='contact-group-2'),
|
||||
)
|
||||
|
||||
contacts = (
|
||||
Contact(name='Contact 1', group=contact_groups[0]),
|
||||
Contact(name='Contact 2', group=contact_groups[0]),
|
||||
Contact(name='Contact 3', group=contact_groups[0]),
|
||||
)
|
||||
Contact.objects.bulk_create(contacts)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'name': 'Contact 4',
|
||||
'group': contact_groups[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Contact 5',
|
||||
'group': contact_groups[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Contact 6',
|
||||
'group': contact_groups[1].pk,
|
||||
},
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from tenancy.filtersets import *
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.testing import ChangeLoggedFilterSetTests
|
||||
|
||||
|
||||
@ -84,3 +84,103 @@ class TenantTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'group': [group[0].slug, group[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ContactGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ContactGroup.objects.all()
|
||||
filterset = ContactGroupFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
parent_contact_groups = (
|
||||
ContactGroup(name='Parent Contact Group 1', slug='parent-contact-group-1'),
|
||||
ContactGroup(name='Parent Contact Group 2', slug='parent-contact-group-2'),
|
||||
ContactGroup(name='Parent Contact Group 3', slug='parent-contact-group-3'),
|
||||
)
|
||||
for contactgroup in parent_contact_groups:
|
||||
contactgroup.save()
|
||||
|
||||
contact_groups = (
|
||||
ContactGroup(name='Contact Group 1', slug='contact-group-1', parent=parent_contact_groups[0], description='A'),
|
||||
ContactGroup(name='Contact Group 2', slug='contact-group-2', parent=parent_contact_groups[1], description='B'),
|
||||
ContactGroup(name='Contact Group 3', slug='contact-group-3', parent=parent_contact_groups[2], description='C'),
|
||||
)
|
||||
for contactgroup in contact_groups:
|
||||
contactgroup.save()
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Contact Group 1', 'Contact Group 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['contact-group-1', 'contact-group-2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_parent(self):
|
||||
parent_groups = ContactGroup.objects.filter(parent__isnull=True)[:2]
|
||||
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ContactRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ContactRole.objects.all()
|
||||
filterset = ContactRoleFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_roles = (
|
||||
ContactRole(name='Contact Role 1', slug='contact-role-1'),
|
||||
ContactRole(name='Contact Role 2', slug='contact-role-2'),
|
||||
ContactRole(name='Contact Role 3', slug='contact-role-3'),
|
||||
)
|
||||
ContactRole.objects.bulk_create(contact_roles)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Contact Role 1', 'Contact Role 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['contact-role-1', 'contact-role-2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ContactTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = Contact.objects.all()
|
||||
filterset = ContactFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_groups = (
|
||||
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
||||
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
||||
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
||||
)
|
||||
for contactgroup in contact_groups:
|
||||
contactgroup.save()
|
||||
|
||||
contacts = (
|
||||
Contact(name='Contact 1', group=contact_groups[0]),
|
||||
Contact(name='Contact 2', group=contact_groups[1]),
|
||||
Contact(name='Contact 3', group=contact_groups[2]),
|
||||
)
|
||||
Contact.objects.bulk_create(contacts)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Contact 1', 'Contact 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_group(self):
|
||||
group = ContactGroup.objects.all()[:2]
|
||||
params = {'group_id': [group[0].pk, group[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'group': [group[0].slug, group[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from tenancy.models import *
|
||||
from utilities.testing import ViewTestCases, create_tags
|
||||
|
||||
|
||||
@ -74,3 +74,105 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
cls.bulk_edit_data = {
|
||||
'group': tenant_groups[1].pk,
|
||||
}
|
||||
|
||||
|
||||
class ContactGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = ContactGroup
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_groups = (
|
||||
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
||||
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
||||
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
||||
)
|
||||
for tenanantgroup in contact_groups:
|
||||
tenanantgroup.save()
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Contact Group X',
|
||||
'slug': 'contact-group-x',
|
||||
'description': 'A new contact group',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,slug,description",
|
||||
"Contact Group 4,contact-group-4,Fourth contact group",
|
||||
"Contact Group 5,contact-group-5,Fifth contact group",
|
||||
"Contact Group 6,contact-group-6,Sixth contact group",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
||||
class ContactRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = ContactRole
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
ContactRole.objects.bulk_create([
|
||||
ContactRole(name='Contact Role 1', slug='contact-role-1'),
|
||||
ContactRole(name='Contact Role 2', slug='contact-role-2'),
|
||||
ContactRole(name='Contact Role 3', slug='contact-role-3'),
|
||||
])
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Devie Role X',
|
||||
'slug': 'contact-role-x',
|
||||
'description': 'New contact role',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,slug",
|
||||
"Contact Role 4,contact-role-4",
|
||||
"Contact Role 5,contact-role-5",
|
||||
"Contact Role 6,contact-role-6",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
||||
class ContactTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = Contact
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
contact_groups = (
|
||||
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
||||
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
||||
)
|
||||
for contactgroup in contact_groups:
|
||||
contactgroup.save()
|
||||
|
||||
Contact.objects.bulk_create([
|
||||
Contact(name='Contact 1', group=contact_groups[0]),
|
||||
Contact(name='Contact 2', group=contact_groups[0]),
|
||||
Contact(name='Contact 3', group=contact_groups[0]),
|
||||
])
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Contact X',
|
||||
'group': contact_groups[1].pk,
|
||||
'comments': 'Some comments',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,slug",
|
||||
"Contact 4,contact-4",
|
||||
"Contact 5,contact-5",
|
||||
"Contact 6,contact-6",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'group': contact_groups[1].pk,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ from django.urls import path
|
||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.views import SlugRedirectView
|
||||
from . import views
|
||||
from .models import Tenant, TenantGroup
|
||||
from .models import *
|
||||
|
||||
app_name = 'tenancy'
|
||||
urlpatterns = [
|
||||
@ -32,4 +32,39 @@ urlpatterns = [
|
||||
path('tenants/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}),
|
||||
path('tenants/<int:pk>/journal/', ObjectJournalView.as_view(), name='tenant_journal', kwargs={'model': Tenant}),
|
||||
|
||||
# Contact groups
|
||||
path('contact-groups/', views.ContactGroupListView.as_view(), name='contactgroup_list'),
|
||||
path('contact-groups/add/', views.ContactGroupEditView.as_view(), name='contactgroup_add'),
|
||||
path('contact-groups/import/', views.ContactGroupBulkImportView.as_view(), name='contactgroup_import'),
|
||||
path('contact-groups/edit/', views.ContactGroupBulkEditView.as_view(), name='contactgroup_bulk_edit'),
|
||||
path('contact-groups/delete/', views.ContactGroupBulkDeleteView.as_view(), name='contactgroup_bulk_delete'),
|
||||
path('contact-groups/<int:pk>/', views.ContactGroupView.as_view(), name='contactgroup'),
|
||||
path('contact-groups/<int:pk>/edit/', views.ContactGroupEditView.as_view(), name='contactgroup_edit'),
|
||||
path('contact-groups/<int:pk>/delete/', views.ContactGroupDeleteView.as_view(), name='contactgroup_delete'),
|
||||
path('contact-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contactgroup_changelog', kwargs={'model': ContactGroup}),
|
||||
|
||||
# Contact roles
|
||||
path('contact-roles/', views.ContactRoleListView.as_view(), name='contactrole_list'),
|
||||
path('contact-roles/add/', views.ContactRoleEditView.as_view(), name='contactrole_add'),
|
||||
path('contact-roles/import/', views.ContactRoleBulkImportView.as_view(), name='contactrole_import'),
|
||||
path('contact-roles/edit/', views.ContactRoleBulkEditView.as_view(), name='contactrole_bulk_edit'),
|
||||
path('contact-roles/delete/', views.ContactRoleBulkDeleteView.as_view(), name='contactrole_bulk_delete'),
|
||||
path('contact-roles/<int:pk>/', views.ContactRoleView.as_view(), name='contactrole'),
|
||||
path('contact-roles/<int:pk>/edit/', views.ContactRoleEditView.as_view(), name='contactrole_edit'),
|
||||
path('contact-roles/<int:pk>/delete/', views.ContactRoleDeleteView.as_view(), name='contactrole_delete'),
|
||||
path('contact-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contactrole_changelog', kwargs={'model': ContactRole}),
|
||||
|
||||
# Contacts
|
||||
path('contacts/', views.ContactListView.as_view(), name='contact_list'),
|
||||
path('contacts/add/', views.ContactEditView.as_view(), name='contact_add'),
|
||||
path('contacts/import/', views.ContactBulkImportView.as_view(), name='contact_import'),
|
||||
path('contacts/edit/', views.ContactBulkEditView.as_view(), name='contact_bulk_edit'),
|
||||
path('contacts/delete/', views.ContactBulkDeleteView.as_view(), name='contact_bulk_delete'),
|
||||
path('contacts/<int:pk>/', views.ContactView.as_view(), name='contact'),
|
||||
path('contacts/<slug:slug>/', SlugRedirectView.as_view(), kwargs={'model': Contact}),
|
||||
path('contacts/<int:pk>/edit/', views.ContactEditView.as_view(), name='contact_edit'),
|
||||
path('contacts/<int:pk>/delete/', views.ContactDeleteView.as_view(), name='contact_delete'),
|
||||
path('contacts/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contact_changelog', kwargs={'model': Contact}),
|
||||
path('contacts/<int:pk>/journal/', ObjectJournalView.as_view(), name='contact_journal', kwargs={'model': Contact}),
|
||||
|
||||
]
|
||||
|
@ -3,9 +3,10 @@ from dcim.models import Site, Rack, Device, RackReservation
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
||||
from netbox.views import generic
|
||||
from utilities.tables import paginate_table
|
||||
from utilities.utils import count_related
|
||||
from virtualization.models import VirtualMachine, Cluster
|
||||
from . import filtersets, forms, tables
|
||||
from .models import Tenant, TenantGroup
|
||||
from .models import *
|
||||
|
||||
|
||||
#
|
||||
@ -140,3 +141,171 @@ class TenantBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = Tenant.objects.prefetch_related('group')
|
||||
filterset = filtersets.TenantFilterSet
|
||||
table = tables.TenantTable
|
||||
|
||||
|
||||
#
|
||||
# Contact groups
|
||||
#
|
||||
|
||||
class ContactGroupListView(generic.ObjectListView):
|
||||
queryset = ContactGroup.objects.add_related_count(
|
||||
ContactGroup.objects.all(),
|
||||
Contact,
|
||||
'group',
|
||||
'contact_count',
|
||||
cumulative=True
|
||||
)
|
||||
filterset = filtersets.ContactGroupFilterSet
|
||||
filterset_form = forms.ContactGroupFilterForm
|
||||
table = tables.ContactGroupTable
|
||||
|
||||
|
||||
class ContactGroupView(generic.ObjectView):
|
||||
queryset = ContactGroup.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
contacts = Contact.objects.restrict(request.user, 'view').filter(
|
||||
group=instance
|
||||
)
|
||||
contacts_table = tables.ContactTable(contacts, exclude=('group',))
|
||||
paginate_table(contacts_table, request)
|
||||
|
||||
return {
|
||||
'contacts_table': contacts_table,
|
||||
}
|
||||
|
||||
|
||||
class ContactGroupEditView(generic.ObjectEditView):
|
||||
queryset = ContactGroup.objects.all()
|
||||
model_form = forms.ContactGroupForm
|
||||
|
||||
|
||||
class ContactGroupDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ContactGroup.objects.all()
|
||||
|
||||
|
||||
class ContactGroupBulkImportView(generic.BulkImportView):
|
||||
queryset = ContactGroup.objects.all()
|
||||
model_form = forms.ContactGroupCSVForm
|
||||
table = tables.ContactGroupTable
|
||||
|
||||
|
||||
class ContactGroupBulkEditView(generic.BulkEditView):
|
||||
queryset = ContactGroup.objects.add_related_count(
|
||||
ContactGroup.objects.all(),
|
||||
Contact,
|
||||
'group',
|
||||
'contact_count',
|
||||
cumulative=True
|
||||
)
|
||||
filterset = filtersets.ContactGroupFilterSet
|
||||
table = tables.ContactGroupTable
|
||||
form = forms.ContactGroupBulkEditForm
|
||||
|
||||
|
||||
class ContactGroupBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ContactGroup.objects.add_related_count(
|
||||
ContactGroup.objects.all(),
|
||||
Contact,
|
||||
'group',
|
||||
'contact_count',
|
||||
cumulative=True
|
||||
)
|
||||
table = tables.ContactGroupTable
|
||||
|
||||
|
||||
#
|
||||
# Contact roles
|
||||
#
|
||||
|
||||
class ContactRoleListView(generic.ObjectListView):
|
||||
queryset = ContactRole.objects.all()
|
||||
filterset = filtersets.ContactRoleFilterSet
|
||||
filterset_form = forms.ContactRoleFilterForm
|
||||
table = tables.ContactRoleTable
|
||||
|
||||
|
||||
class ContactRoleView(generic.ObjectView):
|
||||
queryset = ContactRole.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
contact_assignments = ContactAssignment.objects.restrict(request.user, 'view').filter(
|
||||
role=instance
|
||||
)
|
||||
contacts_table = tables.ContactAssignmentTable(contact_assignments)
|
||||
paginate_table(contacts_table, request)
|
||||
|
||||
return {
|
||||
'contacts_table': contacts_table,
|
||||
'contact_count': ContactAssignment.objects.filter(role=instance).count(),
|
||||
}
|
||||
|
||||
|
||||
class ContactRoleEditView(generic.ObjectEditView):
|
||||
queryset = ContactRole.objects.all()
|
||||
model_form = forms.ContactRoleForm
|
||||
|
||||
|
||||
class ContactRoleDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ContactRole.objects.all()
|
||||
|
||||
|
||||
class ContactRoleBulkImportView(generic.BulkImportView):
|
||||
queryset = ContactRole.objects.all()
|
||||
model_form = forms.ContactRoleCSVForm
|
||||
table = tables.ContactRoleTable
|
||||
|
||||
|
||||
class ContactRoleBulkEditView(generic.BulkEditView):
|
||||
queryset = ContactRole.objects.all()
|
||||
filterset = filtersets.ContactRoleFilterSet
|
||||
table = tables.ContactRoleTable
|
||||
form = forms.ContactRoleBulkEditForm
|
||||
|
||||
|
||||
class ContactRoleBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ContactRole.objects.all()
|
||||
table = tables.ContactRoleTable
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactListView(generic.ObjectListView):
|
||||
queryset = Contact.objects.all()
|
||||
filterset = filtersets.ContactFilterSet
|
||||
filterset_form = forms.ContactFilterForm
|
||||
table = tables.ContactTable
|
||||
|
||||
|
||||
class ContactView(generic.ObjectView):
|
||||
queryset = Contact.objects.all()
|
||||
|
||||
|
||||
class ContactEditView(generic.ObjectEditView):
|
||||
queryset = Contact.objects.all()
|
||||
model_form = forms.ContactForm
|
||||
|
||||
|
||||
class ContactDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Contact.objects.all()
|
||||
|
||||
|
||||
class ContactBulkImportView(generic.BulkImportView):
|
||||
queryset = Contact.objects.all()
|
||||
model_form = forms.ContactCSVForm
|
||||
table = tables.ContactTable
|
||||
|
||||
|
||||
class ContactBulkEditView(generic.BulkEditView):
|
||||
queryset = Contact.objects.prefetch_related('group')
|
||||
filterset = filtersets.ContactFilterSet
|
||||
table = tables.ContactTable
|
||||
form = forms.ContactBulkEditForm
|
||||
|
||||
|
||||
class ContactBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = Contact.objects.prefetch_related('group')
|
||||
filterset = filtersets.ContactFilterSet
|
||||
table = tables.ContactTable
|
||||
|
Reference in New Issue
Block a user