#20923 - Convert tenancy to new UI layout (#21745)

This commit is contained in:
Arthur Hanson
2026-03-26 09:16:31 -07:00
committed by GitHub
parent b929e1aa1b
commit ea756b29e9
11 changed files with 129 additions and 285 deletions

View File

@@ -0,0 +1 @@
<a href="mailto:{{ value }}">{{ value }}</a>

View File

@@ -0,0 +1 @@
<a href="{{ value }}">{{ value }}</a>

View File

@@ -0,0 +1 @@
<a href="tel:{{ value }}">{{ value }}</a>

View File

@@ -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 %}
<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-12 col-md-7">
<div class="card">
<h2 class="card-header">{% trans "Contact" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Groups" %}</th>
<td>
{% if object.groups.all|length > 0 %}
<ol class="list-unstyled mb-0">
{% for group in object.groups.all %}
<li>{{ group|linkify|placeholder }}</li>
{% endfor %}
</ol>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Title" %}</th>
<td>{{ object.title|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Phone" %}</th>
<td>
{% if object.phone %}
<a href="tel:{{ object.phone }}">{{ object.phone }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Email" %}</th>
<td>
{% if object.email %}
<a href="mailto:{{ object.email }}">{{ object.email }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Address" %}</th>
<td>{{ object.address|linebreaksbr|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Link" %}</th>
<td>
{% if object.link %}
<a href="{{ object.link }}">{{ object.link }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'inc/panels/tags.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-5">
{% include 'inc/panels/comments.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h2 class="card-header">{% trans "Assignments" %}</h2>
{% htmx_table 'tenancy:contactassignment_list' contact_id=object.pk %}
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -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 %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:contactgroup_list' %}?parent_id={{ contactgroup.pk }}">{{ contactgroup }}</a></li>
{% for ancestor in object.get_ancestors %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:contactgroup_list' %}?parent_id={{ ancestor.pk }}">{{ ancestor }}</a></li>
{% endfor %}
{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Contact Group" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Parent" %}</th>
<td>{{ object.parent|linkify|placeholder }}</td>
</tr>
</table>
</div>
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-12 col-md-6">
{% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="col col-md-12">
<div class="card">
<h2 class="card-header">
{% trans "Child Groups" %}
{% if perms.tenancy.add_contactgroup %}
<div class="card-actions">
<a href="{% url 'tenancy:contactgroup_add' %}?parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Contact Group" %}
</a>
</div>
{% endif %}
</h2>
{% htmx_table 'tenancy:contactgroup_list' parent_id=object.pk %}
</div>
{% plugin_full_width_page object %}
</div>
{% endblock %}

View File

@@ -1,42 +1 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:contactrole_list' %}">{% trans "Contact Roles" %}</a></li>
{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Contact Role" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'inc/panels/tags.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-12 col-md-6">
{% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/comments.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row mb-3">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -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 %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:tenant_list' %}?group_id={{ group.pk }}">{{ group }}</a></li>
{% endfor %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.group.pk }}">{{ object.group }}</a></li>
{% endif %}
{% endblock breadcrumbs %}
{% block content %}
<div class="row">
<div class="col col-12 col-md-7">
<div class="card">
<h2 class="card-header">{% trans "Tenant" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Group" %}</th>
<td>{{ object.group|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-5">
{% include 'inc/panels/related_objects.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -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 %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:tenantgroup_list' %}?parent_id={{ tenantgroup.pk }}">{{ tenantgroup }}</a></li>
{% for ancestor in object.get_ancestors %}
<li class="breadcrumb-item"><a href="{% url 'tenancy:tenantgroup_list' %}?parent_id={{ ancestor.pk }}">{{ ancestor }}</a></li>
{% endfor %}
{% endblock %}
@@ -18,53 +15,3 @@
</a>
{% endif %}
{% endblock extra_controls %}
{% block content %}
<div class="row mb-3">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Tenant Group" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Parent" %}</th>
<td>{{ object.parent|linkify|placeholder }}</td>
</tr>
</table>
</div>
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-12 col-md-6">
{% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h2 class="card-header">
{% trans "Child Groups" %}
{% if perms.tenancy.add_tenantgroup %}
<div class="card-actions">
<a href="{% url 'tenancy:tenantgroup_add' %}?parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Tenant Group" %}
</a>
</div>
{% endif %}
</h2>
{% htmx_table 'tenancy:tenantgroup_list' parent_id=object.pk %}
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

View File

@@ -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')

View File

@@ -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)