Compare commits
9 Commits
072ddd2d92
...
3ad769f5a6
Author | SHA1 | Date |
---|---|---|
Arthur Hanson | 3ad769f5a6 | |
Tobias Genannt | 5af3c659a5 | |
Arthur Hanson | 4923025fec | |
Arthur Hanson | ded2fe9471 | |
Jeremy Stretch | e05ca710ae | |
Arthur | fcce7b7bf4 | |
Arthur | 510aa2156d | |
Arthur | 4b436c9d4f | |
Arthur | 5429bf651d |
|
@ -1002,6 +1002,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assigned component selectors
|
# Assigned component selectors
|
||||||
consoleporttemplate = DynamicModelChoiceField(
|
consoleporttemplate = DynamicModelChoiceField(
|
||||||
queryset=ConsolePortTemplate.objects.all(),
|
queryset=ConsolePortTemplate.objects.all(),
|
||||||
|
@ -1063,8 +1064,19 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
'component_type', 'component_id',
|
|
||||||
),
|
),
|
||||||
|
FieldSet(
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('interfacetemplate', name=_('Interface')),
|
||||||
|
FieldSet('consoleporttemplate', name=_('Console Port')),
|
||||||
|
FieldSet('consoleserverporttemplate', name=_('Console Server Port')),
|
||||||
|
FieldSet('frontporttemplate', name=_('Front Port')),
|
||||||
|
FieldSet('rearporttemplate', name=_('Rear Port')),
|
||||||
|
FieldSet('powerporttemplate', name=_('Power Port')),
|
||||||
|
FieldSet('poweroutlettemplate', name=_('Power Outlet')),
|
||||||
|
),
|
||||||
|
name=_('Component Assignment')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1079,22 +1091,17 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
component_type = initial.get('component_type')
|
component_type = initial.get('component_type')
|
||||||
component_id = initial.get('component_id')
|
component_id = initial.get('component_id')
|
||||||
|
|
||||||
# Used for picking the default active tab for component selection
|
|
||||||
self.no_component = True
|
|
||||||
|
|
||||||
if instance:
|
if instance:
|
||||||
# When editing set the initial value for component selection
|
# When editing set the initial value for component selection
|
||||||
for component_model in ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS):
|
for component_model in ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS):
|
||||||
if type(instance.component) is component_model.model_class():
|
if type(instance.component) is component_model.model_class():
|
||||||
initial[component_model.model] = instance.component
|
initial[component_model.model] = instance.component
|
||||||
self.no_component = False
|
|
||||||
break
|
break
|
||||||
elif component_type and component_id:
|
elif component_type and component_id:
|
||||||
# When adding the InventoryItem from a component page
|
# When adding the InventoryItem from a component page
|
||||||
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first():
|
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first():
|
||||||
if component := content_type.model_class().objects.filter(pk=component_id).first():
|
if component := content_type.model_class().objects.filter(pk=component_id).first():
|
||||||
initial[content_type.model] = component
|
initial[content_type.model] = component
|
||||||
self.no_component = False
|
|
||||||
|
|
||||||
kwargs['initial'] = initial
|
kwargs['initial'] = initial
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ class CableTerminationType(NetBoxObjectType):
|
||||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
], strawberry.union("CableTerminationTerminationType")]
|
], strawberry.union("CableTerminationTerminationType")] | None
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
|
@ -302,7 +302,7 @@ class InventoryItemTemplateType(ComponentTemplateType):
|
||||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
], strawberry.union("InventoryItemTemplateComponentType")]
|
], strawberry.union("InventoryItemTemplateComponentType")] | None
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
|
@ -431,7 +431,7 @@ class InventoryItemType(ComponentType):
|
||||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||||
], strawberry.union("InventoryItemComponentType")]
|
], strawberry.union("InventoryItemComponentType")] | None
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
|
|
|
@ -1655,7 +1655,6 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = InventoryItemTemplate.objects.all()
|
queryset = InventoryItemTemplate.objects.all()
|
||||||
form = forms.InventoryItemTemplateCreateForm
|
form = forms.InventoryItemTemplateCreateForm
|
||||||
model_form = forms.InventoryItemTemplateForm
|
model_form = forms.InventoryItemTemplateForm
|
||||||
template_name = 'dcim/inventoryitemtemplate_edit.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
# Set component (if any)
|
# Set component (if any)
|
||||||
|
@ -1673,7 +1672,6 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
||||||
class InventoryItemTemplateEditView(generic.ObjectEditView):
|
class InventoryItemTemplateEditView(generic.ObjectEditView):
|
||||||
queryset = InventoryItemTemplate.objects.all()
|
queryset = InventoryItemTemplate.objects.all()
|
||||||
form = forms.InventoryItemTemplateForm
|
form = forms.InventoryItemTemplateForm
|
||||||
template_name = 'dcim/inventoryitemtemplate_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(InventoryItemTemplate, 'delete')
|
@register_model_view(InventoryItemTemplate, 'delete')
|
||||||
|
|
|
@ -133,7 +133,7 @@ class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')],
|
Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')],
|
||||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||||
], strawberry.union("IPAddressAssignmentType")]:
|
], strawberry.union("IPAddressAssignmentType")] | None:
|
||||||
return self.assigned_object
|
return self.assigned_object
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ class VLANGroupType(OrganizationalObjectType):
|
||||||
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
||||||
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
||||||
], strawberry.union("VLANGroupScopeType")]:
|
], strawberry.union("VLANGroupScopeType")] | None:
|
||||||
return self.scope
|
return self.scope
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ PREFERENCES = {
|
||||||
('dark', _('Dark')),
|
('dark', _('Dark')),
|
||||||
),
|
),
|
||||||
default='light',
|
default='light',
|
||||||
|
description=_('Preferred default UI theme')
|
||||||
),
|
),
|
||||||
'ui.htmx_navigation': UserPreference(
|
'ui.htmx_navigation': UserPreference(
|
||||||
label=_('HTMX Navigation'),
|
label=_('HTMX Navigation'),
|
||||||
|
@ -29,14 +30,17 @@ PREFERENCES = {
|
||||||
('', _('Disabled')),
|
('', _('Disabled')),
|
||||||
('true', _('Enabled')),
|
('true', _('Enabled')),
|
||||||
),
|
),
|
||||||
default=False
|
description=_('Enable dynamic UI navigation'),
|
||||||
|
default=False,
|
||||||
|
experimental=True
|
||||||
),
|
),
|
||||||
'locale.language': UserPreference(
|
'locale.language': UserPreference(
|
||||||
label=_('Language'),
|
label=_('Language'),
|
||||||
choices=(
|
choices=(
|
||||||
('', _('Auto')),
|
('', _('Auto')),
|
||||||
*settings.LANGUAGES,
|
*settings.LANGUAGES,
|
||||||
)
|
),
|
||||||
|
description=_('Forces UI translation to the specified language.')
|
||||||
),
|
),
|
||||||
'pagination.per_page': UserPreference(
|
'pagination.per_page': UserPreference(
|
||||||
label=_('Page length'),
|
label=_('Page length'),
|
||||||
|
@ -51,8 +55,8 @@ PREFERENCES = {
|
||||||
('top', _('Top')),
|
('top', _('Top')),
|
||||||
('both', _('Both')),
|
('both', _('Both')),
|
||||||
),
|
),
|
||||||
description=_('Where the paginator controls will be displayed relative to a table'),
|
default='bottom',
|
||||||
default='bottom'
|
description=_('Where the paginator controls will be displayed relative to a table')
|
||||||
),
|
),
|
||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
|
@ -62,6 +66,7 @@ PREFERENCES = {
|
||||||
('json', 'JSON'),
|
('json', 'JSON'),
|
||||||
('yaml', 'YAML'),
|
('yaml', 'YAML'),
|
||||||
),
|
),
|
||||||
|
description=_('The preferred syntax for displaying generic data within the UI')
|
||||||
),
|
),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -477,11 +477,11 @@ SERIALIZATION_MODULES = {
|
||||||
# Exclude potentially sensitive models from wildcard view exemption. These may still be exempted
|
# Exclude potentially sensitive models from wildcard view exemption. These may still be exempted
|
||||||
# by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
|
# by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
|
||||||
EXEMPT_EXCLUDE_MODELS = (
|
EXEMPT_EXCLUDE_MODELS = (
|
||||||
('auth', 'group'),
|
|
||||||
('auth', 'user'),
|
|
||||||
('extras', 'configrevision'),
|
('extras', 'configrevision'),
|
||||||
|
('users', 'group'),
|
||||||
('users', 'objectpermission'),
|
('users', 'objectpermission'),
|
||||||
('users', 'token'),
|
('users', 'token'),
|
||||||
|
('users', 'user'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# All URLs starting with a string listed here are exempt from login enforcement
|
# All URLs starting with a string listed here are exempt from login enforcement
|
||||||
|
|
|
@ -113,6 +113,7 @@ async function bundleStyles() {
|
||||||
'netbox': 'styles/netbox.scss',
|
'netbox': 'styles/netbox.scss',
|
||||||
rack_elevation: 'styles/svg/rack_elevation.scss',
|
rack_elevation: 'styles/svg/rack_elevation.scss',
|
||||||
cable_trace: 'styles/svg/cable_trace.scss',
|
cable_trace: 'styles/svg/cable_trace.scss',
|
||||||
|
'rest-api': 'styles/rest_api.scss',
|
||||||
};
|
};
|
||||||
const pluginOptions = { outputStyle: 'compressed' };
|
const pluginOptions = { outputStyle: 'compressed' };
|
||||||
// Allow cache disabling.
|
// Allow cache disabling.
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.navbar-default {
|
||||||
|
background-color: #1f2e41;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.navbar-default .navbar-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.navbar>.container .navbar-brand, .navbar>.container-fluid .navbar-brand {
|
||||||
|
padding: 12px 0 12px 0;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
.prettyprint {
|
||||||
|
background-color: #f6f8fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
|
@ -39,30 +39,32 @@
|
||||||
{% trans "Clear table preferences" %}
|
{% trans "Clear table preferences" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<table class="table table-hover object-list">
|
<div class="card">
|
||||||
<thead>
|
<table class="table table-hover object-list">
|
||||||
<tr>
|
<thead>
|
||||||
<th>
|
|
||||||
<input type="checkbox" class="toggle form-check-input" title="{% trans "Toggle All" %}">
|
|
||||||
</th>
|
|
||||||
<th>{% trans "Table" %}</th>
|
|
||||||
<th>{% trans "Ordering" %}</th>
|
|
||||||
<th>{% trans "Columns" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for table, prefs in request.user.config.data.tables.items %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<th>
|
||||||
<input type="checkbox" name="pk" value="tables.{{ table }}" class="form-check-input" />
|
<input type="checkbox" class="toggle form-check-input" title="{% trans "Toggle All" %}">
|
||||||
</td>
|
</th>
|
||||||
<td>{{ table }}</td>
|
<th>{% trans "Table" %}</th>
|
||||||
<td>{{ prefs.ordering|join:", "|placeholder }}</td>
|
<th>{% trans "Ordering" %}</th>
|
||||||
<td>{{ prefs.columns|join:", "|placeholder }}</td>
|
<th>{% trans "Columns" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{% for table, prefs in request.user.config.data.tables.items %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="pk" value="tables.{{ table }}" class="form-check-input" />
|
||||||
|
</td>
|
||||||
|
<td>{{ table }}</td>
|
||||||
|
<td>{{ prefs.ordering|join:", "|placeholder }}</td>
|
||||||
|
<td>{{ prefs.columns|join:", "|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="col-9 offset-3">
|
<div class="col-9 offset-3">
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">{% trans "Inventory Item" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.device_type %}
|
|
||||||
{% render_field form.parent %}
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.label %}
|
|
||||||
{% render_field form.role %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">{% trans "Hardware" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.manufacturer %}
|
|
||||||
{% render_field form.part_id %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">{% trans "Component Assignment" %}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2 offset-sm-3">
|
|
||||||
<ul class="nav nav-pills" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="consoleport_tab" data-bs-toggle="tab" aria-controls="consoleport" data-bs-target="#consoleport" class="nav-link {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}">
|
|
||||||
{% trans "Console Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="consoleserverport_tab" data-bs-toggle="tab" aria-controls="consoleserverport" data-bs-target="#consoleserverport" class="nav-link {% if form.initial.consoleserverporttemplate %}active{% endif %}">
|
|
||||||
{% trans "Console Server Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="frontport_tab" data-bs-toggle="tab" aria-controls="frontport" data-bs-target="#frontport" class="nav-link {% if form.initial.frontporttemplate %}active{% endif %}">
|
|
||||||
{% trans "Front Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="interface_tab" data-bs-toggle="tab" aria-controls="interface" data-bs-target="#interface" class="nav-link {% if form.initial.interfacetemplate %}active{% endif %}">
|
|
||||||
{% trans "Interface" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="poweroutlet_tab" data-bs-toggle="tab" aria-controls="poweroutlet" data-bs-target="#poweroutlet" class="nav-link {% if form.initial.poweroutlettemplate %}active{% endif %}">
|
|
||||||
{% trans "Power Outlet" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="powerport_tab" data-bs-toggle="tab" aria-controls="powerport" data-bs-target="#powerport" class="nav-link {% if form.initial.powerporttemplate %}active{% endif %}">
|
|
||||||
{% trans "Power Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="rearport_tab" data-bs-toggle="tab" aria-controls="rearport" data-bs-target="#rearport" class="nav-link {% if form.initial.rearporttemplate %}active{% endif %}">
|
|
||||||
{% trans "Rear Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane {% if form.initial.consoleporttemplate or form.no_component %}active{% endif %}" id="consoleport" role="tabpanel" aria-labeled-by="consoleport_tab">
|
|
||||||
{% render_field form.consoleporttemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.consoleserverporttemplate %}active{% endif %}" id="consoleserverport" role="tabpanel" aria-labeled-by="consoleserverport_tab">
|
|
||||||
{% render_field form.consoleserverporttemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.frontporttemplate %}active{% endif %}" id="frontport" role="tabpanel" aria-labeled-by="frontport_tab">
|
|
||||||
{% render_field form.frontporttemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.interfacetemplate %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
|
|
||||||
{% render_field form.interfacetemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.poweroutlettemplate %}active{% endif %}" id="poweroutlet" role="tabpanel" aria-labeled-by="poweroutlet_tab">
|
|
||||||
{% render_field form.poweroutlettemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.powerporttemplate %}active{% endif %}" id="powerport" role="tabpanel" aria-labeled-by="powerport_tab">
|
|
||||||
{% render_field form.powerporttemplate %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.rearporttemplate %}active{% endif %}" id="rearport" role="tabpanel" aria-labeled-by="rearport_tab">
|
|
||||||
{% render_field form.rearporttemplate %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">{% trans "Custom Fields" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -2,6 +2,13 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block bootstrap_theme %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "rest-api.css" %}"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bootstrap_navbar_variant %}navbar-default{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="icon" type="image/png" href="{% static 'rest-api.ico' %}" />
|
<link rel="icon" type="image/png" href="{% static 'rest-api.ico' %}" />
|
||||||
|
@ -10,5 +17,7 @@
|
||||||
{% block title %}{% if name %}{{ name }} | {% endif %}NetBox {% trans "REST API" %}{% endblock %}
|
{% block title %}{% if name %}{{ name }} | {% endif %}NetBox {% trans "REST API" %}{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block branding %}
|
||||||
<a class="navbar-brand" href="{% url 'home' %}">NetBox</a>
|
<a class="navbar-brand" href="{% url 'home' %}">
|
||||||
|
<img src="{% static 'netbox_logo.svg' %}" height="32" alt="{% trans "NetBox Logo" %}" class="navbar-brand-image">
|
||||||
|
</a>
|
||||||
{% endblock branding %}
|
{% endblock branding %}
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.utils.html import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
|
@ -37,7 +37,14 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
|
||||||
preference_fields = {}
|
preference_fields = {}
|
||||||
for field_name, preference in PREFERENCES.items():
|
for field_name, preference in PREFERENCES.items():
|
||||||
description = f'{preference.description}<br />' if preference.description else ''
|
description = f'{preference.description}<br />' if preference.description else ''
|
||||||
help_text = f'{description}<code>{field_name}</code>'
|
help_text = f'<code>{field_name}</code>'
|
||||||
|
if preference.description:
|
||||||
|
help_text = f'{preference.description}<br />{help_text}'
|
||||||
|
if preference.experimental:
|
||||||
|
help_text = (
|
||||||
|
f'<span class="text-danger"><i class="mdi mdi-alert"></i> Experimental feature</span><br />'
|
||||||
|
f'{help_text}'
|
||||||
|
)
|
||||||
field_kwargs = {
|
field_kwargs = {
|
||||||
'label': preference.label,
|
'label': preference.label,
|
||||||
'choices': preference.choices,
|
'choices': preference.choices,
|
||||||
|
|
|
@ -2,9 +2,10 @@ class UserPreference:
|
||||||
"""
|
"""
|
||||||
Represents a configurable user preference.
|
Represents a configurable user preference.
|
||||||
"""
|
"""
|
||||||
def __init__(self, label, choices, default=None, description='', coerce=lambda x: x):
|
def __init__(self, label, choices, default=None, description='', coerce=lambda x: x, experimental=False):
|
||||||
self.label = label
|
self.label = label
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
self.default = default if default is not None else choices[0]
|
self.default = default if default is not None else choices[0]
|
||||||
self.description = description
|
self.description = description
|
||||||
self.coerce = coerce
|
self.coerce = coerce
|
||||||
|
self.experimental = experimental
|
||||||
|
|
|
@ -469,6 +469,9 @@ class APIViewTestCases:
|
||||||
elif type(field.type) is StrawberryUnion:
|
elif type(field.type) is StrawberryUnion:
|
||||||
# this would require a fragment query
|
# this would require a fragment query
|
||||||
continue
|
continue
|
||||||
|
elif type(field.type) is StrawberryOptional and type(field.type.of_type) is StrawberryUnion:
|
||||||
|
# this would require a fragment query
|
||||||
|
continue
|
||||||
elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
|
elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
|
||||||
fields_string += f'{field.name} {{ id }}\n'
|
fields_string += f'{field.name} {{ id }}\n'
|
||||||
elif hasattr(field, 'is_relation') and field.is_relation:
|
elif hasattr(field, 'is_relation') and field.is_relation:
|
||||||
|
|
|
@ -151,7 +151,7 @@ class ViewTestCases:
|
||||||
with disable_warnings('django.request'):
|
with disable_warnings('django.request'):
|
||||||
self.assertHttpStatus(response, 403)
|
self.assertHttpStatus(response, 403)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_create_object_with_permission(self):
|
def test_create_object_with_permission(self):
|
||||||
|
|
||||||
# Assign unconstrained permission
|
# Assign unconstrained permission
|
||||||
|
@ -190,7 +190,7 @@ class ViewTestCases:
|
||||||
self.assertEqual(len(objectchanges), 1)
|
self.assertEqual(len(objectchanges), 1)
|
||||||
self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE)
|
self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_create_object_with_constrained_permission(self):
|
def test_create_object_with_constrained_permission(self):
|
||||||
|
|
||||||
# Assign constrained permission
|
# Assign constrained permission
|
||||||
|
@ -253,7 +253,7 @@ class ViewTestCases:
|
||||||
with disable_warnings('django.request'):
|
with disable_warnings('django.request'):
|
||||||
self.assertHttpStatus(self.client.post(**request), 403)
|
self.assertHttpStatus(self.client.post(**request), 403)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_edit_object_with_permission(self):
|
def test_edit_object_with_permission(self):
|
||||||
instance = self._get_queryset().first()
|
instance = self._get_queryset().first()
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ class ViewTestCases:
|
||||||
self.assertEqual(len(objectchanges), 1)
|
self.assertEqual(len(objectchanges), 1)
|
||||||
self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE)
|
self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_edit_object_with_constrained_permission(self):
|
def test_edit_object_with_constrained_permission(self):
|
||||||
instance1, instance2 = self._get_queryset().all()[:2]
|
instance1, instance2 = self._get_queryset().all()[:2]
|
||||||
|
|
||||||
|
@ -602,7 +602,7 @@ class ViewTestCases:
|
||||||
with disable_warnings('django.request'):
|
with disable_warnings('django.request'):
|
||||||
self.assertHttpStatus(response, 403)
|
self.assertHttpStatus(response, 403)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_bulk_import_objects_with_permission(self):
|
def test_bulk_import_objects_with_permission(self):
|
||||||
initial_count = self._get_queryset().count()
|
initial_count = self._get_queryset().count()
|
||||||
data = {
|
data = {
|
||||||
|
@ -665,7 +665,7 @@ class ViewTestCases:
|
||||||
if value is not None and not isinstance(field, ForeignKey):
|
if value is not None and not isinstance(field, ForeignKey):
|
||||||
self.assertEqual(value, value)
|
self.assertEqual(value, value)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_bulk_import_objects_with_constrained_permission(self):
|
def test_bulk_import_objects_with_constrained_permission(self):
|
||||||
initial_count = self._get_queryset().count()
|
initial_count = self._get_queryset().count()
|
||||||
data = {
|
data = {
|
||||||
|
@ -720,7 +720,7 @@ class ViewTestCases:
|
||||||
with disable_warnings('django.request'):
|
with disable_warnings('django.request'):
|
||||||
self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 403)
|
self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 403)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_bulk_edit_objects_with_permission(self):
|
def test_bulk_edit_objects_with_permission(self):
|
||||||
pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
|
pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
|
||||||
data = {
|
data = {
|
||||||
|
@ -745,7 +745,7 @@ class ViewTestCases:
|
||||||
for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
|
for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
|
||||||
self.assertInstanceEqual(instance, self.bulk_edit_data)
|
self.assertInstanceEqual(instance, self.bulk_edit_data)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||||
def test_bulk_edit_objects_with_constrained_permission(self):
|
def test_bulk_edit_objects_with_constrained_permission(self):
|
||||||
pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
|
pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
|
||||||
data = {
|
data = {
|
||||||
|
|
Loading…
Reference in New Issue