mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 16:48:16 -06:00
Merge branch 'netbox-community:develop' into script_rq_queue_name
This commit is contained in:
commit
e2f2799f73
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,3 +28,4 @@ netbox.pid
|
||||
.idea
|
||||
.coverage
|
||||
.vscode
|
||||
.python-version
|
||||
|
@ -24,7 +24,7 @@ If you believe you've uncovered a security vulnerability and wish to report it c
|
||||
|
||||
Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous.
|
||||
|
||||
If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/netbox-community/netbox/security/advisories/new) on GitHub, or email a brief description of the suspected bug and instructions for reproduction to **security@netbox.dev**. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project.
|
||||
If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/netbox-community/netbox/security/advisories/new) on GitHub. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project.
|
||||
|
||||
### Bug Bounties
|
||||
|
||||
|
@ -2,6 +2,26 @@
|
||||
|
||||
## v4.0.6 (FUTURE)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#15348](https://github.com/netbox-community/netbox/issues/15348) - Show saved filters alongside quick search on object list views
|
||||
* [#15794](https://github.com/netbox-community/netbox/issues/15794) - Dynamically populate related objects in UI views
|
||||
* [#16256](https://github.com/netbox-community/netbox/issues/16256) - Enable alphabetical ordering of bookmarks on dashboard
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#13925](https://github.com/netbox-community/netbox/issues/13925) - Fix support for "zulu" (UTC) timestamps for custom fields
|
||||
* [#14829](https://github.com/netbox-community/netbox/issues/14829) - Fix support for simple conditions (without AND/OR) in event rules
|
||||
* [#16143](https://github.com/netbox-community/netbox/issues/16143) - Display timestamps in tables in the configured timezone
|
||||
* [#16416](https://github.com/netbox-community/netbox/issues/16416) - Retain dark/light mode toggle on mobile view
|
||||
* [#16444](https://github.com/netbox-community/netbox/issues/16444) - Disable ordering circuits list by A/Z termination
|
||||
* [#16450](https://github.com/netbox-community/netbox/issues/16450) - Searching for rack unit in form dropdown should be case-insensitive
|
||||
* [#16452](https://github.com/netbox-community/netbox/issues/16452) - Fix sizing of buttons within object attribute panels
|
||||
* [#16454](https://github.com/netbox-community/netbox/issues/16454) - Address DNS lookup bug in `django-debug-toolbar
|
||||
* [#16460](https://github.com/netbox-community/netbox/issues/16460) - Omit spaces from telephone number URLs
|
||||
* [#16512](https://github.com/netbox-community/netbox/issues/16512) - Restore a user's preferred language (if any) on login
|
||||
* [#16542](https://github.com/netbox-community/netbox/issues/16542) - Fix bulk form operations when HTMX is enabled
|
||||
|
||||
---
|
||||
|
||||
## v4.0.5 (2024-06-06)
|
||||
|
@ -104,10 +104,16 @@ class LoginView(View):
|
||||
# Ensure the user has a UserConfig defined. (This should normally be handled by
|
||||
# create_userconfig() on user creation.)
|
||||
if not hasattr(request.user, 'config'):
|
||||
config = get_config()
|
||||
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
|
||||
request.user.config = get_config()
|
||||
UserConfig(user=request.user, data=request.user.config.DEFAULT_USER_PREFERENCES).save()
|
||||
|
||||
return self.redirect_to_next(request, logger)
|
||||
response = self.redirect_to_next(request, logger)
|
||||
|
||||
# Set the user's preferred language (if any)
|
||||
if language := request.user.config.get('locale.language'):
|
||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
||||
|
||||
return response
|
||||
|
||||
else:
|
||||
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
|
||||
@ -145,9 +151,10 @@ class LogoutView(View):
|
||||
logger.info(f"User {username} has logged out")
|
||||
messages.info(request, "You have logged out.")
|
||||
|
||||
# Delete session key cookie (if set) upon logout
|
||||
# Delete session key & language cookies (if set) upon logout
|
||||
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
|
||||
response.delete_cookie('session_key')
|
||||
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)
|
||||
|
||||
return response
|
||||
|
||||
|
@ -63,10 +63,12 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_a = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
orderable=False,
|
||||
verbose_name=_('Side A')
|
||||
)
|
||||
termination_z = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
orderable=False,
|
||||
verbose_name=_('Side Z')
|
||||
)
|
||||
commit_rate = CommitRateColumn(
|
||||
|
@ -219,9 +219,9 @@ class RackViewSet(NetBoxModelViewSet):
|
||||
)
|
||||
|
||||
# Enable filtering rack units by ID
|
||||
q = data['q']
|
||||
if q:
|
||||
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
|
||||
if q := data['q']:
|
||||
q = q.lower()
|
||||
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name']).lower()]
|
||||
|
||||
page = self.paginate_queryset(elevation)
|
||||
if page is not None:
|
||||
|
@ -465,7 +465,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
label=_('Cluster'),
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
selector=True
|
||||
selector=True,
|
||||
query_params={
|
||||
'site_id': ['$site', 'null']
|
||||
},
|
||||
)
|
||||
comments = CommentField()
|
||||
local_context_data = JSONField(
|
||||
|
@ -8,6 +8,7 @@ from dcim.models import *
|
||||
from extras.models import CustomField
|
||||
from tenancy.models import Tenant
|
||||
from utilities.data import drange
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
|
||||
|
||||
class LocationTestCase(TestCase):
|
||||
@ -533,6 +534,36 @@ class DeviceTestCase(TestCase):
|
||||
device2.full_clean()
|
||||
device2.save()
|
||||
|
||||
def test_device_mismatched_site_cluster(self):
|
||||
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
Cluster.objects.create(name='Cluster 1', type=cluster_type)
|
||||
|
||||
sites = (
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
clusters = (
|
||||
Cluster(name='Cluster 1', type=cluster_type, site=sites[0]),
|
||||
Cluster(name='Cluster 2', type=cluster_type, site=sites[1]),
|
||||
Cluster(name='Cluster 3', type=cluster_type, site=None),
|
||||
)
|
||||
Cluster.objects.bulk_create(clusters)
|
||||
|
||||
device_type = DeviceType.objects.first()
|
||||
device_role = DeviceRole.objects.first()
|
||||
|
||||
# Device with site only should pass
|
||||
Device(name='device1', site=sites[0], device_type=device_type, role=device_role).full_clean()
|
||||
|
||||
# Device with site, cluster non-site should pass
|
||||
Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean()
|
||||
|
||||
# Device with mismatched site & cluster should fail
|
||||
with self.assertRaises(ValidationError):
|
||||
Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean()
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
|
@ -660,6 +660,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
# Validate date & time
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
|
||||
if type(value) is not datetime:
|
||||
# Work around UTC issue for Python < 3.11; see
|
||||
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
|
||||
if type(value) is str and value.endswith('Z'):
|
||||
value = f'{value[:-1]}+00:00'
|
||||
try:
|
||||
datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
|
BIN
netbox/project-static/dist/netbox.css
vendored
BIN
netbox/project-static/dist/netbox.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
30
netbox/project-static/src/forms/savedFiltersSelect.ts
Normal file
30
netbox/project-static/src/forms/savedFiltersSelect.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { isTruthy } from '../util';
|
||||
|
||||
/**
|
||||
* Handle saved filter change event.
|
||||
*
|
||||
* @param event "change" event for the saved filter select
|
||||
*/
|
||||
function handleSavedFilterChange(event: Event): void {
|
||||
const savedFilter = event.currentTarget as HTMLSelectElement;
|
||||
let baseUrl = savedFilter.baseURI.split('?')[0];
|
||||
const preFilter = '?';
|
||||
|
||||
const selectedOptions = Array.from(savedFilter.options)
|
||||
.filter(option => option.selected)
|
||||
.map(option => `filter_id=${option.value}`)
|
||||
.join('&');
|
||||
|
||||
baseUrl += `${preFilter}${selectedOptions}`;
|
||||
document.location.href = baseUrl;
|
||||
}
|
||||
|
||||
export function initSavedFilterSelect(): void {
|
||||
const divResults = document.getElementById('results');
|
||||
if (isTruthy(divResults)) {
|
||||
const savedFilterSelect = document.getElementById('id_filter_id');
|
||||
if (isTruthy(savedFilterSelect)) {
|
||||
savedFilterSelect.addEventListener('change', handleSavedFilterChange);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import { initSideNav } from './sidenav';
|
||||
import { initDashboard } from './dashboard';
|
||||
import { initRackElevation } from './racks';
|
||||
import { initHtmx } from './htmx';
|
||||
import { initSavedFilterSelect } from './forms/savedFiltersSelect';
|
||||
|
||||
function initDocument(): void {
|
||||
for (const init of [
|
||||
@ -31,6 +32,7 @@ function initDocument(): void {
|
||||
initDashboard,
|
||||
initRackElevation,
|
||||
initHtmx,
|
||||
initSavedFilterSelect,
|
||||
]) {
|
||||
init();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
// Overrides of external libraries
|
||||
@import 'overrides/bootstrap';
|
||||
@import 'overrides/tabler';
|
||||
@import 'overrides/tomselect';
|
||||
|
||||
// Transitional styling to ease migration of templates from NetBox v3.x
|
||||
@import 'transitional/badges';
|
||||
|
8
netbox/project-static/styles/overrides/_tomselect.scss
Normal file
8
netbox/project-static/styles/overrides/_tomselect.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.ts-wrapper.multi {
|
||||
.ts-control {
|
||||
padding: 7px 7px 3px 7px;
|
||||
div {
|
||||
margin: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ Blocks:
|
||||
|
||||
{# User menu (mobile view) #}
|
||||
<div class="navbar-nav flex-row d-lg-none">
|
||||
{% include 'inc/light_toggle.html' %}
|
||||
{% include 'inc/user_menu.html' %}
|
||||
</div>
|
||||
|
||||
@ -52,14 +53,7 @@ Blocks:
|
||||
|
||||
<div class="navbar-nav flex-row align-items-center order-md-last">
|
||||
{# Dark/light mode toggle #}
|
||||
<div class="d-none d-md-flex">
|
||||
<button class="btn color-mode-toggle hide-theme-dark" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb"></i>
|
||||
</button>
|
||||
<button class="btn color-mode-toggle hide-theme-light" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb-on"></i>
|
||||
</button>
|
||||
</div>
|
||||
{% include 'inc/light_toggle.html' %}
|
||||
|
||||
{# User menu #}
|
||||
{% include 'inc/user_menu.html' %}
|
||||
|
@ -28,7 +28,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Rack" %}</th>
|
||||
<td class="d-flex justify-content-between">
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
{% if object.rack %}
|
||||
{{ object.rack|linkify }}
|
||||
<a href="{{ object.rack.get_absolute_url }}?device={{ object.pk }}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
||||
|
@ -73,7 +73,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Physical Address" %}</th>
|
||||
<td class="d-flex justify-content-between">
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
{% if object.physical_address %}
|
||||
<span>{{ object.physical_address|linebreaksbr }}</span>
|
||||
{% if config.MAPS_URL %}
|
||||
|
10
netbox/templates/inc/light_toggle.html
Normal file
10
netbox/templates/inc/light_toggle.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="d-flex">
|
||||
<button class="btn color-mode-toggle hide-theme-dark" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb"></i>
|
||||
</button>
|
||||
<button class="btn color-mode-toggle hide-theme-light" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb-on"></i>
|
||||
</button>
|
||||
</div>
|
@ -1,21 +1,32 @@
|
||||
{% load helpers %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3" id="results">
|
||||
<div class="col-auto d-print-none">
|
||||
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
|
||||
<input type="search" results="5" name="q" id="quicksearch" class="form-control px-2 py-1" placeholder="Quick search"
|
||||
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
|
||||
<input type="search" results="5" name="q" id="quicksearch" class="form-control" placeholder="{% trans "Quick search" %}"
|
||||
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
|
||||
<span class="input-group-text py-1">
|
||||
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
|
||||
</span>
|
||||
{% block extra_table_controls %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-print-none">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="mdi mdi-filter" title="{% trans "Saved filter" %}"></i>
|
||||
</div>
|
||||
{{ filter_form.filter_id }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-auto d-print-none">
|
||||
{% if request.user.is_authenticated and table_modal %}
|
||||
<div class="table-configure input-group">
|
||||
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}" data-bs-target="#{{ table_modal }}"
|
||||
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}"
|
||||
data-bs-target="#{{ table_modal }}"
|
||||
class="btn">
|
||||
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
|
||||
</button>
|
||||
@ -23,3 +34,4 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-06-12 05:01+0000\n"
|
||||
"POT-Creation-Date: 2024-06-19 05:02+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"
|
||||
@ -58,7 +58,7 @@ msgstr ""
|
||||
msgid "Allowed IPs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/account/views.py:197
|
||||
#: netbox/account/views.py:204
|
||||
msgid "Your preferences have been updated."
|
||||
msgstr ""
|
||||
|
||||
@ -158,7 +158,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/filtersets.py:207
|
||||
#: netbox/circuits/forms/model_forms.py:136
|
||||
#: netbox/circuits/forms/model_forms.py:152
|
||||
#: netbox/circuits/tables/circuits.py:105 netbox/dcim/forms/bulk_edit.py:167
|
||||
#: netbox/circuits/tables/circuits.py:107 netbox/dcim/forms/bulk_edit.py:167
|
||||
#: netbox/dcim/forms/bulk_edit.py:239 netbox/dcim/forms/bulk_edit.py:575
|
||||
#: netbox/dcim/forms/bulk_edit.py:771 netbox/dcim/forms/bulk_import.py:130
|
||||
#: netbox/dcim/forms/bulk_import.py:184 netbox/dcim/forms/bulk_import.py:257
|
||||
@ -308,7 +308,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/filtersets.py:212
|
||||
#: netbox/circuits/forms/model_forms.py:109
|
||||
#: netbox/circuits/forms/model_forms.py:131
|
||||
#: netbox/circuits/tables/circuits.py:96 netbox/dcim/forms/connections.py:71
|
||||
#: netbox/circuits/tables/circuits.py:98 netbox/dcim/forms/connections.py:71
|
||||
#: netbox/templates/circuits/circuit.html:15
|
||||
#: netbox/templates/circuits/circuittermination.html:19
|
||||
#: netbox/templates/dcim/inc/cable_termination.html:55
|
||||
@ -325,7 +325,7 @@ msgstr ""
|
||||
#: netbox/circuits/tables/providers.py:33 netbox/dcim/forms/bulk_edit.py:127
|
||||
#: netbox/dcim/forms/filtersets.py:188 netbox/dcim/forms/model_forms.py:122
|
||||
#: netbox/dcim/tables/sites.py:94 netbox/ipam/models/asns.py:126
|
||||
#: netbox/ipam/tables/asn.py:27 netbox/ipam/views.py:219
|
||||
#: netbox/ipam/tables/asn.py:27 netbox/ipam/views.py:210
|
||||
#: netbox/netbox/navigation/menu.py:159 netbox/netbox/navigation/menu.py:162
|
||||
#: netbox/templates/circuits/provider.html:23
|
||||
msgid "ASNs"
|
||||
@ -469,7 +469,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/model_forms.py:45
|
||||
#: netbox/circuits/forms/model_forms.py:59
|
||||
#: netbox/circuits/forms/model_forms.py:91
|
||||
#: netbox/circuits/tables/circuits.py:56 netbox/circuits/tables/circuits.py:100
|
||||
#: netbox/circuits/tables/circuits.py:56 netbox/circuits/tables/circuits.py:102
|
||||
#: netbox/circuits/tables/providers.py:72
|
||||
#: netbox/circuits/tables/providers.py:103
|
||||
#: netbox/templates/circuits/circuit.html:18
|
||||
@ -748,7 +748,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/bulk_edit.py:191
|
||||
#: netbox/circuits/forms/bulk_edit.py:215
|
||||
#: netbox/circuits/forms/model_forms.py:153
|
||||
#: netbox/circuits/tables/circuits.py:109
|
||||
#: netbox/circuits/tables/circuits.py:111
|
||||
#: netbox/templates/circuits/inc/circuit_termination_fields.html:62
|
||||
#: netbox/templates/circuits/providernetwork.html:17
|
||||
msgid "Provider Network"
|
||||
@ -895,7 +895,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/filtersets.py:653 netbox/dcim/forms/filtersets.py:1010
|
||||
#: netbox/netbox/navigation/menu.py:44 netbox/netbox/navigation/menu.py:46
|
||||
#: netbox/tenancy/forms/filtersets.py:42 netbox/tenancy/tables/columns.py:70
|
||||
#: netbox/tenancy/tables/contacts.py:25 netbox/tenancy/views.py:19
|
||||
#: netbox/tenancy/tables/contacts.py:25 netbox/tenancy/views.py:18
|
||||
#: netbox/virtualization/forms/filtersets.py:37
|
||||
#: netbox/virtualization/forms/filtersets.py:48
|
||||
#: netbox/virtualization/forms/filtersets.py:106
|
||||
@ -1328,21 +1328,21 @@ msgstr ""
|
||||
msgid "Circuit ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:66
|
||||
#: netbox/circuits/tables/circuits.py:67
|
||||
#: netbox/wireless/forms/model_forms.py:160
|
||||
msgid "Side A"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:70
|
||||
#: netbox/circuits/tables/circuits.py:72
|
||||
msgid "Side Z"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:73
|
||||
#: netbox/circuits/tables/circuits.py:75
|
||||
#: netbox/templates/circuits/circuit.html:55
|
||||
msgid "Commit Rate"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:76 netbox/circuits/tables/providers.py:48
|
||||
#: netbox/circuits/tables/circuits.py:78 netbox/circuits/tables/providers.py:48
|
||||
#: netbox/circuits/tables/providers.py:82
|
||||
#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1001
|
||||
#: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29
|
||||
@ -2067,8 +2067,8 @@ msgstr ""
|
||||
msgid "No workers found"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/views.py:335 netbox/core/views.py:378 netbox/core/views.py:401
|
||||
#: netbox/core/views.py:419 netbox/core/views.py:454
|
||||
#: netbox/core/views.py:331 netbox/core/views.py:374 netbox/core/views.py:397
|
||||
#: netbox/core/views.py:415 netbox/core/views.py:450
|
||||
#, python-brace-format
|
||||
msgid "Job {job_id} not found"
|
||||
msgstr ""
|
||||
@ -2946,7 +2946,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/bulk_create.py:40 netbox/extras/forms/filtersets.py:410
|
||||
#: netbox/extras/forms/model_forms.py:443
|
||||
#: netbox/extras/forms/model_forms.py:495 netbox/netbox/forms/base.py:84
|
||||
#: netbox/netbox/forms/mixins.py:81 netbox/netbox/tables/columns.py:458
|
||||
#: netbox/netbox/forms/mixins.py:81 netbox/netbox/tables/columns.py:461
|
||||
#: netbox/templates/circuits/inc/circuit_termination.html:32
|
||||
#: netbox/templates/generic/bulk_edit.html:65
|
||||
#: netbox/templates/inc/panels/tags.html:5
|
||||
@ -5974,7 +5974,7 @@ msgstr ""
|
||||
#: netbox/netbox/navigation/menu.py:60 netbox/netbox/navigation/menu.py:62
|
||||
#: netbox/virtualization/forms/model_forms.py:122
|
||||
#: netbox/virtualization/tables/clusters.py:83
|
||||
#: netbox/virtualization/views.py:210
|
||||
#: netbox/virtualization/views.py:202
|
||||
msgid "Devices"
|
||||
msgstr ""
|
||||
|
||||
@ -6054,8 +6054,8 @@ msgid "Power outlets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:243 netbox/dcim/tables/devices.py:1046
|
||||
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1006
|
||||
#: netbox/dcim/views.py:1245 netbox/dcim/views.py:1931
|
||||
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:985
|
||||
#: netbox/dcim/views.py:1224 netbox/dcim/views.py:1900
|
||||
#: netbox/netbox/navigation/menu.py:81 netbox/netbox/navigation/menu.py:237
|
||||
#: netbox/templates/dcim/device/base.html:37
|
||||
#: netbox/templates/dcim/device_list.html:43
|
||||
@ -6067,7 +6067,7 @@ msgstr ""
|
||||
#: netbox/templates/virtualization/virtualmachine/base.html:27
|
||||
#: netbox/templates/virtualization/virtualmachine_list.html:14
|
||||
#: netbox/virtualization/tables/virtualmachines.py:100
|
||||
#: netbox/virtualization/views.py:367 netbox/wireless/tables/wirelesslan.py:55
|
||||
#: netbox/virtualization/views.py:359 netbox/wireless/tables/wirelesslan.py:55
|
||||
msgid "Interfaces"
|
||||
msgstr ""
|
||||
|
||||
@ -6093,8 +6093,8 @@ msgid "Module Bay"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:310 netbox/dcim/tables/devicetypes.py:48
|
||||
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1081
|
||||
#: netbox/dcim/views.py:2024 netbox/netbox/navigation/menu.py:90
|
||||
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1060
|
||||
#: netbox/dcim/views.py:1993 netbox/netbox/navigation/menu.py:90
|
||||
#: netbox/templates/dcim/device/base.html:52
|
||||
#: netbox/templates/dcim/device_list.html:71
|
||||
#: netbox/templates/dcim/devicetype/base.html:49
|
||||
@ -6124,8 +6124,8 @@ msgid "Allocated draw (W)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:546 netbox/ipam/forms/model_forms.py:747
|
||||
#: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:602
|
||||
#: netbox/ipam/views.py:701 netbox/netbox/navigation/menu.py:145
|
||||
#: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:589
|
||||
#: netbox/ipam/views.py:688 netbox/netbox/navigation/menu.py:145
|
||||
#: netbox/netbox/navigation/menu.py:147
|
||||
#: netbox/templates/dcim/interface.html:339
|
||||
#: netbox/templates/ipam/ipaddress_bulk_add.html:15
|
||||
@ -6218,8 +6218,8 @@ msgstr ""
|
||||
msgid "Instances"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:946
|
||||
#: netbox/dcim/views.py:1185 netbox/dcim/views.py:1871
|
||||
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:925
|
||||
#: netbox/dcim/views.py:1164 netbox/dcim/views.py:1840
|
||||
#: netbox/netbox/navigation/menu.py:84
|
||||
#: netbox/templates/dcim/device/base.html:25
|
||||
#: netbox/templates/dcim/device_list.html:15
|
||||
@ -6229,8 +6229,8 @@ msgstr ""
|
||||
msgid "Console Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:961
|
||||
#: netbox/dcim/views.py:1200 netbox/dcim/views.py:1886
|
||||
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:940
|
||||
#: netbox/dcim/views.py:1179 netbox/dcim/views.py:1855
|
||||
#: netbox/netbox/navigation/menu.py:85
|
||||
#: netbox/templates/dcim/device/base.html:28
|
||||
#: netbox/templates/dcim/device_list.html:22
|
||||
@ -6240,8 +6240,8 @@ msgstr ""
|
||||
msgid "Console Server Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:976
|
||||
#: netbox/dcim/views.py:1215 netbox/dcim/views.py:1901
|
||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:955
|
||||
#: netbox/dcim/views.py:1194 netbox/dcim/views.py:1870
|
||||
#: netbox/netbox/navigation/menu.py:86
|
||||
#: netbox/templates/dcim/device/base.html:31
|
||||
#: netbox/templates/dcim/device_list.html:29
|
||||
@ -6251,8 +6251,8 @@ msgstr ""
|
||||
msgid "Power Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:991
|
||||
#: netbox/dcim/views.py:1230 netbox/dcim/views.py:1916
|
||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:970
|
||||
#: netbox/dcim/views.py:1209 netbox/dcim/views.py:1885
|
||||
#: netbox/netbox/navigation/menu.py:87
|
||||
#: netbox/templates/dcim/device/base.html:34
|
||||
#: netbox/templates/dcim/device_list.html:36
|
||||
@ -6262,8 +6262,8 @@ msgstr ""
|
||||
msgid "Power Outlets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1021
|
||||
#: netbox/dcim/views.py:1260 netbox/dcim/views.py:1952
|
||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1000
|
||||
#: netbox/dcim/views.py:1239 netbox/dcim/views.py:1921
|
||||
#: netbox/netbox/navigation/menu.py:82
|
||||
#: netbox/templates/dcim/device/base.html:40
|
||||
#: netbox/templates/dcim/devicetype/base.html:37
|
||||
@ -6272,8 +6272,8 @@ msgstr ""
|
||||
msgid "Front Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1036
|
||||
#: netbox/dcim/views.py:1275 netbox/dcim/views.py:1967
|
||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1015
|
||||
#: netbox/dcim/views.py:1254 netbox/dcim/views.py:1936
|
||||
#: netbox/netbox/navigation/menu.py:83
|
||||
#: netbox/templates/dcim/device/base.html:43
|
||||
#: netbox/templates/dcim/device_list.html:50
|
||||
@ -6283,16 +6283,16 @@ msgstr ""
|
||||
msgid "Rear Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1066
|
||||
#: netbox/dcim/views.py:2005 netbox/netbox/navigation/menu.py:89
|
||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1045
|
||||
#: netbox/dcim/views.py:1974 netbox/netbox/navigation/menu.py:89
|
||||
#: netbox/templates/dcim/device/base.html:49
|
||||
#: netbox/templates/dcim/device_list.html:57
|
||||
#: netbox/templates/dcim/devicetype/base.html:46
|
||||
msgid "Device Bays"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1051
|
||||
#: netbox/dcim/views.py:1986 netbox/netbox/navigation/menu.py:88
|
||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1030
|
||||
#: netbox/dcim/views.py:1955 netbox/netbox/navigation/menu.py:88
|
||||
#: netbox/templates/dcim/device/base.html:46
|
||||
#: netbox/templates/dcim/device_list.html:64
|
||||
#: netbox/templates/dcim/devicetype/base.html:43
|
||||
@ -6350,38 +6350,38 @@ msgstr ""
|
||||
msgid "Test case must set peer_termination_type"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:137
|
||||
#: netbox/dcim/views.py:139
|
||||
#, python-brace-format
|
||||
msgid "Disconnected {count} {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:698 netbox/netbox/navigation/menu.py:28
|
||||
#: netbox/dcim/views.py:684 netbox/netbox/navigation/menu.py:28
|
||||
msgid "Reservations"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:716 netbox/templates/dcim/location.html:90
|
||||
#: netbox/dcim/views.py:702 netbox/templates/dcim/location.html:90
|
||||
#: netbox/templates/dcim/site.html:140
|
||||
msgid "Non-Racked Devices"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2037 netbox/extras/forms/model_forms.py:453
|
||||
#: netbox/dcim/views.py:2006 netbox/extras/forms/model_forms.py:453
|
||||
#: netbox/templates/extras/configcontext.html:10
|
||||
#: netbox/virtualization/forms/model_forms.py:225
|
||||
#: netbox/virtualization/views.py:407
|
||||
#: netbox/virtualization/views.py:399
|
||||
msgid "Config Context"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2047 netbox/virtualization/views.py:417
|
||||
#: netbox/dcim/views.py:2016 netbox/virtualization/views.py:409
|
||||
msgid "Render Config"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2097 netbox/extras/tables/tables.py:440
|
||||
#: netbox/dcim/views.py:2066 netbox/extras/tables/tables.py:440
|
||||
#: netbox/netbox/navigation/menu.py:234 netbox/netbox/navigation/menu.py:236
|
||||
#: netbox/virtualization/views.py:185
|
||||
#: netbox/virtualization/views.py:177
|
||||
msgid "Virtual Machines"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2989 netbox/ipam/tables/ip.py:233
|
||||
#: netbox/dcim/views.py:2948 netbox/ipam/tables/ip.py:233
|
||||
msgid "Children"
|
||||
msgstr ""
|
||||
|
||||
@ -7686,56 +7686,56 @@ msgstr ""
|
||||
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:667
|
||||
#: netbox/extras/models/customfields.py:671
|
||||
msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:674
|
||||
#: netbox/extras/models/customfields.py:678
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:684
|
||||
#: netbox/extras/models/customfields.py:688
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:693
|
||||
#: netbox/extras/models/customfields.py:697
|
||||
#, python-brace-format
|
||||
msgid "Value must be an object ID, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:699
|
||||
#: netbox/extras/models/customfields.py:703
|
||||
#, python-brace-format
|
||||
msgid "Value must be a list of object IDs, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:703
|
||||
#: netbox/extras/models/customfields.py:707
|
||||
#, python-brace-format
|
||||
msgid "Found invalid object ID: {id}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:706
|
||||
#: netbox/extras/models/customfields.py:710
|
||||
msgid "Required field cannot be empty."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:725
|
||||
#: netbox/extras/models/customfields.py:729
|
||||
msgid "Base set of predefined choices (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:737
|
||||
#: netbox/extras/models/customfields.py:741
|
||||
msgid "Choices are automatically ordered alphabetically"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:744
|
||||
#: netbox/extras/models/customfields.py:748
|
||||
msgid "custom field choice set"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:745
|
||||
#: netbox/extras/models/customfields.py:749
|
||||
msgid "custom field choice sets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:781
|
||||
#: netbox/extras/models/customfields.py:785
|
||||
msgid "Must define base or extra choices."
|
||||
msgstr ""
|
||||
|
||||
@ -9415,7 +9415,7 @@ msgid "The primary function of this VLAN"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/models/vlans.py:215 netbox/ipam/tables/ip.py:175
|
||||
#: netbox/ipam/tables/vlans.py:78 netbox/ipam/views.py:978
|
||||
#: netbox/ipam/tables/vlans.py:78 netbox/ipam/views.py:961
|
||||
#: netbox/netbox/navigation/menu.py:180 netbox/netbox/navigation/menu.py:182
|
||||
msgid "VLANs"
|
||||
msgstr ""
|
||||
@ -9487,7 +9487,7 @@ msgid "Added"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/ip.py:127 netbox/ipam/tables/ip.py:165
|
||||
#: netbox/ipam/tables/vlans.py:138 netbox/ipam/views.py:349
|
||||
#: netbox/ipam/tables/vlans.py:138 netbox/ipam/views.py:342
|
||||
#: netbox/netbox/navigation/menu.py:152 netbox/netbox/navigation/menu.py:154
|
||||
#: netbox/templates/ipam/vlan.html:84
|
||||
msgid "Prefixes"
|
||||
@ -9588,23 +9588,23 @@ msgid ""
|
||||
"are allowed in DNS names"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:541
|
||||
#: netbox/ipam/views.py:528
|
||||
msgid "Child Prefixes"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:576
|
||||
#: netbox/ipam/views.py:563
|
||||
msgid "Child Ranges"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:902
|
||||
#: netbox/ipam/views.py:889
|
||||
msgid "Related IPs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:1133
|
||||
#: netbox/ipam/views.py:1116
|
||||
msgid "Device Interfaces"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:1150
|
||||
#: netbox/ipam/views.py:1133
|
||||
msgid "VM Interfaces"
|
||||
msgstr ""
|
||||
|
||||
@ -10159,7 +10159,7 @@ msgstr ""
|
||||
#: netbox/templates/virtualization/virtualmachine/base.html:32
|
||||
#: netbox/templates/virtualization/virtualmachine_list.html:21
|
||||
#: netbox/virtualization/tables/virtualmachines.py:103
|
||||
#: netbox/virtualization/views.py:388
|
||||
#: netbox/virtualization/views.py:380
|
||||
msgid "Virtual Disks"
|
||||
msgstr ""
|
||||
|
||||
@ -10498,15 +10498,15 @@ msgstr ""
|
||||
msgid "Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:185
|
||||
#: netbox/netbox/tables/columns.py:188
|
||||
msgid "Toggle all"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:287
|
||||
#: netbox/netbox/tables/columns.py:290
|
||||
msgid "Toggle Dropdown"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:552 netbox/templates/core/job.html:35
|
||||
#: netbox/netbox/tables/columns.py:555 netbox/templates/core/job.html:35
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
@ -10790,36 +10790,28 @@ msgstr ""
|
||||
msgid "NetBox Logo"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:56
|
||||
msgid "Enable dark mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:59
|
||||
msgid "Enable light mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:145
|
||||
#: netbox/templates/base/layout.html:139
|
||||
msgid "Docs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:151
|
||||
#: netbox/templates/base/layout.html:145
|
||||
#: netbox/templates/rest_framework/api.html:10
|
||||
msgid "REST API"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:157
|
||||
#: netbox/templates/base/layout.html:151
|
||||
msgid "REST API documentation"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:164
|
||||
#: netbox/templates/base/layout.html:158
|
||||
msgid "GraphQL API"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:171
|
||||
#: netbox/templates/base/layout.html:165
|
||||
msgid "Source Code"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:177
|
||||
#: netbox/templates/base/layout.html:171
|
||||
msgid "Community"
|
||||
msgstr ""
|
||||
|
||||
@ -11112,8 +11104,8 @@ msgstr ""
|
||||
#: netbox/templates/core/rq_worker_list.html:45
|
||||
#: netbox/templates/extras/script_result.html:49
|
||||
#: netbox/templates/extras/script_result.html:51
|
||||
#: netbox/templates/inc/table_controls_htmx.html:18
|
||||
#: netbox/templates/inc/table_controls_htmx.html:20
|
||||
#: netbox/templates/inc/table_controls_htmx.html:28
|
||||
#: netbox/templates/inc/table_controls_htmx.html:31
|
||||
msgid "Configure Table"
|
||||
msgstr ""
|
||||
|
||||
@ -12689,6 +12681,14 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/light_toggle.html:4
|
||||
msgid "Enable dark mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/light_toggle.html:7
|
||||
msgid "Enable light mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/missing_prerequisites.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
@ -12729,6 +12729,14 @@ msgstr ""
|
||||
msgid "Data is out of sync with upstream file"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/table_controls_htmx.html:7
|
||||
msgid "Quick search"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/table_controls_htmx.html:19
|
||||
msgid "Saved filter"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/user_menu.html:23
|
||||
msgid "Django Admin"
|
||||
msgstr ""
|
||||
@ -14072,17 +14080,17 @@ msgstr ""
|
||||
msgid "{value} is not a valid regular expression."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:40
|
||||
#: netbox/utilities/views.py:44
|
||||
#, python-brace-format
|
||||
msgid "{self.__class__.__name__} must implement get_required_permission()"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:76
|
||||
#: netbox/utilities/views.py:80
|
||||
#, python-brace-format
|
||||
msgid "{class_name} must implement get_required_permission()"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:100
|
||||
#: netbox/utilities/views.py:104
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{class_name} has no queryset defined. ObjectPermissionRequiredMixin may only "
|
||||
|
@ -29,7 +29,7 @@ def linkify_phone(value):
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
return f"tel:{value}"
|
||||
return f"tel:{value.replace(' ', '')}"
|
||||
|
||||
|
||||
def register_table_column(column, name, *tables):
|
||||
|
@ -11,7 +11,7 @@
|
||||
{% elif customfield.type == 'date' and value %}
|
||||
{{ value|isodate }}
|
||||
{% elif customfield.type == 'datetime' and value %}
|
||||
{{ value|isodate }} {{ value|isodatetime }}
|
||||
{{ value|isodatetime }}
|
||||
{% elif customfield.type == 'url' and value %}
|
||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||
{% elif customfield.type == 'json' and value %}
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from utilities.querydict import dict_to_querydict
|
||||
@ -124,5 +125,5 @@ def formaction(context):
|
||||
if HTMX navigation is enabled (per the user's preferences).
|
||||
"""
|
||||
if context.get('htmx_navigation', False):
|
||||
return 'hx-push-url="true" hx-post'
|
||||
return mark_safe('hx-push-url="true" hx-post')
|
||||
return 'formaction'
|
||||
|
@ -281,6 +281,10 @@ def applied_filters(context, model, form, query_params):
|
||||
if filter_name not in querydict:
|
||||
continue
|
||||
|
||||
# Skip saved filters, as they're displayed alongside the quick search widget
|
||||
if filter_name == 'filter_id':
|
||||
continue
|
||||
|
||||
bound_field = form.fields[filter_name].get_bound_field(form, filter_name)
|
||||
querydict.pop(filter_name)
|
||||
display_value = ', '.join([str(v) for v in get_selected_values(form, filter_name)])
|
||||
|
@ -178,8 +178,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
required=False,
|
||||
selector=True,
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
}
|
||||
'site_id': ['$site', 'null']
|
||||
},
|
||||
)
|
||||
device = DynamicModelChoiceField(
|
||||
label=_('Device'),
|
||||
|
@ -180,7 +180,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
|
||||
})
|
||||
|
||||
# Validate site for cluster & device
|
||||
if self.cluster and self.site and self.cluster.site != self.site:
|
||||
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
|
||||
raise ValidationError({
|
||||
'cluster': _(
|
||||
'The selected cluster ({cluster}) is not assigned to this site ({site}).'
|
||||
|
@ -63,6 +63,9 @@ class VirtualMachineTestCase(TestCase):
|
||||
# VM with site only should pass
|
||||
VirtualMachine(name='vm1', site=sites[0]).full_clean()
|
||||
|
||||
# VM with site, cluster non-site should pass
|
||||
VirtualMachine(name='vm1', site=sites[0], cluster=clusters[2]).full_clean()
|
||||
|
||||
# VM with non-site cluster only should pass
|
||||
VirtualMachine(name='vm1', cluster=clusters[2]).full_clean()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user