mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #4742: Add tagging for cables, power panels, and rack reservations
This commit is contained in:
@ -8,6 +8,10 @@
|
||||
|
||||
NetBox v2.9 replaces Django's built-in permissions framework with one that supports object-based assignment of permissions using arbitrary constraints. When granting a user or group to perform a certain action on one or more types of objects, an administrator can optionally specify a set of constraints. The permission will apply only to objects which match the specified constraints. For example, assigning permission to modify devices with the constraint `{"tenant__group__name": "Customers"}` would grant the permission only for devices assigned to a tenant belonging to the "Customers" group.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#4742](https://github.com/netbox-community/netbox/issues/4742) - Add tagging for cables, power panels, and rack reservations
|
||||
|
||||
### Configuration Changes
|
||||
|
||||
* `REMOTE_AUTH_DEFAULT_PERMISSIONS` now takes a dictionary rather than a list. This is a mapping of permission names to a dictionary of constraining attributes, or `None`. For example, `['dcim.add_site', 'dcim.change_site']` would become `{'dcim.add_site': None, 'dcim.change_site': None}`.
|
||||
|
@ -165,10 +165,11 @@ class RackReservationSerializer(ValidatedModelSerializer):
|
||||
rack = NestedRackSerializer()
|
||||
user = NestedUserSerializer()
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description']
|
||||
fields = ['id', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags']
|
||||
|
||||
|
||||
class RackElevationDetailFilterSerializer(serializers.Serializer):
|
||||
@ -640,12 +641,13 @@ class CableSerializer(ValidatedModelSerializer):
|
||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||
status = ChoiceField(choices=CableStatusChoices, required=False)
|
||||
length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type', 'termination_b_id',
|
||||
'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||
'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||
]
|
||||
|
||||
def _get_termination(self, obj, side):
|
||||
@ -729,11 +731,12 @@ class PowerPanelSerializer(ValidatedModelSerializer):
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
tags = TagListSerializerField(required=False)
|
||||
powerfeed_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = ['id', 'site', 'rack_group', 'name', 'powerfeed_count']
|
||||
fields = ['id', 'site', 'rack_group', 'name', 'tags', 'powerfeed_count']
|
||||
|
||||
|
||||
class PowerFeedSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
|
@ -298,6 +298,7 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet):
|
||||
to_field_name='username',
|
||||
label='User (name)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
@ -1117,6 +1118,7 @@ class CableFilterSet(BaseFilterSet):
|
||||
method='filter_device',
|
||||
field_name='device__tenant__slug'
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
@ -1265,6 +1267,7 @@ class PowerPanelFilterSet(BaseFilterSet):
|
||||
lookup_expr='in',
|
||||
label='Rack group (ID)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
|
@ -750,11 +750,14 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
|
||||
),
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
tags = TagField(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'rack', 'units', 'user', 'tenant_group', 'tenant', 'description',
|
||||
'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -825,7 +828,7 @@ class RackReservationCSVForm(CSVModelForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RackReservation.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -851,6 +854,7 @@ class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
|
||||
|
||||
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
||||
model = RackReservation
|
||||
field_order = ['q', 'site', 'group_id', 'tenant_group', 'tenant']
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
@ -872,6 +876,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
@ -3662,11 +3667,14 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
|
||||
class CableForm(BootstrapMixin, forms.ModelForm):
|
||||
tags = TagField(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||
'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect2,
|
||||
@ -3799,7 +3807,7 @@ class CableCSVForm(CSVModelForm):
|
||||
return length_unit if length_unit is not None else ''
|
||||
|
||||
|
||||
class CableBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Cable.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -3912,6 +3920,7 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
||||
required=False,
|
||||
label='Device'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
@ -4325,11 +4334,14 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm):
|
||||
queryset=RackGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
tags = TagField(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'site', 'rack_group', 'name',
|
||||
'site', 'rack_group', 'name', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -4359,7 +4371,7 @@ class PowerPanelCSVForm(CSVModelForm):
|
||||
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
|
||||
|
||||
|
||||
class PowerPanelBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=PowerPanel.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -4420,6 +4432,7 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
|
30
netbox/dcim/migrations/0107_add_tags.py
Normal file
30
netbox/dcim/migrations/0107_add_tags.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Generated by Django 3.0.6 on 2020-06-10 18:32
|
||||
|
||||
from django.db import migrations
|
||||
import taggit.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0042_customfield_manager'),
|
||||
('dcim', '0106_role_default_color'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerpanel',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||
),
|
||||
]
|
@ -832,6 +832,7 @@ class RackReservation(ChangeLoggedModel):
|
||||
description = models.CharField(
|
||||
max_length=200
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
@ -1832,6 +1833,7 @@ class PowerPanel(ChangeLoggedModel):
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
@ -2106,6 +2108,7 @@ class Cable(ChangeLoggedModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
|
@ -399,6 +399,9 @@ class RackReservationTable(BaseTable):
|
||||
orderable=False,
|
||||
verbose_name='Units'
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:rackreservation_list'
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=RACKRESERVATION_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
@ -408,7 +411,8 @@ class RackReservationTable(BaseTable):
|
||||
class Meta(BaseTable.Meta):
|
||||
model = RackReservation
|
||||
fields = (
|
||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'actions',
|
||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
|
||||
'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description', 'actions',
|
||||
@ -1086,12 +1090,15 @@ class CableTable(BaseTable):
|
||||
order_by='_abs_length'
|
||||
)
|
||||
color = ColorColumn()
|
||||
tags = TagColumn(
|
||||
url_name='dcim:cable_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Cable
|
||||
fields = (
|
||||
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
|
||||
'status', 'type', 'color', 'length',
|
||||
'status', 'type', 'color', 'length', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
|
||||
@ -1245,10 +1252,13 @@ class PowerPanelTable(BaseTable):
|
||||
template_code=POWERPANEL_POWERFEED_COUNT,
|
||||
verbose_name='Feeds'
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:powerpanel_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = PowerPanel
|
||||
fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
|
||||
fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count', 'tags')
|
||||
default_columns = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
|
||||
|
||||
|
||||
|
@ -202,6 +202,7 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'user': user3.pk,
|
||||
'tenant': None,
|
||||
'description': 'Rack reservation',
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
@ -1510,6 +1511,7 @@ class CableTestCase(
|
||||
'color': 'c0c0c0',
|
||||
'length': 100,
|
||||
'length_unit': CableLengthUnitChoices.UNIT_FOOT,
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
@ -1609,6 +1611,7 @@ class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'site': sites[1].pk,
|
||||
'rack_group': rackgroups[1].pk,
|
||||
'name': 'Power Panel X',
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
|
@ -81,6 +81,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'extras/inc/tags_panel.html' with tags=cable.tags.all url='dcim:cable_list' %}
|
||||
{% plugin_left_page cable %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
@ -29,5 +29,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.tags %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,6 +82,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'extras/inc/tags_panel.html' with tags=powerpanel.tags.all url='dcim:powerpanel_list' %}
|
||||
{% plugin_left_page powerpanel %}
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
@ -124,6 +124,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'extras/inc/tags_panel.html' with tags=rackreservation.tags.all url='dcim:rackreservation_list' %}
|
||||
{% plugin_left_page rackreservation %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
@ -16,6 +16,7 @@
|
||||
{% render_field form.tenant_group %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.description %}
|
||||
{% render_field form.tags %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
Reference in New Issue
Block a user