#9166 - Add UI Visibility setting for custom fields

This commit is contained in:
kkthxbye 2022-05-24 10:12:32 +02:00
parent 64146b8cb1
commit 20eaa7d069
14 changed files with 84 additions and 13 deletions

View File

@ -84,13 +84,14 @@ class CustomFieldSerializer(ValidatedModelSerializer):
) )
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False) filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
data_type = serializers.SerializerMethodField() data_type = serializers.SerializerMethodField()
ui_visibility = ChoiceField(choices=CustomFieldVisibilityChoices, required=False)
class Meta: class Meta:
model = CustomField model = CustomField
fields = [ fields = [
'id', 'url', 'display', 'content_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name', 'id', 'url', 'display', 'content_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
'description', 'required', 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'description', 'required', 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum',
'validation_regex', 'choices', 'created', 'last_updated', 'validation_regex', 'choices', 'created', 'last_updated', 'ui_visibility',
] ]
def get_data_type(self, obj): def get_data_type(self, obj):

View File

@ -47,6 +47,19 @@ class CustomFieldFilterLogicChoices(ChoiceSet):
) )
class CustomFieldVisibilityChoices(ChoiceSet):
VISIBILITY_READ_WRITE = 'read-write'
VISIBILITY_READ_ONLY = 'read-only'
VISIBILITY_HIDDEN = 'hidden'
CHOICES = (
(VISIBILITY_READ_WRITE, 'Read/Write'),
(VISIBILITY_READ_ONLY, 'Read-only'),
(VISIBILITY_HIDDEN, 'Hidden'),
)
# #
# CustomLinks # CustomLinks
# #

View File

@ -62,7 +62,9 @@ class CustomFieldFilterSet(BaseFilterSet):
class Meta: class Meta:
model = CustomField model = CustomField
fields = ['id', 'content_types', 'name', 'group_name', 'required', 'filter_logic', 'weight', 'description'] fields = [
'id', 'content_types', 'name', 'group_name', 'required', 'filter_logic', 'weight', 'description', 'ui_visibility'
]
def search(self, queryset, name, value): def search(self, queryset, name, value):
if not value.strip(): if not value.strip():

View File

@ -37,6 +37,13 @@ class CustomFieldBulkEditForm(BulkEditForm):
weight = forms.IntegerField( weight = forms.IntegerField(
required=False required=False
) )
ui_visibility = forms.ChoiceField(
label="UI visibility",
choices=add_blank_choice(CustomFieldVisibilityChoices),
required=False,
initial='',
widget=StaticSelect()
)
nullable_fields = ('group_name', 'description',) nullable_fields = ('group_name', 'description',)

View File

@ -37,7 +37,7 @@ class CustomFieldCSVForm(CSVModelForm):
model = CustomField model = CustomField
fields = ( fields = (
'name', 'label', 'group_name', 'type', 'content_types', 'required', 'description', 'weight', 'filter_logic', 'name', 'label', 'group_name', 'type', 'content_types', 'required', 'description', 'weight', 'filter_logic',
'default', 'choices', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'default', 'choices', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'ui_visibility',
) )

View File

@ -1,6 +1,7 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from extras.models import * from extras.models import *
from extras.choices import CustomFieldVisibilityChoices
__all__ = ( __all__ = (
'CustomFieldsMixin', 'CustomFieldsMixin',
@ -42,8 +43,14 @@ class CustomFieldsMixin:
Append form fields for all CustomFields assigned to this object type. Append form fields for all CustomFields assigned to this object type.
""" """
for customfield in self._get_custom_fields(self._get_content_type()): for customfield in self._get_custom_fields(self._get_content_type()):
if customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN:
continue
field_name = f'cf_{customfield.name}' field_name = f'cf_{customfield.name}'
self.fields[field_name] = self._get_form_field(customfield) self.fields[field_name] = self._get_form_field(customfield)
if customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
self.fields[field_name].disabled = True
# Annotate the field in the list of CustomField form fields # Annotate the field in the list of CustomField form fields
self.custom_fields[field_name] = customfield self.custom_fields[field_name] = customfield

View File

@ -32,7 +32,7 @@ __all__ = (
class CustomFieldFilterForm(FilterForm): class CustomFieldFilterForm(FilterForm):
fieldsets = ( fieldsets = (
(None, ('q',)), (None, ('q',)),
('Attributes', ('content_types', 'type', 'group_name', 'weight', 'required')), ('Attributes', ('content_types', 'type', 'group_name', 'weight', 'required', 'ui_visibility')),
) )
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.all(),
@ -56,6 +56,12 @@ class CustomFieldFilterForm(FilterForm):
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
ui_visibility = forms.ChoiceField(
choices=add_blank_choice(CustomFieldVisibilityChoices),
required=False,
label=_('UI Visibility'),
widget=StaticSelect()
)
class CustomLinkFilterForm(FilterForm): class CustomLinkFilterForm(FilterForm):

View File

@ -41,7 +41,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
fieldsets = ( fieldsets = (
('Custom Field', ( ('Custom Field', (
'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'weight', 'required', 'description', 'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'weight', 'required', 'description', 'ui_visibility',
)), )),
('Behavior', ('filter_logic',)), ('Behavior', ('filter_logic',)),
('Values', ('default', 'choices')), ('Values', ('default', 'choices')),
@ -58,6 +58,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
widgets = { widgets = {
'type': StaticSelect(), 'type': StaticSelect(),
'filter_logic': StaticSelect(), 'filter_logic': StaticSelect(),
'ui_visibility': StaticSelect(),
} }

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-23 20:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('extras', '0074_customfield_group_name'),
]
operations = [
migrations.AddField(
model_name='customfield',
name='ui_visibility',
field=models.CharField(default='read-write', max_length=50),
),
]

View File

@ -136,6 +136,12 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
null=True, null=True,
help_text='Comma-separated list of available choices (for selection fields)' help_text='Comma-separated list of available choices (for selection fields)'
) )
ui_visibility = models.CharField(
max_length=50,
choices=CustomFieldVisibilityChoices,
default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
help_text='Specifies the visibility of custom field in the UI.'
)
objects = CustomFieldManager() objects = CustomFieldManager()
class Meta: class Meta:

View File

@ -28,12 +28,13 @@ class CustomFieldTable(NetBoxTable):
) )
content_types = columns.ContentTypesColumn() content_types = columns.ContentTypesColumn()
required = columns.BooleanColumn() required = columns.BooleanColumn()
ui_visibility = columns.ChoiceFieldColumn(verbose_name="UI visibility")
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = CustomField model = CustomField
fields = ( fields = (
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'weight', 'default', 'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'weight', 'default',
'description', 'filter_logic', 'choices', 'created', 'last_updated', 'description', 'filter_logic', 'choices', 'created', 'last_updated', 'ui_visibility',
) )
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description') default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')

View File

@ -36,13 +36,14 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'default': None, 'default': None,
'weight': 200, 'weight': 200,
'required': True, 'required': True,
'ui_visibility': CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
} }
cls.csv_data = ( cls.csv_data = (
'name,label,type,content_types,weight,filter_logic,choices,validation_minimum,validation_maximum,validation_regex', 'name,label,type,content_types,weight,filter_logic,choices,validation_minimum,validation_maximum,validation_regex,ui_visibility',
'field4,Field 4,text,dcim.site,100,exact,,,,[a-z]{3}', 'field4,Field 4,text,dcim.site,100,exact,,,,[a-z]{3},read-write',
'field5,Field 5,integer,dcim.site,100,exact,,1,100,', 'field5,Field 5,integer,dcim.site,100,exact,,1,100,,read-write',
'field6,Field 6,select,dcim.site,100,exact,"A,B,C",,,', 'field6,Field 6,select,dcim.site,100,exact,"A,B,C",,,,read-write',
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {

View File

@ -9,7 +9,7 @@ from django.core.validators import ValidationError
from django.db import models from django.db import models
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from extras.choices import ObjectChangeActionChoices from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
from extras.utils import register_features from extras.utils import register_features
from netbox.signals import post_clean from netbox.signals import post_clean
from utilities.utils import serialize_object from utilities.utils import serialize_object
@ -100,7 +100,7 @@ class CustomFieldsMixin(models.Model):
""" """
return self.custom_field_data return self.custom_field_data
def get_custom_fields(self): def get_custom_fields(self, omit_hidden=False):
""" """
Return a dictionary of custom fields for a single object in the form `{field: value}`. Return a dictionary of custom fields for a single object in the form `{field: value}`.
@ -114,6 +114,10 @@ class CustomFieldsMixin(models.Model):
data = {} data = {}
for field in CustomField.objects.get_for_model(self): for field in CustomField.objects.get_for_model(self):
# Skip fields that are hidden if 'omit_hidden' is set
if omit_hidden and field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN:
continue
value = self.custom_field_data.get(field.name) value = self.custom_field_data.get(field.name)
data[field] = field.deserialize(value) data[field] = field.deserialize(value)
@ -124,7 +128,7 @@ class CustomFieldsMixin(models.Model):
Return a dictionary of custom field/value mappings organized by group. Return a dictionary of custom field/value mappings organized by group.
""" """
grouped_custom_fields = defaultdict(dict) grouped_custom_fields = defaultdict(dict)
for cf, value in self.get_custom_fields().items(): for cf, value in self.get_custom_fields(omit_hidden=True).items():
grouped_custom_fields[cf.group_name][cf] = value grouped_custom_fields[cf.group_name][cf] = value
return dict(grouped_custom_fields) return dict(grouped_custom_fields)

View File

@ -42,6 +42,10 @@
<th scope="row">Weight</th> <th scope="row">Weight</th>
<td>{{ object.weight }}</td> <td>{{ object.weight }}</td>
</tr> </tr>
<tr>
<th scope="row">UI Visibility</th>
<td>{{ object.get_ui_visibility_display }}</td>
</tr>
</table> </table>
</div> </div>
</div> </div>