diff --git a/netbox/templates/tenancy/attrs/email.html b/netbox/templates/tenancy/attrs/email.html
new file mode 100644
index 000000000..2ee254a64
--- /dev/null
+++ b/netbox/templates/tenancy/attrs/email.html
@@ -0,0 +1 @@
+{{ value }}
diff --git a/netbox/templates/tenancy/attrs/link.html b/netbox/templates/tenancy/attrs/link.html
new file mode 100644
index 000000000..30b750ba8
--- /dev/null
+++ b/netbox/templates/tenancy/attrs/link.html
@@ -0,0 +1 @@
+{{ value }}
diff --git a/netbox/templates/tenancy/attrs/phone.html b/netbox/templates/tenancy/attrs/phone.html
new file mode 100644
index 000000000..93bcde0c6
--- /dev/null
+++ b/netbox/templates/tenancy/attrs/phone.html
@@ -0,0 +1 @@
+{{ value }}
diff --git a/netbox/templates/tenancy/contact.html b/netbox/templates/tenancy/contact.html
index 790e08489..f15e1d050 100644
--- a/netbox/templates/tenancy/contact.html
+++ b/netbox/templates/tenancy/contact.html
@@ -1,100 +1 @@
{% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-{% block breadcrumbs %}
- {{ block.super }}
- {% if object.group %}
-
{{ object.group }}
- {% endif %}
-{% endblock breadcrumbs %}
-
-{% block content %}
-
-
-
-
-
-
- | {% trans "Groups" %} |
-
- {% if object.groups.all|length > 0 %}
-
- {% for group in object.groups.all %}
- - {{ group|linkify|placeholder }}
- {% endfor %}
-
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
- |
-
-
- | {% trans "Name" %} |
- {{ object.name }} |
-
-
- | {% trans "Title" %} |
- {{ object.title|placeholder }} |
-
-
- | {% trans "Phone" %} |
-
- {% if object.phone %}
- {{ object.phone }}
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
- |
-
-
- | {% trans "Email" %} |
-
- {% if object.email %}
- {{ object.email }}
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
- |
-
-
- | {% trans "Address" %} |
- {{ object.address|linebreaksbr|placeholder }} |
-
-
- | {% trans "Link" %} |
-
- {% if object.link %}
- {{ object.link }}
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
- |
-
-
- | {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
-
- {% include 'inc/panels/tags.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'inc/panels/comments.html' %}
- {% include 'inc/panels/custom_fields.html' %}
- {% plugin_right_page object %}
-
-
-
-
-
-
- {% htmx_table 'tenancy:contactassignment_list' contact_id=object.pk %}
-
- {% plugin_full_width_page object %}
-
-
-{% endblock %}
diff --git a/netbox/templates/tenancy/contactgroup.html b/netbox/templates/tenancy/contactgroup.html
index bdcf675dd..20f0e6ef2 100644
--- a/netbox/templates/tenancy/contactgroup.html
+++ b/netbox/templates/tenancy/contactgroup.html
@@ -1,60 +1,8 @@
{% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
{% block breadcrumbs %}
{{ block.super }}
- {% for contactgroup in object.get_ancestors %}
- {{ contactgroup }}
+ {% for ancestor in object.get_ancestors %}
+ {{ ancestor }}
{% endfor %}
{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
- | {% trans "Name" %} |
- {{ object.name }} |
-
-
- | {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
- | {% trans "Parent" %} |
- {{ object.parent|linkify|placeholder }} |
-
-
-
- {% include 'inc/panels/tags.html' %}
- {% include 'inc/panels/comments.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'inc/panels/related_objects.html' %}
- {% include 'inc/panels/custom_fields.html' %}
- {% plugin_right_page object %}
-
-
-
-
-
- {% htmx_table 'tenancy:contactgroup_list' parent_id=object.pk %}
-
- {% plugin_full_width_page object %}
-
-{% endblock %}
diff --git a/netbox/templates/tenancy/contactrole.html b/netbox/templates/tenancy/contactrole.html
index 44e004d21..f15e1d050 100644
--- a/netbox/templates/tenancy/contactrole.html
+++ b/netbox/templates/tenancy/contactrole.html
@@ -1,42 +1 @@
{% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-{% block breadcrumbs %}
- {% trans "Contact Roles" %}
-{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
- | {% trans "Name" %} |
- {{ object.name }} |
-
-
- | {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
-
- {% include 'inc/panels/tags.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'inc/panels/related_objects.html' %}
- {% include 'inc/panels/comments.html' %}
- {% include 'inc/panels/custom_fields.html' %}
- {% plugin_right_page object %}
-
-
-
-
- {% plugin_full_width_page object %}
-
-
-{% endblock %}
diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html
index a3a977697..09f401fa8 100644
--- a/netbox/templates/tenancy/tenant.html
+++ b/netbox/templates/tenancy/tenant.html
@@ -1,44 +1,11 @@
{% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load i18n %}
{% block breadcrumbs %}
{{ block.super }}
{% if object.group %}
+ {% for group in object.group.get_ancestors %}
+ {{ group }}
+ {% endfor %}
{{ object.group }}
{% endif %}
-{% endblock breadcrumbs %}
-
-{% block content %}
-
-
-
-
-
-
- | {% trans "Group" %} |
- {{ object.group|linkify|placeholder }} |
-
-
- | {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
-
- {% include 'inc/panels/custom_fields.html' %}
- {% include 'inc/panels/tags.html' %}
- {% include 'inc/panels/comments.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'inc/panels/related_objects.html' %}
- {% plugin_right_page object %}
-
-
-
-
- {% plugin_full_width_page object %}
-
-
{% endblock %}
diff --git a/netbox/templates/tenancy/tenantgroup.html b/netbox/templates/tenancy/tenantgroup.html
index 5ca3ba554..f4e32b697 100644
--- a/netbox/templates/tenancy/tenantgroup.html
+++ b/netbox/templates/tenancy/tenantgroup.html
@@ -1,13 +1,10 @@
{% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
{% load i18n %}
{% block breadcrumbs %}
{{ block.super }}
- {% for tenantgroup in object.get_ancestors %}
- {{ tenantgroup }}
+ {% for ancestor in object.get_ancestors %}
+ {{ ancestor }}
{% endfor %}
{% endblock %}
@@ -18,53 +15,3 @@
{% endif %}
{% endblock extra_controls %}
-
-{% block content %}
-
-
-
-
-
-
- | {% trans "Name" %} |
- {{ object.name }} |
-
-
- | {% trans "Description" %} |
- {{ object.description|placeholder }} |
-
-
- | {% trans "Parent" %} |
- {{ object.parent|linkify|placeholder }} |
-
-
-
- {% include 'inc/panels/tags.html' %}
- {% include 'inc/panels/comments.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'inc/panels/related_objects.html' %}
- {% include 'inc/panels/custom_fields.html' %}
- {% plugin_right_page object %}
-
-
-
-
-
-
- {% htmx_table 'tenancy:tenantgroup_list' parent_id=object.pk %}
-
- {% plugin_full_width_page object %}
-
-
-{% endblock %}
diff --git a/netbox/tenancy/ui/__init__.py b/netbox/tenancy/ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/netbox/tenancy/ui/panels.py b/netbox/tenancy/ui/panels.py
new file mode 100644
index 000000000..75d512385
--- /dev/null
+++ b/netbox/tenancy/ui/panels.py
@@ -0,0 +1,19 @@
+from django.utils.translation import gettext_lazy as _
+
+from netbox.ui import attrs, panels
+
+
+class TenantPanel(panels.ObjectAttributesPanel):
+ group = attrs.RelatedObjectAttr('group', linkify=True)
+ description = attrs.TextAttr('description')
+
+
+class ContactPanel(panels.ObjectAttributesPanel):
+ groups = attrs.RelatedObjectListAttr('groups', linkify=True, label=_('Groups'))
+ name = attrs.TextAttr('name')
+ title = attrs.TextAttr('title')
+ phone = attrs.TemplatedAttr('phone', label=_('Phone'), template_name='tenancy/attrs/phone.html')
+ email = attrs.TemplatedAttr('email', label=_('Email'), template_name='tenancy/attrs/email.html')
+ address = attrs.AddressAttr('address', map_url=False)
+ link = attrs.TemplatedAttr('link', label=_('Link'), template_name='tenancy/attrs/link.html')
+ description = attrs.TextAttr('description')
diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py
index 3e427a3ce..26b0ac5ab 100644
--- a/netbox/tenancy/views.py
+++ b/netbox/tenancy/views.py
@@ -1,13 +1,24 @@
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
+from django.utils.translation import gettext_lazy as _
+from extras.ui.panels import CustomFieldsPanel, TagsPanel
from netbox.object_actions import BulkDelete, BulkEdit, BulkExport, BulkImport
+from netbox.ui import actions, layout
+from netbox.ui.panels import (
+ CommentsPanel,
+ NestedGroupObjectPanel,
+ ObjectsTablePanel,
+ OrganizationalObjectPanel,
+ RelatedObjectsPanel,
+)
from netbox.views import generic
from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
+from .ui import panels
#
# Tenant groups
@@ -31,6 +42,31 @@ class TenantGroupListView(generic.ObjectListView):
@register_model_view(TenantGroup)
class TenantGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = TenantGroup.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ NestedGroupObjectPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ CustomFieldsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ 'tenancy.tenantgroup',
+ filters={'parent_id': lambda ctx: ctx['object'].pk},
+ title=_('Child Groups'),
+ actions=[
+ actions.AddObject(
+ 'tenancy.tenantgroup',
+ url_params={'parent': lambda ctx: ctx['object'].pk},
+ label=_('Add Tenant Group'),
+ ),
+ ],
+ ),
+ ],
+ )
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
@@ -106,6 +142,17 @@ class TenantListView(generic.ObjectListView):
@register_model_view(Tenant)
class TenantView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Tenant.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.TenantPanel(),
+ CustomFieldsPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
return {
@@ -173,6 +220,31 @@ class ContactGroupListView(generic.ObjectListView):
@register_model_view(ContactGroup)
class ContactGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ContactGroup.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ NestedGroupObjectPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ CustomFieldsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ 'tenancy.contactgroup',
+ filters={'parent_id': lambda ctx: ctx['object'].pk},
+ title=_('Child Groups'),
+ actions=[
+ actions.AddObject(
+ 'tenancy.contactgroup',
+ url_params={'parent': lambda ctx: ctx['object'].pk},
+ label=_('Add Contact Group'),
+ ),
+ ],
+ ),
+ ],
+ )
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
@@ -254,6 +326,17 @@ class ContactRoleListView(generic.ObjectListView):
@register_model_view(ContactRole)
class ContactRoleView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ContactRole.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ OrganizationalObjectPanel(),
+ TagsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ CommentsPanel(),
+ CustomFieldsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
return {
@@ -317,6 +400,23 @@ class ContactListView(generic.ObjectListView):
@register_model_view(Contact)
class ContactView(generic.ObjectView):
queryset = Contact.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.ContactPanel(),
+ TagsPanel(),
+ ],
+ right_panels=[
+ CommentsPanel(),
+ CustomFieldsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ 'tenancy.contactassignment',
+ filters={'contact_id': lambda ctx: ctx['object'].pk},
+ title=_('Assignments'),
+ ),
+ ],
+ )
@register_model_view(Contact, 'add', detail=False)