mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
Merge branch 'develop' into 16660-use-ngettext_for_strings_with_count
This commit is contained in:
commit
b642d16ef4
@ -135,4 +135,6 @@ First, run the `build-site` action, by navigating to Actions > build-site > Run
|
|||||||
|
|
||||||
Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag.
|
Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag.
|
||||||
|
|
||||||
|
Clear the CDN cache from the [Kinsta](https://my.kinsta.com/) portal. Navigate to _Sites_ / _NetBox Labs_ / _Live_, select _CDN_ in the left-nav, click the _Clear CDN cache_ button, and confirm the clear operation.
|
||||||
|
|
||||||
Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated.
|
Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated.
|
||||||
|
@ -66,9 +66,6 @@ class CircuitTypeImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CircuitImportForm(NetBoxModelImportForm):
|
class CircuitImportForm(NetBoxModelImportForm):
|
||||||
|
@ -20,7 +20,7 @@ from utilities.filters import (
|
|||||||
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
||||||
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
||||||
from wireless.models import WirelessLAN, WirelessLink
|
from wireless.models import WirelessLAN, WirelessLink
|
||||||
@ -1018,6 +1018,17 @@ class DeviceFilterSet(
|
|||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
label=_('VM cluster (ID)'),
|
label=_('VM cluster (ID)'),
|
||||||
)
|
)
|
||||||
|
cluster_group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='cluster__group__slug',
|
||||||
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Cluster group (slug)'),
|
||||||
|
)
|
||||||
|
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='cluster__group',
|
||||||
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
label=_('Cluster group (ID)'),
|
||||||
|
)
|
||||||
model = django_filters.ModelMultipleChoiceFilter(
|
model = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device_type__slug',
|
field_name='device_type__slug',
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
|
@ -174,9 +174,6 @@ class RackRoleImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RackImportForm(NetBoxModelImportForm):
|
class RackImportForm(NetBoxModelImportForm):
|
||||||
@ -384,9 +381,6 @@ class DeviceRoleImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ('name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags')
|
fields = ('name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags')
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformImportForm(NetBoxModelImportForm):
|
class PlatformImportForm(NetBoxModelImportForm):
|
||||||
@ -1104,9 +1098,6 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItemRole
|
model = InventoryItemRole
|
||||||
fields = ('name', 'slug', 'color', 'description')
|
fields = ('name', 'slug', 'color', 'description')
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1183,9 +1174,6 @@ class CableImportForm(NetBoxModelImportForm):
|
|||||||
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
||||||
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
|
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _clean_side(self, side):
|
def _clean_side(self, side):
|
||||||
"""
|
"""
|
||||||
|
@ -14,6 +14,7 @@ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_ch
|
|||||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import NumberWithOptions
|
from utilities.forms.widgets import NumberWithOptions
|
||||||
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
|
|
||||||
@ -655,6 +656,7 @@ class DeviceFilterForm(
|
|||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||||
name=_('Components')
|
name=_('Components')
|
||||||
),
|
),
|
||||||
|
FieldSet('cluster_group_id', 'cluster_id', name=_('Cluster')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
||||||
'has_virtual_device_context',
|
'has_virtual_device_context',
|
||||||
@ -821,6 +823,16 @@ class DeviceFilterForm(
|
|||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
cluster_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Cluster.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Cluster')
|
||||||
|
)
|
||||||
|
cluster_group_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Cluster group')
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from ipam.models import ASN, IPAddress, RIR, VRF
|
|||||||
from netbox.choices import ColorChoices
|
from netbox.choices import ColorChoices
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
|
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
|
||||||
from virtualization.models import Cluster, ClusterType
|
from virtualization.models import Cluster, ClusterType, ClusterGroup
|
||||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
@ -1959,10 +1959,16 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||||
|
cluster_groups = (
|
||||||
|
ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
|
||||||
|
ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
|
||||||
|
ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'),
|
||||||
|
)
|
||||||
|
ClusterGroup.objects.bulk_create(cluster_groups)
|
||||||
clusters = (
|
clusters = (
|
||||||
Cluster(name='Cluster 1', type=cluster_type),
|
Cluster(name='Cluster 1', type=cluster_type, group=cluster_groups[0]),
|
||||||
Cluster(name='Cluster 2', type=cluster_type),
|
Cluster(name='Cluster 2', type=cluster_type, group=cluster_groups[1]),
|
||||||
Cluster(name='Cluster 3', type=cluster_type),
|
Cluster(name='Cluster 3', type=cluster_type, group=cluster_groups[2]),
|
||||||
)
|
)
|
||||||
Cluster.objects.bulk_create(clusters)
|
Cluster.objects.bulk_create(clusters)
|
||||||
|
|
||||||
@ -2213,6 +2219,13 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'cluster_id': [clusters[0].pk, clusters[1].pk]}
|
params = {'cluster_id': [clusters[0].pk, clusters[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_cluster_group(self):
|
||||||
|
cluster_groups = ClusterGroup.objects.all()[:2]
|
||||||
|
params = {'cluster_group_id': [cluster_groups[0].pk, cluster_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'cluster_group': [cluster_groups[0].slug, cluster_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_model(self):
|
def test_model(self):
|
||||||
params = {'model': ['model-1', 'model-2']}
|
params = {'model': ['model-1', 'model-2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -228,9 +228,6 @@ class TagImportForm(CSVModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('name', 'slug', 'color', 'description')
|
fields = ('name', 'slug', 'color', 'description')
|
||||||
help_texts = {
|
|
||||||
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryImportForm(NetBoxModelImportForm):
|
class JournalEntryImportForm(NetBoxModelImportForm):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.search import LookupTypes
|
from netbox.search import LookupTypes
|
||||||
from netbox.search.backends import search_backend
|
from netbox.search.backends import search_backend
|
||||||
@ -36,7 +36,8 @@ class SearchForm(forms.Form):
|
|||||||
lookup = forms.ChoiceField(
|
lookup = forms.ChoiceField(
|
||||||
choices=LOOKUP_CHOICES,
|
choices=LOOKUP_CHOICES,
|
||||||
initial=LookupTypes.PARTIAL,
|
initial=LookupTypes.PARTIAL,
|
||||||
required=False
|
required=False,
|
||||||
|
label=_('Lookup')
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -8,6 +8,7 @@ from django.db.models.fields.related import ForeignKey
|
|||||||
from django.db.models.functions import window
|
from django.db.models.functions import window
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
import netaddr
|
import netaddr
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class SearchBackend:
|
|||||||
# Organize choices by category
|
# Organize choices by category
|
||||||
categories = defaultdict(dict)
|
categories = defaultdict(dict)
|
||||||
for label, idx in registry['search'].items():
|
for label, idx in registry['search'].items():
|
||||||
categories[idx.get_category()][label] = title(idx.model._meta.verbose_name)
|
categories[idx.get_category()][label] = _(title(idx.model._meta.verbose_name))
|
||||||
|
|
||||||
# Compile a nested tuple of choices for form rendering
|
# Compile a nested tuple of choices for form rendering
|
||||||
results = (
|
results = (
|
||||||
|
@ -2,6 +2,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from utilities.ordering import naturalize
|
from utilities.ordering import naturalize
|
||||||
@ -26,6 +27,7 @@ class ColorField(models.CharField):
|
|||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
kwargs['widget'] = ColorSelect
|
kwargs['widget'] = ColorSelect
|
||||||
|
kwargs['help_text'] = mark_safe(_('RGB color in hexadecimal. Example: ') + '<code>00ff00</code>')
|
||||||
return super().formfield(**kwargs)
|
return super().formfield(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user