Compare commits

..

2 Commits

Author SHA1 Message Date
Arthur
86f6de40d2 add docs and tests 2026-03-10 08:58:07 -07:00
Arthur
83c6149e49 #21114 Allow specifying exclude directories for Data Sources 2026-03-10 08:46:47 -07:00
30 changed files with 779 additions and 500 deletions

View File

@@ -30,39 +30,9 @@ jobs:
with:
fetch-depth: 1
# Workaround for claude-code-action bug with fork PRs: The action fetches by branch name
# (git fetch origin --depth=N <branch>), but fork PR branches don't exist on origin.
# Fix: redirect origin to the fork's URL so the action can fetch the branch directly.
- name: Configure git remote for fork PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Determine PR number based on event type
if [ "${{ github.event_name }}" = "issue_comment" ]; then
PR_NUMBER="${{ github.event.issue.number }}"
elif [ "${{ github.event_name }}" = "pull_request_review_comment" ] || [ "${{ github.event_name }}" = "pull_request_review" ]; then
PR_NUMBER="${{ github.event.pull_request.number }}"
else
exit 0 # issues event — no PR branch to worry about
fi
# Fetch fork info in one API call; silently skip if this is not a PR
PR_INFO=$(gh pr view "${PR_NUMBER}" --json isCrossRepository,headRepositoryOwner,headRepository 2>/dev/null || echo "")
if [ -z "$PR_INFO" ]; then
exit 0
fi
IS_FORK=$(echo "$PR_INFO" | jq -r '.isCrossRepository')
if [ "$IS_FORK" = "true" ]; then
FORK_OWNER=$(echo "$PR_INFO" | jq -r '.headRepositoryOwner.login')
FORK_REPO=$(echo "$PR_INFO" | jq -r '.headRepository.name')
echo "Fork PR detected from ${FORK_OWNER}/${FORK_REPO}: updating origin to fork URL"
git remote set-url origin "https://github.com/${FORK_OWNER}/${FORK_REPO}.git"
fi
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@e763fe78de2db7389e04818a00b5ff8ba13d1360 # v1
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

View File

@@ -11,14 +11,14 @@ permissions:
pull-requests: write
discussions: write
concurrency:
group: lock-threads
jobs:
lock:
if: github.repository == 'netbox-community/netbox'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v6.0.0
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
with:
issue-inactive-days: 90
pr-inactive-days: 30
discussion-inactive-days: 180
issue-lock-reason: 'resolved'

View File

@@ -36,13 +36,16 @@ If false, synchronization will be disabled.
### Ignore Rules
A set of rules (one per line) identifying filenames to ignore during synchronization. Some examples are provided below. See Python's [`fnmatch()` documentation](https://docs.python.org/3/library/fnmatch.html) for a complete reference.
A set of rules (one per line) identifying files or paths to ignore during synchronization. Rules are matched against both the full relative path (e.g. `subdir/file.txt`) and the bare filename, so path-based patterns can be used to exclude entire directories. Some examples are provided below. See Python's [`fnmatch()` documentation](https://docs.python.org/3/library/fnmatch.html) for a complete reference.
| Rule | Description |
|----------------|------------------------------------------|
| `README` | Ignore any files named `README` |
| `*.txt` | Ignore any files with a `.txt` extension |
| `data???.json` | Ignore e.g. `data123.json` |
| Rule | Description |
|-----------------------|------------------------------------------------------|
| `README` | Ignore any files named `README` |
| `*.txt` | Ignore any files with a `.txt` extension |
| `data???.json` | Ignore e.g. `data123.json` |
| `subdir/*` | Ignore all files within `subdir/` |
| `subdir/*/*` | Ignore all files one level deep within `subdir/` |
| `*/dev/*` | Ignore files inside any directory named `dev/` |
### Sync Interval

View File

@@ -43,7 +43,7 @@ class DataSourceForm(PrimaryModelForm):
attrs={
'rows': 5,
'class': 'font-monospace',
'placeholder': '.cache\n*.txt'
'placeholder': '.cache\n*.txt\nsubdir/*'
}
),
}

View File

@@ -69,7 +69,7 @@ class DataSource(JobsMixin, PrimaryModel):
ignore_rules = models.TextField(
verbose_name=_('ignore rules'),
blank=True,
help_text=_("Patterns (one per line) matching files to ignore when syncing")
help_text=_("Patterns (one per line) matching files or paths to ignore when syncing")
)
parameters = models.JSONField(
verbose_name=_('parameters'),
@@ -258,21 +258,22 @@ class DataSource(JobsMixin, PrimaryModel):
if path.startswith('.'):
continue
for file_name in file_names:
if not self._ignore(file_name):
paths.add(os.path.join(path, file_name))
file_path = os.path.join(path, file_name)
if not self._ignore(file_path):
paths.add(file_path)
logger.debug(f"Found {len(paths)} files")
return paths
def _ignore(self, filename):
def _ignore(self, file_path):
"""
Returns a boolean indicating whether the file should be ignored per the DataSource's configured
ignore rules.
ignore rules. file_path is the full relative path (e.g. "subdir/file.txt").
"""
if filename.startswith('.'):
if os.path.basename(file_path).startswith('.'):
return True
for rule in self.ignore_rules.splitlines():
if fnmatchcase(filename, rule):
if fnmatchcase(file_path, rule) or fnmatchcase(os.path.basename(file_path), rule):
return True
return False

View File

@@ -10,6 +10,26 @@ from dcim.models import Device, Location, Site
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
class DataSourceIgnoreRulesTestCase(TestCase):
def test_no_ignore_rules(self):
ds = DataSource(ignore_rules='')
self.assertFalse(ds._ignore('README.md'))
self.assertFalse(ds._ignore('subdir/file.py'))
def test_ignore_by_filename(self):
ds = DataSource(ignore_rules='*.txt')
self.assertTrue(ds._ignore('notes.txt'))
self.assertTrue(ds._ignore('subdir/notes.txt'))
self.assertFalse(ds._ignore('notes.py'))
def test_ignore_by_subdirectory(self):
ds = DataSource(ignore_rules='dev/*')
self.assertTrue(ds._ignore('dev/README.md'))
self.assertTrue(ds._ignore('dev/script.py'))
self.assertFalse(ds._ignore('prod/script.py'))
class DataSourceChangeLoggingTestCase(TestCase):
def test_password_added_on_create(self):

View File

@@ -405,7 +405,6 @@ class DeviceViewSet(
NetBoxModelViewSet
):
queryset = Device.objects.prefetch_related(
'device_type__manufacturer', # Referenced by Device.__str__() for unnamed devices
'parent_bay', # Referenced by DeviceSerializer.get_parent_device()
)
filterset_class = filtersets.DeviceFilterSet

View File

@@ -218,7 +218,7 @@ class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
class Meta(PrimaryModelTable.Meta):
model = RackReservation
fields = (
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'status', 'user', 'tenant',
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'status', 'user', 'created', 'tenant',
'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
)
default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'status', 'user', 'description')

View File

@@ -10,7 +10,6 @@ __all__ = (
'BooleanAttr',
'ChoiceAttr',
'ColorAttr',
'DateTimeAttr',
'GPSCoordinatesAttr',
'GenericForeignKeyAttr',
'ImageAttr',
@@ -368,26 +367,6 @@ class GPSCoordinatesAttr(ObjectAttribute):
})
class DateTimeAttr(ObjectAttribute):
"""
A date or datetime attribute.
Parameters:
spec (str): Controls the rendering format. Use 'date' for date-only rendering,
or 'seconds'/'minutes' for datetime rendering with the given precision.
"""
template_name = 'ui/attrs/datetime.html'
def __init__(self, *args, spec='seconds', **kwargs):
super().__init__(*args, **kwargs)
self.spec = spec
def get_context(self, obj, context):
return {
'spec': self.spec,
}
class TimezoneAttr(ObjectAttribute):
"""
A timezone value. Includes the numeric offset from UTC.

View File

@@ -1 +0,0 @@
{% load helpers %}{% if spec == 'date' %}{{ value|isodate }}{% else %}{{ value|isodatetime:spec }}{% endif %}

View File

@@ -1 +0,0 @@
{% load helpers %}{{ object.get_full_name|placeholder }}

View File

@@ -1,3 +1,60 @@
{% extends 'generic/object.html' %}
{% load i18n %}
{% load helpers %}
{% load render_table from django_tables2 %}
{% block title %}{% trans "Group" %} {{ object.name }}{% endblock %}
{% block subtitle %}{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "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>
</table>
</div>
</div>
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Users" %}</h2>
<div class="list-group list-group-flush">
{% for user in object.users.all %}
<a href="{% url 'users:user' pk=user.pk %}" class="list-group-item list-group-item-action">{{ user }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Assigned Permissions" %}</h2>
<div class="list-group list-group-flush">
{% for perm in object.object_permissions.all %}
<a href="{% url 'users:objectpermission' pk=perm.pk %}" class="list-group-item list-group-item-action">{{ perm }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Owner Membership" %}</h2>
<div class="list-group list-group-flush">
{% for owner in object.owners.all %}
<a href="{% url 'users:owner' pk=owner.pk %}" class="list-group-item list-group-item-action">{{ owner }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,5 +1,93 @@
{% extends 'generic/object.html' %}
{% load i18n %}
{% load helpers %}
{% load render_table from django_tables2 %}
{% block title %}{% trans "Permission" %} {{ object.name }}{% endblock %}
{% block subtitle %}{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Permission" %}</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 "Enabled" %}</th>
<td>{% checkmark object.enabled %}</td>
</tr>
</table>
</div>
<div class="card">
<h2 class="card-header">{% trans "Actions" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "View" %}</th>
<td>{% checkmark object.can_view %}</td>
</tr>
<tr>
<th scope="row">{% trans "Add" %}</th>
<td>{% checkmark object.can_add %}</td>
</tr>
<tr>
<th scope="row">{% trans "Change" %}</th>
<td>{% checkmark object.can_change %}</td>
</tr>
<tr>
<th scope="row">{% trans "Delete" %}</th>
<td>{% checkmark object.can_delete %}</td>
</tr>
</table>
</div>
<div class="card">
<h2 class="card-header">{% trans "Constraints" %}</h2>
<div class="card-body">
{% if object.constraints %}
<pre>{{ object.constraints|json }}</pre>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Object Types" %}</h2>
<ul class="list-group list-group-flush">
{% for user in object.object_types.all %}
<li class="list-group-item">{{ user }}</li>
{% endfor %}
</ul>
</div>
<div class="card">
<h2 class="card-header">{% trans "Assigned Users" %}</h2>
<div class="list-group list-group-flush">
{% for user in object.users.all %}
<a href="{% url 'users:user' pk=user.pk %}" class="list-group-item list-group-item-action">{{ user }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Assigned Groups" %}</h2>
<div class="list-group list-group-flush">
{% for group in object.groups.all %}
<a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -11,3 +11,50 @@
{% endblock %}
{% block subtitle %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Owner" %}</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 "Group" %}</th>
<td>{{ object.group|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
</table>
</div>
<div class="card">
<h2 class="card-header">{% trans "Groups" %}</h2>
<div class="list-group list-group-flush">
{% for group in object.user_groups.all %}
<a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Users" %}</h2>
<div class="list-group list-group-flush">
{% for user in object.users.all %}
<a href="{% url 'users:user' pk=user.pk %}" class="list-group-item list-group-item-action">{{ user }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-6">
{% include 'inc/panels/related_objects.html' with filter_name='owner_id' %}
</div>
</div>
{% endblock %}

View File

@@ -1,3 +1,46 @@
{% extends 'generic/object.html' %}
{% load i18n %}
{% load helpers %}
{% load render_table from django_tables2 %}
{% block subtitle %}{% endblock %}
{% block extra_controls %}
{% if perms.users.add_owner %}
<a href="{% url 'users:owner_add' %}?group={{ object.pk }}" class="btn btn-primary">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Owner" %}
</a>
{% endif %}
{% endblock extra_controls %}
{% block content %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "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>
</table>
</div>
</div>
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Members" %}</h2>
<div class="list-group list-group-flush">
{% for owner in object.members.all %}
<a href="{% url 'users:owner' pk=owner.pk %}" class="list-group-item list-group-item-action">{{ owner }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% load i18n %}
<div class="card">
<h2 class="card-header">{% trans "Object Types" %}</h2>
<ul class="list-group list-group-flush">
{% for object_type in object.object_types.all %}
<li class="list-group-item">{{ object_type }}</li>
{% empty %}
<li class="list-group-item text-muted">{% trans "None" %}</li>
{% endfor %}
</ul>
</div>

View File

@@ -1,3 +1,85 @@
{% extends 'generic/object.html' %}
{% load i18n %}
{% block title %}{% trans "User" %} {{ object.username }}{% endblock %}
{% block subtitle %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "User" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Username" %}</th>
<td>{{ object.username }}</td>
</tr>
<tr>
<th scope="row">{% trans "Full Name" %}</th>
<td>{{ object.get_full_name|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Email" %}</th>
<td>{{ object.email|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Account Created" %}</th>
<td>{{ object.date_joined|isodate }}</td>
</tr>
<tr>
<th scope="row">{% trans "Last Login" %}</th>
<td>{{ object.last_login|isodatetime:"minutes"|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Active" %}</th>
<td>{% checkmark object.is_active %}</td>
</tr>
<tr>
<th scope="row">{% trans "Superuser" %}</th>
<td>{% checkmark object.is_superuser %}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Assigned Groups" %}</h2>
<div class="list-group list-group-flush">
{% for group in object.groups.all %}
<a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Assigned Permissions" %}</h2>
<div class="list-group list-group-flush">
{% for perm in object.object_permissions.all %}
<a href="{% url 'users:objectpermission' pk=perm.pk %}" class="list-group-item list-group-item-action">{{ perm }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
<div class="card">
<h2 class="card-header">{% trans "Owner Membership" %}</h2>
<div class="list-group list-group-flush">
{% for owner in object.owners.all %}
<a href="{% url 'users:owner' pk=owner.pk %}" class="list-group-item list-group-item-action">{{ owner }}</a>
{% empty %}
<div class="list-group-item text-muted">{% trans "None" %}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% if perms.core.view_objectchange %}
<div class="row">
<div class="col-md-12">
{% include 'users/inc/user_activity.html' with user=object table=changelog_table %}
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,3 +0,0 @@
{% load i18n %}
<span id="secret" class="font-monospace" data-secret="{{ value }}">{{ value }}</span>
<button type="button" class="btn btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>

View File

@@ -0,0 +1,25 @@
{% load helpers %}
{% load i18n %}
<div class="card">
<h2 class="card-header">{% trans "Authentication" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Type" %}</th>
<td>{{ object.get_auth_type_display|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Cipher" %}</th>
<td>{{ object.get_auth_cipher_display|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "PSK" %}</th>
<td>
<span id="secret" class="font-monospace" data-secret="{{ object.auth_psk }}">{{ object.auth_psk|placeholder }}</span>
{% if object.auth_psk %}
<button type="button" class="btn btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>
{% endif %}
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,51 @@
{% load helpers %}
{% load i18n %}
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Device" %}</th>
<td>{{ interface.device|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Interface" %}</th>
<td>{{ interface|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Type" %}</th>
<td>
{{ interface.get_type_display }}
</td>
</tr>
<tr>
<th scope="row">{% trans "Role" %}</th>
<td>
{{ interface.get_rf_role_display|placeholder }}
</td>
</tr>
<tr>
<th scope="row">{% trans "Channel" %}</th>
<td>
{{ interface.get_rf_channel_display|placeholder }}
</td>
</tr>
<tr>
<th scope="row">{% trans "Channel Frequency" %}</th>
<td>
{% if interface.rf_channel_frequency %}
{{ interface.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Channel Width" %}</th>
<td>
{% if interface.rf_channel_width %}
{{ interface.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
</table>

View File

@@ -1,48 +0,0 @@
{% extends "ui/panels/_base.html" %}
{% load helpers %}
{% load i18n %}
{% block panel_content %}
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Device" %}</th>
<td>{{ interface.device|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Interface" %}</th>
<td>{{ interface|linkify }}</td>
</tr>
<tr>
<th scope="row">{% trans "Type" %}</th>
<td>{{ interface.get_type_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "Role" %}</th>
<td>{{ interface.get_rf_role_display|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Channel" %}</th>
<td>{{ interface.get_rf_channel_display|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Channel Frequency" %}</th>
<td>
{% if interface.rf_channel_frequency %}
{{ interface.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" context "Abbreviation for megahertz" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">{% trans "Channel Width" %}</th>
<td>
{% if interface.rf_channel_width %}
{{ interface.rf_channel_width|floatformat:"-3" }} {% trans "MHz" context "Abbreviation for megahertz" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
</table>
{% endblock panel_content %}

View File

@@ -1 +1,74 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Wireless LAN" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "SSID" %}</th>
<td>{{ object.ssid }}</td>
</tr>
<tr>
<th scope="row">{% trans "Group" %}</th>
<td>{{ object.group|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Status" %}</th>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
<tr>
<th scope="row">{% trans "Scope" %}</th>
{% if object.scope %}
<td>{{ object.scope|linkify }} ({% trans object.scope_type.name %})</td>
{% else %}
<td>{{ ''|placeholder }}</td>
{% endif %}
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "VLAN" %}</th>
<td>{{ object.vlan|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Tenant" %}</th>
<td>
{% if object.tenant.group %}
{{ object.tenant.group|linkify }} /
{% endif %}
{{ object.tenant|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 'wireless/inc/authentication_attrs.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="card">
<h2 class="card-header">{% trans "Attached Interfaces" %}</h2>
<div class="card-body table-responsive">
{% render_table interfaces_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=interfaces_table.paginator page=interfaces_table.page %}
</div>
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -1,4 +1,7 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block breadcrumbs %}
@@ -15,3 +18,53 @@
</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 "Wireless LAN 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.wireless.add_wirelesslangroup %}
<div class="card-actions">
<a href="{% url 'wireless:wirelesslangroup_add' %}?parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Wireless LAN Group" %}
</a>
</div>
{% endif %}
</h2>
{% htmx_table 'wireless:wirelesslangroup_list' parent_id=object.pk %}
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -1 +1,68 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Interface" %} A</h2>
{% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_a %}
</div>
<div class="card">
<h2 class="card-header">{% trans "Link Properties" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Status" %}</th>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
<tr>
<th scope="row">{% trans "SSID" %}</th>
<td>{{ object.ssid|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Tenant" %}</th>
<td>
{% if object.tenant.group %}
{{ object.tenant.group|linkify }} /
{% endif %}
{{ object.tenant|linkify|placeholder }}
</td>
</tr>
<tr>
<th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">{% trans "Distance" %}</th>
<td>
{% if object.distance is not None %}
{{ object.distance|floatformat }} {{ object.get_distance_unit_display }}
{% else %}
{{ ''|placeholder }}
{% endif %}
</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">
<div class="card">
<h2 class="card-header">{% trans "Interface" %} B</h2>
{% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %}
</div>
{% include 'wireless/inc/authentication_attrs.html' %}
{% include 'inc/panels/custom_fields.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

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-11 05:18+0000\n"
"POT-Creation-Date: 2026-03-07 05:14+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -62,7 +62,7 @@ msgstr ""
#: netbox/ipam/choices.py:31 netbox/ipam/choices.py:49
#: netbox/ipam/choices.py:69 netbox/ipam/choices.py:154
#: netbox/templates/extras/configcontext.html:29
#: netbox/users/forms/bulk_edit.py:41 netbox/users/ui/panels.py:38
#: netbox/templates/users/user.html:35 netbox/users/forms/bulk_edit.py:41
#: netbox/virtualization/choices.py:22 netbox/virtualization/choices.py:45
#: netbox/vpn/choices.py:19 netbox/vpn/choices.py:280
#: netbox/wireless/choices.py:25
@@ -172,8 +172,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:323 netbox/dcim/forms/bulk_edit.py:673
#: netbox/dcim/forms/bulk_edit.py:860 netbox/dcim/forms/bulk_import.py:146
#: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:349
#: netbox/dcim/forms/bulk_import.py:640 netbox/dcim/forms/bulk_import.py:1612
#: netbox/dcim/forms/bulk_import.py:1640 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/bulk_import.py:640 netbox/dcim/forms/bulk_import.py:1609
#: netbox/dcim/forms/bulk_import.py:1637 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/filtersets.py:256 netbox/dcim/forms/filtersets.py:379
#: netbox/dcim/forms/filtersets.py:483 netbox/dcim/forms/filtersets.py:855
#: netbox/dcim/forms/filtersets.py:1073 netbox/dcim/forms/filtersets.py:1147
@@ -471,7 +471,7 @@ msgstr ""
#: netbox/dcim/tables/devicetypes.py:214 netbox/dcim/tables/devicetypes.py:255
#: netbox/dcim/tables/devicetypes.py:274 netbox/dcim/tables/racks.py:30
#: netbox/extras/forms/bulk_edit.py:306 netbox/extras/tables/tables.py:552
#: netbox/netbox/ui/attrs.py:194 netbox/templates/circuits/circuittype.html:30
#: netbox/netbox/ui/attrs.py:193 netbox/templates/circuits/circuittype.html:30
#: netbox/templates/circuits/virtualcircuittype.html:30
#: netbox/templates/dcim/cable.html:44 netbox/templates/dcim/frontport.html:40
#: netbox/templates/dcim/inventoryitemrole.html:26
@@ -500,7 +500,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:865 netbox/dcim/forms/bulk_import.py:886
#: netbox/dcim/forms/bulk_import.py:972 netbox/dcim/forms/bulk_import.py:1101
#: netbox/dcim/forms/bulk_import.py:1120 netbox/dcim/forms/bulk_import.py:1465
#: netbox/dcim/forms/bulk_import.py:1677 netbox/dcim/forms/filtersets.py:1104
#: netbox/dcim/forms/bulk_import.py:1674 netbox/dcim/forms/filtersets.py:1104
#: netbox/dcim/forms/filtersets.py:1205 netbox/dcim/forms/filtersets.py:1333
#: netbox/dcim/forms/filtersets.py:1424 netbox/dcim/forms/filtersets.py:1444
#: netbox/dcim/forms/filtersets.py:1464 netbox/dcim/forms/filtersets.py:1484
@@ -572,7 +572,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:265 netbox/dcim/forms/bulk_import.py:374
#: netbox/dcim/forms/bulk_import.py:605 netbox/dcim/forms/bulk_import.py:765
#: netbox/dcim/forms/bulk_import.py:1230 netbox/dcim/forms/bulk_import.py:1453
#: netbox/dcim/forms/bulk_import.py:1672 netbox/dcim/forms/bulk_import.py:1735
#: netbox/dcim/forms/bulk_import.py:1669 netbox/dcim/forms/bulk_import.py:1732
#: netbox/dcim/forms/filtersets.py:208 netbox/dcim/forms/filtersets.py:268
#: netbox/dcim/forms/filtersets.py:396 netbox/dcim/forms/filtersets.py:504
#: netbox/dcim/forms/filtersets.py:901 netbox/dcim/forms/filtersets.py:1024
@@ -646,7 +646,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:122 netbox/dcim/forms/bulk_import.py:167
#: netbox/dcim/forms/bulk_import.py:258 netbox/dcim/forms/bulk_import.py:379
#: netbox/dcim/forms/bulk_import.py:579 netbox/dcim/forms/bulk_import.py:1471
#: netbox/dcim/forms/bulk_import.py:1728 netbox/dcim/forms/filtersets.py:143
#: netbox/dcim/forms/bulk_import.py:1725 netbox/dcim/forms/filtersets.py:143
#: netbox/dcim/forms/filtersets.py:202 netbox/dcim/forms/filtersets.py:235
#: netbox/dcim/forms/filtersets.py:363 netbox/dcim/forms/filtersets.py:442
#: netbox/dcim/forms/filtersets.py:463 netbox/dcim/forms/filtersets.py:823
@@ -890,6 +890,10 @@ msgstr ""
#: netbox/templates/tenancy/contactrole.html:22
#: netbox/templates/tenancy/tenant.html:24
#: netbox/templates/tenancy/tenantgroup.html:33
#: netbox/templates/users/group.html:21
#: netbox/templates/users/objectpermission.html:21
#: netbox/templates/users/owner.html:30
#: netbox/templates/users/ownergroup.html:27
#: netbox/templates/vpn/ikepolicy.html:17
#: netbox/templates/vpn/ikeproposal.html:17
#: netbox/templates/vpn/ipsecpolicy.html:17
@@ -1047,7 +1051,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:105 netbox/dcim/forms/bulk_import.py:164
#: netbox/dcim/forms/bulk_import.py:267 netbox/dcim/forms/bulk_import.py:376
#: netbox/dcim/forms/bulk_import.py:607 netbox/dcim/forms/bulk_import.py:767
#: netbox/dcim/forms/bulk_import.py:1232 netbox/dcim/forms/bulk_import.py:1674
#: netbox/dcim/forms/bulk_import.py:1232 netbox/dcim/forms/bulk_import.py:1671
#: netbox/ipam/forms/bulk_import.py:200 netbox/ipam/forms/bulk_import.py:264
#: netbox/ipam/forms/bulk_import.py:300 netbox/ipam/forms/bulk_import.py:512
#: netbox/ipam/forms/bulk_import.py:525
@@ -1063,8 +1067,8 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:235
#: netbox/dcim/forms/bulk_import.py:126 netbox/dcim/forms/bulk_import.py:171
#: netbox/dcim/forms/bulk_import.py:383 netbox/dcim/forms/bulk_import.py:583
#: netbox/dcim/forms/bulk_import.py:1475 netbox/dcim/forms/bulk_import.py:1669
#: netbox/dcim/forms/bulk_import.py:1732 netbox/ipam/forms/bulk_import.py:49
#: netbox/dcim/forms/bulk_import.py:1475 netbox/dcim/forms/bulk_import.py:1666
#: netbox/dcim/forms/bulk_import.py:1729 netbox/ipam/forms/bulk_import.py:49
#: netbox/ipam/forms/bulk_import.py:78 netbox/ipam/forms/bulk_import.py:106
#: netbox/ipam/forms/bulk_import.py:126 netbox/ipam/forms/bulk_import.py:146
#: netbox/ipam/forms/bulk_import.py:174 netbox/ipam/forms/bulk_import.py:259
@@ -1142,8 +1146,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:439 netbox/dcim/forms/bulk_edit.py:678
#: netbox/dcim/forms/bulk_edit.py:727 netbox/dcim/forms/bulk_edit.py:869
#: netbox/dcim/forms/bulk_import.py:252 netbox/dcim/forms/bulk_import.py:355
#: netbox/dcim/forms/bulk_import.py:646 netbox/dcim/forms/bulk_import.py:1618
#: netbox/dcim/forms/bulk_import.py:1652 netbox/dcim/forms/filtersets.py:114
#: netbox/dcim/forms/bulk_import.py:646 netbox/dcim/forms/bulk_import.py:1615
#: netbox/dcim/forms/bulk_import.py:1649 netbox/dcim/forms/filtersets.py:114
#: netbox/dcim/forms/filtersets.py:358 netbox/dcim/forms/filtersets.py:393
#: netbox/dcim/forms/filtersets.py:438 netbox/dcim/forms/filtersets.py:491
#: netbox/dcim/forms/filtersets.py:820 netbox/dcim/forms/filtersets.py:864
@@ -1340,6 +1344,9 @@ msgstr ""
#: netbox/templates/ipam/inc/panels/fhrp_groups.html:23
#: netbox/templates/ipam/panels/fhrp_groups.html:9
#: netbox/templates/ipam/vlan.html:27 netbox/templates/tenancy/tenant.html:20
#: netbox/templates/users/group.html:6 netbox/templates/users/group.html:14
#: netbox/templates/users/owner.html:26
#: netbox/templates/users/ownergroup.html:20
#: netbox/templates/vpn/tunnel.html:29
#: netbox/templates/wireless/wirelesslan.html:18
#: netbox/tenancy/forms/bulk_edit.py:44 netbox/tenancy/forms/bulk_import.py:46
@@ -1736,6 +1743,10 @@ msgstr ""
#: netbox/templates/tenancy/contactgroup.html:21
#: netbox/templates/tenancy/contactrole.html:18
#: netbox/templates/tenancy/tenantgroup.html:29
#: netbox/templates/users/group.html:17
#: netbox/templates/users/objectpermission.html:17
#: netbox/templates/users/owner.html:22
#: netbox/templates/users/ownergroup.html:23
#: netbox/templates/vpn/ikepolicy.html:13
#: netbox/templates/vpn/ikeproposal.html:13
#: netbox/templates/vpn/ipsecpolicy.html:13
@@ -1859,7 +1870,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:1096 netbox/dcim/forms/bulk_import.py:1115
#: netbox/dcim/forms/bulk_import.py:1134 netbox/dcim/forms/bulk_import.py:1146
#: netbox/dcim/forms/bulk_import.py:1194 netbox/dcim/forms/bulk_import.py:1316
#: netbox/dcim/forms/bulk_import.py:1722 netbox/dcim/forms/connections.py:34
#: netbox/dcim/forms/bulk_import.py:1719 netbox/dcim/forms/connections.py:34
#: netbox/dcim/forms/filtersets.py:156 netbox/dcim/forms/filtersets.py:1021
#: netbox/dcim/forms/filtersets.py:1054 netbox/dcim/forms/filtersets.py:1202
#: netbox/dcim/forms/filtersets.py:1418 netbox/dcim/forms/filtersets.py:1441
@@ -2106,7 +2117,8 @@ msgid "Local"
msgstr ""
#: netbox/core/data_backends.py:64 netbox/core/tables/change_logging.py:21
#: netbox/templates/account/profile.html:13 netbox/users/tables.py:64
#: netbox/templates/account/profile.html:13 netbox/templates/users/user.html:15
#: netbox/users/tables.py:64
msgid "Username"
msgstr ""
@@ -2177,6 +2189,7 @@ msgstr ""
#: netbox/templates/extras/eventrule.html:17
#: netbox/templates/extras/savedfilter.html:25
#: netbox/templates/extras/tableconfig.html:33
#: netbox/templates/users/objectpermission.html:25
#: netbox/users/forms/bulk_edit.py:87 netbox/users/forms/bulk_edit.py:105
#: netbox/users/forms/filtersets.py:67 netbox/users/forms/filtersets.py:133
#: netbox/users/tables.py:30 netbox/users/tables.py:113
@@ -2289,7 +2302,8 @@ msgstr ""
#: netbox/templates/core/objectchange.html:36
#: netbox/templates/extras/savedfilter.html:21
#: netbox/templates/extras/tableconfig.html:29
#: netbox/templates/inc/user_menu.html:31 netbox/users/filtersets.py:135
#: netbox/templates/inc/user_menu.html:31 netbox/templates/users/user.html:4
#: netbox/templates/users/user.html:12 netbox/users/filtersets.py:135
#: netbox/users/filtersets.py:217 netbox/users/forms/filtersets.py:81
#: netbox/users/forms/filtersets.py:126 netbox/users/forms/model_forms.py:181
#: netbox/users/forms/model_forms.py:221 netbox/users/tables.py:22
@@ -2461,7 +2475,7 @@ msgstr ""
#: netbox/core/models/files.py:30 netbox/core/models/jobs.py:60
#: netbox/extras/models/models.py:852 netbox/extras/models/notifications.py:39
#: netbox/extras/models/notifications.py:195
#: netbox/netbox/models/features.py:62 netbox/users/models/tokens.py:51
#: netbox/netbox/models/features.py:61 netbox/users/models/tokens.py:51
msgid "created"
msgstr ""
@@ -2579,7 +2593,7 @@ msgid ""
msgstr ""
#: netbox/core/models/data.py:290 netbox/core/models/files.py:34
#: netbox/netbox/models/features.py:68
#: netbox/netbox/models/features.py:67
msgid "last updated"
msgstr ""
@@ -2733,7 +2747,7 @@ msgid "Deletion is prevented by a protection rule: {message}"
msgstr ""
#: netbox/core/tables/change_logging.py:26
#: netbox/templates/account/profile.html:17
#: netbox/templates/account/profile.html:17 netbox/templates/users/user.html:19
msgid "Full Name"
msgstr ""
@@ -4420,8 +4434,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:438 netbox/dcim/forms/bulk_edit.py:891
#: netbox/dcim/forms/bulk_import.py:362 netbox/dcim/forms/bulk_import.py:365
#: netbox/dcim/forms/bulk_import.py:653 netbox/dcim/forms/bulk_import.py:1659
#: netbox/dcim/forms/bulk_import.py:1663 netbox/dcim/forms/filtersets.py:123
#: netbox/dcim/forms/bulk_import.py:653 netbox/dcim/forms/bulk_import.py:1656
#: netbox/dcim/forms/bulk_import.py:1660 netbox/dcim/forms/filtersets.py:123
#: netbox/dcim/forms/filtersets.py:359 netbox/dcim/forms/filtersets.py:448
#: netbox/dcim/forms/filtersets.py:462 netbox/dcim/forms/filtersets.py:501
#: netbox/dcim/forms/filtersets.py:874 netbox/dcim/forms/filtersets.py:1086
@@ -4628,17 +4642,17 @@ msgstr ""
msgid "Domain"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:886 netbox/dcim/forms/bulk_import.py:1646
#: netbox/dcim/forms/bulk_edit.py:886 netbox/dcim/forms/bulk_import.py:1643
#: netbox/dcim/forms/filtersets.py:1316 netbox/dcim/forms/model_forms.py:865
msgid "Power panel"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:908 netbox/dcim/forms/bulk_import.py:1682
#: netbox/dcim/forms/bulk_edit.py:908 netbox/dcim/forms/bulk_import.py:1679
#: netbox/dcim/forms/filtersets.py:1338 netbox/templates/dcim/powerfeed.html:83
msgid "Supply"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:914 netbox/dcim/forms/bulk_import.py:1687
#: netbox/dcim/forms/bulk_edit.py:914 netbox/dcim/forms/bulk_import.py:1684
#: netbox/dcim/forms/filtersets.py:1343 netbox/templates/dcim/powerfeed.html:95
msgid "Phase"
msgstr ""
@@ -4886,7 +4900,7 @@ msgid "available options"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:149 netbox/dcim/forms/bulk_import.py:643
#: netbox/dcim/forms/bulk_import.py:1643 netbox/ipam/forms/bulk_import.py:493
#: netbox/dcim/forms/bulk_import.py:1640 netbox/ipam/forms/bulk_import.py:493
#: netbox/virtualization/forms/bulk_import.py:64
#: netbox/virtualization/forms/bulk_import.py:102
msgid "Assigned site"
@@ -4949,7 +4963,7 @@ msgstr ""
msgid "Parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:359 netbox/dcim/forms/bulk_import.py:1656
#: netbox/dcim/forms/bulk_import.py:359 netbox/dcim/forms/bulk_import.py:1653
msgid "Rack's location (if any)"
msgstr ""
@@ -5014,7 +5028,7 @@ msgstr ""
msgid "Limit platform assignments to this manufacturer"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:576 netbox/dcim/forms/bulk_import.py:1725
#: netbox/dcim/forms/bulk_import.py:576 netbox/dcim/forms/bulk_import.py:1722
#: netbox/tenancy/forms/bulk_import.py:116
msgid "Assigned role"
msgstr ""
@@ -5364,24 +5378,24 @@ msgstr ""
msgid "Color name (e.g. \"Red\") or hex code (e.g. \"f44336\")"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1542
#: netbox/dcim/forms/bulk_import.py:1539
#, python-brace-format
msgid "Side {side_upper}: {device} {termination_object} is already connected"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1548
#: netbox/dcim/forms/bulk_import.py:1545
#, python-brace-format
msgid "{side_upper} side termination not found: {device} {name}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1569
#: netbox/dcim/forms/bulk_import.py:1566
#, python-brace-format
msgid ""
"{color} did not match any used color name and was longer than six "
"characters: invalid hex."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1594 netbox/dcim/forms/model_forms.py:900
#: netbox/dcim/forms/bulk_import.py:1591 netbox/dcim/forms/model_forms.py:900
#: netbox/dcim/tables/devices.py:1124
#: netbox/templates/dcim/panels/virtual_chassis_members.html:10
#: netbox/templates/dcim/virtualchassis.html:17
@@ -5389,49 +5403,49 @@ msgstr ""
msgid "Master"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1598
#: netbox/dcim/forms/bulk_import.py:1595
msgid "Master device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1615
#: netbox/dcim/forms/bulk_import.py:1612
msgid "Name of parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1649
#: netbox/dcim/forms/bulk_import.py:1646
msgid "Upstream power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1679
#: netbox/dcim/forms/bulk_import.py:1676
msgid "Primary or redundant"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1684
#: netbox/dcim/forms/bulk_import.py:1681
msgid "Supply type (AC/DC)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1689
#: netbox/dcim/forms/bulk_import.py:1686
msgid "Single or three-phase"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1739 netbox/dcim/forms/model_forms.py:1875
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/model_forms.py:1875
#: netbox/dcim/ui/panels.py:108
#: netbox/templates/dcim/virtualdevicecontext.html:30
#: netbox/virtualization/ui/panels.py:28
msgid "Primary IPv4"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1743
#: netbox/dcim/forms/bulk_import.py:1740
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1746 netbox/dcim/forms/model_forms.py:1884
#: netbox/dcim/forms/bulk_import.py:1743 netbox/dcim/forms/model_forms.py:1884
#: netbox/dcim/ui/panels.py:113
#: netbox/templates/dcim/virtualdevicecontext.html:41
#: netbox/virtualization/ui/panels.py:33
msgid "Primary IPv6"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1750
#: netbox/dcim/forms/bulk_import.py:1747
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
msgstr ""
@@ -5949,7 +5963,8 @@ msgstr ""
#: netbox/dcim/forms/object_create.py:312 netbox/dcim/tables/devices.py:1130
#: netbox/ipam/tables/fhrp.py:31 netbox/templates/dcim/virtualchassis.html:43
#: netbox/templates/dcim/virtualchassis_edit.html:59
#: netbox/templates/ipam/fhrpgroup.html:38 netbox/users/views.py:347
#: netbox/templates/ipam/fhrpgroup.html:38
#: netbox/templates/users/ownergroup.html:35
msgid "Members"
msgstr ""
@@ -8813,6 +8828,7 @@ msgstr ""
#: netbox/extras/forms/bulk_import.py:318
#: netbox/extras/forms/model_forms.py:414 netbox/netbox/navigation/menu.py:415
#: netbox/templates/extras/notificationgroup.html:41
#: netbox/templates/users/group.html:29 netbox/templates/users/owner.html:46
#: netbox/users/forms/filtersets.py:181 netbox/users/forms/model_forms.py:265
#: netbox/users/forms/model_forms.py:277 netbox/users/forms/model_forms.py:352
#: netbox/users/forms/model_forms.py:483 netbox/users/forms/model_forms.py:498
@@ -8829,7 +8845,8 @@ msgstr ""
#: netbox/netbox/navigation/menu.py:416
#: netbox/templates/extras/notificationgroup.html:31
#: netbox/templates/tenancy/contact.html:21
#: netbox/tenancy/forms/bulk_edit.py:121 netbox/tenancy/forms/filtersets.py:107
#: netbox/templates/users/owner.html:36 netbox/tenancy/forms/bulk_edit.py:121
#: netbox/tenancy/forms/filtersets.py:107
#: netbox/tenancy/forms/model_forms.py:93 netbox/tenancy/tables/contacts.py:57
#: netbox/tenancy/tables/contacts.py:101 netbox/users/forms/filtersets.py:176
#: netbox/users/forms/model_forms.py:210 netbox/users/forms/model_forms.py:222
@@ -9991,7 +10008,7 @@ msgstr ""
#: netbox/extras/tables/tables.py:517 netbox/extras/tables/tables.py:555
#: netbox/templates/extras/customfield.html:105
#: netbox/templates/extras/eventrule.html:27
#: netbox/templates/users/panels/object_types.html:3 netbox/users/tables.py:110
#: netbox/templates/users/objectpermission.html:64 netbox/users/tables.py:110
msgid "Object Types"
msgstr ""
@@ -10042,7 +10059,7 @@ msgstr ""
#: netbox/netbox/forms/mixins.py:162 netbox/netbox/forms/mixins.py:187
#: netbox/netbox/tables/tables.py:292 netbox/netbox/tables/tables.py:307
#: netbox/netbox/tables/tables.py:322 netbox/templates/generic/object.html:61
#: netbox/users/forms/model_forms.py:481
#: netbox/templates/users/owner.html:19 netbox/users/forms/model_forms.py:481
msgid "Owner"
msgstr ""
@@ -12063,46 +12080,46 @@ msgstr ""
msgid "Lookup"
msgstr ""
#: netbox/netbox/models/features.py:311
#: netbox/netbox/models/features.py:310
#, python-brace-format
msgid "Invalid value for custom field '{name}': {error}"
msgstr ""
#: netbox/netbox/models/features.py:320
#: netbox/netbox/models/features.py:319
#, python-brace-format
msgid "Custom field '{name}' must have a unique value."
msgstr ""
#: netbox/netbox/models/features.py:327
#: netbox/netbox/models/features.py:326
#, python-brace-format
msgid "Missing required custom field '{name}'."
msgstr ""
#: netbox/netbox/models/features.py:521
#: netbox/netbox/models/features.py:519
msgid "Remote data source"
msgstr ""
#: netbox/netbox/models/features.py:531
#: netbox/netbox/models/features.py:529
msgid "data path"
msgstr ""
#: netbox/netbox/models/features.py:535
#: netbox/netbox/models/features.py:533
msgid "Path to remote file (relative to data source root)"
msgstr ""
#: netbox/netbox/models/features.py:538
#: netbox/netbox/models/features.py:536
msgid "auto sync enabled"
msgstr ""
#: netbox/netbox/models/features.py:540
#: netbox/netbox/models/features.py:538
msgid "Enable automatic synchronization of data when the data file is updated"
msgstr ""
#: netbox/netbox/models/features.py:543
#: netbox/netbox/models/features.py:541
msgid "date synced"
msgstr ""
#: netbox/netbox/models/features.py:636
#: netbox/netbox/models/features.py:634
#, python-brace-format
msgid "{class_name} must implement a sync_data() method."
msgstr ""
@@ -12489,7 +12506,7 @@ msgstr ""
#: netbox/templates/dcim/manufacturer.html:8
#: netbox/templates/extras/tableconfig_edit.html:29
#: netbox/templates/generic/bulk_add_component.html:22
#: netbox/users/ui/panels.py:52
#: netbox/templates/users/objectpermission.html:38
#: netbox/utilities/templates/helpers/table_config_form.html:20
#: netbox/utilities/templates/widgets/splitmultiselect.html:11
#: netbox/utilities/templatetags/buttons.py:175
@@ -12526,7 +12543,8 @@ msgstr ""
#: netbox/templates/htmx/delete_form.html:70
#: netbox/templates/ipam/inc/panels/fhrp_groups.html:48
#: netbox/templates/ipam/panels/fhrp_groups.html:34
#: netbox/users/ui/panels.py:54 netbox/utilities/templatetags/buttons.py:146
#: netbox/templates/users/objectpermission.html:46
#: netbox/utilities/templatetags/buttons.py:146
msgid "Delete"
msgstr ""
@@ -12783,13 +12801,13 @@ msgstr ""
msgid "Copy"
msgstr ""
#: netbox/netbox/ui/attrs.py:213
#: netbox/netbox/ui/attrs.py:212
#, python-brace-format
msgid ""
"Invalid decoding option: {decoding}! Must be one of {image_decoding_choices}"
msgstr ""
#: netbox/netbox/ui/attrs.py:344
#: netbox/netbox/ui/attrs.py:343
msgid "GPS coordinates"
msgstr ""
@@ -13059,25 +13077,26 @@ msgid "Account Details"
msgstr ""
#: netbox/templates/account/profile.html:27
#: netbox/templates/tenancy/contact.html:53
#: netbox/templates/tenancy/contact.html:53 netbox/templates/users/user.html:23
#: netbox/tenancy/forms/bulk_edit.py:104
msgid "Email"
msgstr ""
#: netbox/templates/account/profile.html:31
#: netbox/templates/account/profile.html:31 netbox/templates/users/user.html:27
msgid "Account Created"
msgstr ""
#: netbox/templates/account/profile.html:35
#: netbox/templates/account/profile.html:35 netbox/templates/users/user.html:31
msgid "Last Login"
msgstr ""
#: netbox/templates/account/profile.html:39 netbox/users/ui/panels.py:39
#: netbox/templates/account/profile.html:39 netbox/templates/users/user.html:39
msgid "Superuser"
msgstr ""
#: netbox/templates/account/profile.html:47 netbox/users/views.py:104
#: netbox/users/views.py:283
#: netbox/templates/account/profile.html:47
#: netbox/templates/users/objectpermission.html:82
#: netbox/templates/users/user.html:47
msgid "Assigned Groups"
msgstr ""
@@ -13107,7 +13126,14 @@ msgstr ""
#: netbox/templates/ipam/panels/fhrp_groups.html:42
#: netbox/templates/ui/panels/comments.html:9
#: netbox/templates/ui/panels/related_objects.html:22
#: netbox/templates/users/panels/object_types.html:8
#: netbox/templates/users/group.html:34 netbox/templates/users/group.html:44
#: netbox/templates/users/group.html:54
#: netbox/templates/users/objectpermission.html:77
#: netbox/templates/users/objectpermission.html:87
#: netbox/templates/users/owner.html:41 netbox/templates/users/owner.html:51
#: netbox/templates/users/ownergroup.html:40
#: netbox/templates/users/user.html:52 netbox/templates/users/user.html:62
#: netbox/templates/users/user.html:72
msgid "None"
msgstr ""
@@ -13439,7 +13465,8 @@ msgstr ""
msgid "every %(interval)s minutes"
msgstr ""
#: netbox/templates/core/objectchange.html:29 netbox/users/ui/panels.py:53
#: netbox/templates/core/objectchange.html:29
#: netbox/templates/users/objectpermission.html:42
msgid "Change"
msgstr ""
@@ -14225,8 +14252,8 @@ msgstr ""
#: netbox/templates/dcim/virtualchassis_add_member.html:27
#: netbox/templates/generic/object_edit.html:78
#: netbox/templates/users/objectpermission.html:31
#: netbox/users/forms/filtersets.py:64 netbox/users/forms/model_forms.py:373
#: netbox/users/ui/panels.py:49
msgid "Actions"
msgstr ""
@@ -15365,19 +15392,45 @@ msgstr ""
msgid "Local time"
msgstr ""
#: netbox/templates/users/inc/user_activity.html:6 netbox/users/views.py:118
#: netbox/templates/users/group.html:39 netbox/templates/users/user.html:57
msgid "Assigned Permissions"
msgstr ""
#: netbox/templates/users/group.html:49 netbox/templates/users/user.html:67
msgid "Owner Membership"
msgstr ""
#: netbox/templates/users/inc/user_activity.html:6
msgid "Recent Activity"
msgstr ""
#: netbox/templates/users/inc/user_activity.html:9 netbox/users/views.py:123
#: netbox/templates/users/inc/user_activity.html:9
msgid "View All"
msgstr ""
#: netbox/templates/users/objectpermission.html:4
#: netbox/templates/users/objectpermission.html:6
#: netbox/templates/users/objectpermission.html:14
#: netbox/users/forms/filtersets.py:63
msgid "Permission"
msgstr ""
#: netbox/templates/users/objectpermission.html:34
msgid "View"
msgstr ""
#: netbox/templates/users/objectpermission.html:52
#: netbox/users/forms/model_forms.py:363 netbox/users/forms/model_forms.py:376
msgid "Constraints"
msgstr ""
#: netbox/templates/users/objectpermission.html:72
msgid "Assigned Users"
msgstr ""
#: netbox/templates/users/ownergroup.html:11
msgid "Add Owner"
msgstr ""
#: netbox/templates/users/token.html:4 netbox/users/forms/bulk_import.py:48
#: netbox/users/forms/filtersets.py:117 netbox/users/forms/model_forms.py:127
msgid "Token"
@@ -15934,11 +15987,6 @@ msgstr ""
msgid "Actions granted in addition to those listed above"
msgstr ""
#: netbox/users/forms/model_forms.py:363 netbox/users/forms/model_forms.py:376
#: netbox/users/views.py:275
msgid "Constraints"
msgstr ""
#: netbox/users/forms/model_forms.py:365
msgid ""
"JSON expression of a queryset filter that will return only permitted "
@@ -16158,34 +16206,6 @@ msgstr ""
msgid "Example Usage"
msgstr ""
#: netbox/users/ui/panels.py:32
msgid "Full name"
msgstr ""
#: netbox/users/ui/panels.py:36
msgid "Account created"
msgstr ""
#: netbox/users/ui/panels.py:37
msgid "Last login"
msgstr ""
#: netbox/users/ui/panels.py:51
msgid "View"
msgstr ""
#: netbox/users/views.py:108 netbox/users/views.py:206
msgid "Assigned Permissions"
msgstr ""
#: netbox/users/views.py:112 netbox/users/views.py:210
msgid "Owner Membership"
msgstr ""
#: netbox/users/views.py:280
msgid "Assigned Users"
msgstr ""
#: netbox/utilities/api.py:184
#, python-brace-format
msgid "Related object not found using the provided attributes: {params}"
@@ -16615,7 +16635,7 @@ msgstr ""
msgid "Unknown app_label/model_name for {name}"
msgstr ""
#: netbox/utilities/request.py:95
#: netbox/utilities/request.py:92
#, python-brace-format
msgid "Invalid IP address set for {header}: {ip}"
msgstr ""

View File

@@ -23,38 +23,3 @@ class TokenExamplePanel(panels.Panel):
actions = [
actions.CopyContent('token-example')
]
class UserPanel(panels.ObjectAttributesPanel):
username = attrs.TextAttr('username')
full_name = attrs.TemplatedAttr(
'get_full_name',
label=_('Full name'),
template_name='users/attrs/full_name.html',
)
email = attrs.TextAttr('email')
date_joined = attrs.DateTimeAttr('date_joined', label=_('Account created'), spec='date')
last_login = attrs.DateTimeAttr('last_login', label=_('Last login'), spec='minutes')
is_active = attrs.BooleanAttr('is_active', label=_('Active'))
is_superuser = attrs.BooleanAttr('is_superuser', label=_('Superuser'))
class ObjectPermissionPanel(panels.ObjectAttributesPanel):
name = attrs.TextAttr('name')
description = attrs.TextAttr('description')
enabled = attrs.BooleanAttr('enabled')
class ObjectPermissionActionsPanel(panels.ObjectAttributesPanel):
title = _('Actions')
can_view = attrs.BooleanAttr('can_view', label=_('View'))
can_add = attrs.BooleanAttr('can_add', label=_('Add'))
can_change = attrs.BooleanAttr('can_change', label=_('Change'))
can_delete = attrs.BooleanAttr('can_delete', label=_('Delete'))
class OwnerPanel(panels.ObjectAttributesPanel):
name = attrs.TextAttr('name')
group = attrs.RelatedObjectAttr('group', linkify=True)
description = attrs.TextAttr('description')

View File

@@ -1,18 +1,9 @@
from django.db.models import Count
from django.utils.translation import gettext_lazy as _
from core.models import ObjectChange
from core.tables import ObjectChangeTable
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
from netbox.ui import actions, layout
from netbox.ui.panels import (
ContextTablePanel,
JSONPanel,
ObjectsTablePanel,
OrganizationalObjectPanel,
RelatedObjectsPanel,
TemplatePanel,
)
from netbox.ui import layout
from netbox.views import generic
from users.ui import panels
from utilities.query import count_related
@@ -95,39 +86,7 @@ class UserListView(generic.ObjectListView):
@register_model_view(User)
class UserView(generic.ObjectView):
queryset = User.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.UserPanel(),
],
right_panels=[
ObjectsTablePanel(
'users.Group', title=_('Assigned Groups'), filters={'user_id': lambda ctx: ctx['object'].pk}
),
ObjectsTablePanel(
'users.ObjectPermission',
title=_('Assigned Permissions'),
filters={'user_id': lambda ctx: ctx['object'].pk},
),
ObjectsTablePanel(
'users.Owner', title=_('Owner Membership'), filters={'user_id': lambda ctx: ctx['object'].pk}
),
],
bottom_panels=[
ContextTablePanel(
'changelog_table',
title=_('Recent Activity'),
actions=[
actions.LinkAction(
view_name='core:objectchange_list',
url_params={'user_id': lambda ctx: ctx['object'].pk},
label=_('View All'),
button_icon='arrow-right-thick',
permissions=['core.view_objectchange'],
),
],
),
],
)
template_name = 'users/user.html'
def get_extra_context(self, request, instance):
changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter(user=instance)[:20]
@@ -195,22 +154,7 @@ class GroupListView(generic.ObjectListView):
@register_model_view(Group)
class GroupView(generic.ObjectView):
queryset = Group.objects.all()
layout = layout.SimpleLayout(
left_panels=[
OrganizationalObjectPanel(),
],
right_panels=[
ObjectsTablePanel('users.User', filters={'group_id': lambda ctx: ctx['object'].pk}),
ObjectsTablePanel(
'users.ObjectPermission',
title=_('Assigned Permissions'),
filters={'group_id': lambda ctx: ctx['object'].pk},
),
ObjectsTablePanel(
'users.Owner', title=_('Owner Membership'), filters={'user_group_id': lambda ctx: ctx['object'].pk}
),
],
)
template_name = 'users/group.html'
@register_model_view(Group, 'add', detail=False)
@@ -268,22 +212,7 @@ class ObjectPermissionListView(generic.ObjectListView):
@register_model_view(ObjectPermission)
class ObjectPermissionView(generic.ObjectView):
queryset = ObjectPermission.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.ObjectPermissionPanel(),
panels.ObjectPermissionActionsPanel(),
JSONPanel('constraints', title=_('Constraints')),
],
right_panels=[
TemplatePanel('users/panels/object_types.html'),
ObjectsTablePanel(
'users.User', title=_('Assigned Users'), filters={'permission_id': lambda ctx: ctx['object'].pk}
),
ObjectsTablePanel(
'users.Group', title=_('Assigned Groups'), filters={'permission_id': lambda ctx: ctx['object'].pk}
),
],
)
template_name = 'users/objectpermission.html'
@register_model_view(ObjectPermission, 'add', detail=False)
@@ -326,7 +255,7 @@ class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
@register_model_view(OwnerGroup, 'list', path='', detail=False)
class OwnerGroupListView(generic.ObjectListView):
queryset = OwnerGroup.objects.annotate(
owner_count=count_related(Owner, 'group')
owner_count=count_related(Owner, 'group')
)
filterset = filtersets.OwnerGroupFilterSet
filterset_form = forms.OwnerGroupFilterForm
@@ -334,26 +263,14 @@ class OwnerGroupListView(generic.ObjectListView):
@register_model_view(OwnerGroup)
class OwnerGroupView(generic.ObjectView):
class OwnerGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = OwnerGroup.objects.all()
layout = layout.SimpleLayout(
left_panels=[
OrganizationalObjectPanel(),
],
right_panels=[
ObjectsTablePanel(
'users.Owner',
filters={'group_id': lambda ctx: ctx['object'].pk},
title=_('Members'),
actions=[
actions.AddObject(
'users.Owner',
url_params={'group': lambda ctx: ctx['object'].pk},
),
],
),
],
)
template_name = 'users/ownergroup.html'
def get_extra_context(self, request, instance):
return {
'related_models': self.get_related_models(request, instance),
}
@register_model_view(OwnerGroup, 'add', detail=False)
@@ -409,16 +326,7 @@ class OwnerListView(generic.ObjectListView):
@register_model_view(Owner)
class OwnerView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Owner.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.OwnerPanel(),
ObjectsTablePanel('users.Group', filters={'owner_id': lambda ctx: ctx['object'].pk}),
ObjectsTablePanel('users.User', filters={'owner_id': lambda ctx: ctx['object'].pk}),
],
right_panels=[
RelatedObjectsPanel(),
],
)
template_name = 'users/owner.html'
def get_extra_context(self, request, instance):
return {

View File

@@ -1,51 +0,0 @@
from django.utils.translation import gettext_lazy as _
from netbox.ui import attrs, panels
class WirelessLANGroupPanel(panels.NestedGroupObjectPanel):
pass
class WirelessLANPanel(panels.ObjectAttributesPanel):
ssid = attrs.TextAttr('ssid', label=_('SSID'))
group = attrs.RelatedObjectAttr('group', linkify=True)
status = attrs.ChoiceAttr('status')
scope = attrs.GenericForeignKeyAttr('scope', linkify=True)
description = attrs.TextAttr('description')
vlan = attrs.RelatedObjectAttr('vlan', label=_('VLAN'), linkify=True)
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
class WirelessAuthenticationPanel(panels.ObjectAttributesPanel):
title = _('Authentication')
auth_type = attrs.ChoiceAttr('auth_type', label=_('Type'))
auth_cipher = attrs.ChoiceAttr('auth_cipher', label=_('Cipher'))
auth_psk = attrs.TemplatedAttr('auth_psk', label=_('PSK'), template_name='wireless/attrs/auth_psk.html')
class WirelessLinkInterfacePanel(panels.ObjectPanel):
template_name = 'wireless/panels/wirelesslink_interface.html'
def __init__(self, interface_attr, title, **kwargs):
super().__init__(**kwargs)
self.interface_attr = interface_attr
self.title = title
def get_context(self, context):
obj = context['object']
return {
**super().get_context(context),
'interface': getattr(obj, self.interface_attr),
}
class WirelessLinkPropertiesPanel(panels.ObjectAttributesPanel):
title = _('Link Properties')
status = attrs.ChoiceAttr('status')
ssid = attrs.TextAttr('ssid', label=_('SSID'))
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
description = attrs.TextAttr('description')
distance = attrs.NumericAttr('distance', unit_accessor='get_distance_unit_display')

View File

@@ -1,20 +1,10 @@
from django.utils.translation import gettext_lazy as _
from dcim.models import Interface
from extras.ui.panels import CustomFieldsPanel, TagsPanel
from netbox.ui import actions, layout
from netbox.ui.panels import (
CommentsPanel,
ObjectsTablePanel,
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
#
# Wireless LAN groups
@@ -38,33 +28,6 @@ class WirelessLANGroupListView(generic.ObjectListView):
@register_model_view(WirelessLANGroup)
class WirelessLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = WirelessLANGroup.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.WirelessLANGroupPanel(),
TagsPanel(),
CommentsPanel(),
],
right_panels=[
RelatedObjectsPanel(),
CustomFieldsPanel(),
],
bottom_panels=[
ObjectsTablePanel(
model='wireless.WirelessLANGroup',
title=_('Child Groups'),
filters={'parent_id': lambda ctx: ctx['object'].pk},
actions=[
actions.AddObject(
'wireless.WirelessLANGroup',
label=_('Add Wireless LAN Group'),
url_params={
'parent': lambda ctx: ctx['object'].pk,
}
),
],
),
],
)
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
@@ -142,24 +105,17 @@ class WirelessLANListView(generic.ObjectListView):
@register_model_view(WirelessLAN)
class WirelessLANView(generic.ObjectView):
queryset = WirelessLAN.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.WirelessLANPanel(),
TagsPanel(),
CommentsPanel(),
],
right_panels=[
panels.WirelessAuthenticationPanel(),
CustomFieldsPanel(),
],
bottom_panels=[
ObjectsTablePanel(
model='dcim.Interface',
title=_('Attached Interfaces'),
filters={'wireless_lan_id': lambda ctx: ctx['object'].pk},
),
],
)
def get_extra_context(self, request, instance):
attached_interfaces = Interface.objects.restrict(request.user, 'view').filter(
wireless_lans=instance
)
interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces)
interfaces_table.configure(request)
return {
'interfaces_table': interfaces_table,
}
@register_model_view(WirelessLAN, 'add', detail=False)
@@ -217,19 +173,6 @@ class WirelessLinkListView(generic.ObjectListView):
@register_model_view(WirelessLink)
class WirelessLinkView(generic.ObjectView):
queryset = WirelessLink.objects.all()
layout = layout.SimpleLayout(
left_panels=[
panels.WirelessLinkInterfacePanel('interface_a', title=_('Interface A')),
panels.WirelessLinkPropertiesPanel(),
TagsPanel(),
CommentsPanel(),
],
right_panels=[
panels.WirelessLinkInterfacePanel('interface_b', title=_('Interface B')),
panels.WirelessAuthenticationPanel(),
CustomFieldsPanel(),
],
)
@register_model_view(WirelessLink, 'add', detail=False)