mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 17:26:10 -06:00
Merge branch 'develop' into 11432-device-field
This commit is contained in:
commit
418fa2f6e9
@ -18,4 +18,4 @@ interface.
|
|||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
|
||||||
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Set this to `True` **only** if you are actively developing the NetBox code base.
|
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Additionally, enabling this setting disables the debug warning banner in the UI. Set this to `True` **only** if you are actively developing the NetBox code base.
|
||||||
|
@ -132,7 +132,7 @@ Once you have created a report, it will appear in the reports list. Initially, r
|
|||||||
!!! note
|
!!! note
|
||||||
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
|
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Via the Web UI
|
### Via the Web UI
|
||||||
|
|
||||||
|
@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
|
* [#11453](https://github.com/netbox-community/netbox/issues/11453) - Display a warning banner when `DEBUG` is enabled
|
||||||
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
|
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
|
||||||
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
|
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#10615](https://github.com/netbox-community/netbox/issues/10615) - Fix filtering of cable terminations by A/B end
|
||||||
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
|
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
|
||||||
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
|
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
|
||||||
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
|
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
|
||||||
|
@ -1354,6 +1354,24 @@ class CommonInterfaceFilterSet(django_filters.FilterSet):
|
|||||||
label=_('L2VPN'),
|
label=_('L2VPN'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def filter_vlan_id(self, queryset, name, value):
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(untagged_vlan_id=value) |
|
||||||
|
Q(tagged_vlans=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_vlan(self, queryset, name, value):
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(untagged_vlan_id__vid=value) |
|
||||||
|
Q(tagged_vlans__vid=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceFilterSet(
|
class InterfaceFilterSet(
|
||||||
ModularDeviceComponentFilterSet,
|
ModularDeviceComponentFilterSet,
|
||||||
@ -1461,24 +1479,6 @@ class InterfaceFilterSet(
|
|||||||
except Device.DoesNotExist:
|
except Device.DoesNotExist:
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
def filter_vlan_id(self, queryset, name, value):
|
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(untagged_vlan_id=value) |
|
|
||||||
Q(tagged_vlans=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_vlan(self, queryset, name, value):
|
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(untagged_vlan_id__vid=value) |
|
|
||||||
Q(tagged_vlans__vid=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_kind(self, queryset, name, value):
|
def filter_kind(self, queryset, name, value):
|
||||||
value = value.strip().lower()
|
value = value.strip().lower()
|
||||||
return {
|
return {
|
||||||
@ -1667,12 +1667,14 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
field_name='terminations__termination_type'
|
field_name='terminations__termination_type'
|
||||||
)
|
)
|
||||||
termination_a_id = MultiValueNumberFilter(
|
termination_a_id = MultiValueNumberFilter(
|
||||||
|
method='filter_by_cable_end_a',
|
||||||
field_name='terminations__termination_id'
|
field_name='terminations__termination_id'
|
||||||
)
|
)
|
||||||
termination_b_type = ContentTypeFilter(
|
termination_b_type = ContentTypeFilter(
|
||||||
field_name='terminations__termination_type'
|
field_name='terminations__termination_type'
|
||||||
)
|
)
|
||||||
termination_b_id = MultiValueNumberFilter(
|
termination_b_id = MultiValueNumberFilter(
|
||||||
|
method='filter_by_cable_end_b',
|
||||||
field_name='terminations__termination_id'
|
field_name='terminations__termination_id'
|
||||||
)
|
)
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
@ -1730,6 +1732,18 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
# Supported objects: device, rack, location, site
|
# Supported objects: device, rack, location, site
|
||||||
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
|
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
|
||||||
|
|
||||||
|
def filter_by_cable_end(self, queryset, name, value, side):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return queryset.filter(**{f'{name}__in': value, 'terminations__cable_end': side}).distinct()
|
||||||
|
|
||||||
|
def filter_by_cable_end_a(self, queryset, name, value):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_A)
|
||||||
|
|
||||||
|
def filter_by_cable_end_b(self, queryset, name, value):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B)
|
||||||
|
|
||||||
|
|
||||||
class CableTerminationFilterSet(BaseFilterSet):
|
class CableTerminationFilterSet(BaseFilterSet):
|
||||||
termination_type = ContentTypeFilter()
|
termination_type = ContentTypeFilter()
|
||||||
|
@ -97,6 +97,12 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'created', 'last_updated',
|
'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def validate_type(self, value):
|
||||||
|
if self.instance and self.instance.type != value:
|
||||||
|
raise serializers.ValidationError('Changing the type of custom fields is not supported.')
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
def get_data_type(self, obj):
|
def get_data_type(self, obj):
|
||||||
types = CustomFieldTypeChoices
|
types = CustomFieldTypeChoices
|
||||||
if obj.type == types.TYPE_INTEGER:
|
if obj.type == types.TYPE_INTEGER:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
# TODO: Come up with a canonical way to register suitable models
|
# TODO: Come up with a canonical way to register suitable models
|
||||||
limit_choices_to=FeatureQuery('webhooks'),
|
limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Type of the related object (for object/multi-object fields only)")
|
help_text=_("Type of the related object (for object/multi-object fields only)")
|
||||||
)
|
)
|
||||||
@ -64,6 +65,13 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
'ui_visibility': StaticSelect(),
|
'ui_visibility': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable changing the type of a CustomField as it almost universally causes errors if custom field data is already present.
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['type'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||||
content_types = ContentTypeMultipleChoiceField(
|
content_types = ContentTypeMultipleChoiceField(
|
||||||
|
@ -102,6 +102,11 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
|||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
update_data = {
|
||||||
|
'content_types': ['dcim.device'],
|
||||||
|
'name': 'New_Name',
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -70,10 +70,17 @@ Blocks:
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if settings.DEBUG and not settings.DEVELOPER %}
|
||||||
|
<div class="alert alert-warning text-center mx-3" role="alert">
|
||||||
|
<strong><i class="mdi mdi-alert"></i> Debug mode is enabled.</strong>
|
||||||
|
Performance may be limited. Debugging should never be enabled on a production system.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if config.MAINTENANCE_MODE %}
|
{% if config.MAINTENANCE_MODE %}
|
||||||
<div class="alert alert-warning text-center mx-3" role="alert">
|
<div class="alert alert-warning text-center mx-3" role="alert">
|
||||||
<h4><i class="mdi mdi-alert"></i> Maintenance Mode</h4>
|
<h5><i class="mdi mdi-alert"></i> Maintenance Mode</h5>
|
||||||
<span>NetBox is currently in maintenance mode. Functionality may be limited.</span>
|
NetBox is currently in maintenance mode. Functionality may be limited.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -48,6 +48,10 @@ def get_viewname(model, action=None, rest_api=False):
|
|||||||
if is_plugin:
|
if is_plugin:
|
||||||
viewname = f'plugins-api:{app_label}-api:{model_name}'
|
viewname = f'plugins-api:{app_label}-api:{model_name}'
|
||||||
else:
|
else:
|
||||||
|
# Alter the app_label for group and user model_name to point to users app
|
||||||
|
if app_label == 'auth' and model_name in ['group', 'user']:
|
||||||
|
app_label = 'users'
|
||||||
|
|
||||||
viewname = f'{app_label}-api:{model_name}'
|
viewname = f'{app_label}-api:{model_name}'
|
||||||
# Append the action, if any
|
# Append the action, if any
|
||||||
if action:
|
if action:
|
||||||
|
Loading…
Reference in New Issue
Block a user