Add ui_visible and ui_editable fields

This commit is contained in:
Jeremy Stretch 2023-11-16 09:12:58 -05:00
parent 840b7d804c
commit b1a60ca894
17 changed files with 167 additions and 52 deletions

View File

@ -97,14 +97,16 @@ class CustomFieldSerializer(ValidatedModelSerializer):
data_type = serializers.SerializerMethodField() data_type = serializers.SerializerMethodField()
choice_set = NestedCustomFieldChoiceSetSerializer(required=False) choice_set = NestedCustomFieldChoiceSetSerializer(required=False)
ui_visibility = ChoiceField(choices=CustomFieldVisibilityChoices, required=False) ui_visibility = ChoiceField(choices=CustomFieldVisibilityChoices, required=False)
ui_visible = ChoiceField(choices=CustomFieldUIVisibleChoices, required=False)
ui_editable = ChoiceField(choices=CustomFieldUIEditableChoices, 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', 'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'default', 'description', 'required', 'search_weight', 'filter_logic', 'ui_visibility', 'ui_visible', 'ui_editable',
'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'created', 'is_cloneable', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
'last_updated', 'choice_set', 'created', 'last_updated',
] ]
def validate_type(self, value): def validate_type(self, value):

View File

@ -68,6 +68,32 @@ class CustomFieldVisibilityChoices(ChoiceSet):
) )
class CustomFieldUIVisibleChoices(ChoiceSet):
ALWAYS = 'always'
IF_SET = 'if-set'
HIDDEN = 'hidden'
CHOICES = (
(ALWAYS, _('Always'), 'green'),
(IF_SET, _('If set'), 'yellow'),
(HIDDEN, _('Hidden'), 'gray'),
)
class CustomFieldUIEditableChoices(ChoiceSet):
YES = 'yes'
NO = 'no'
HIDDEN = 'hidden'
CHOICES = (
(YES, _('Yes'), 'green'),
(NO, _('No'), 'red'),
(HIDDEN, _('Hidden'), 'gray'),
)
class CustomFieldChoiceSetBaseChoices(ChoiceSet): class CustomFieldChoiceSetBaseChoices(ChoiceSet):
IATA = 'IATA' IATA = 'IATA'

View File

@ -88,7 +88,7 @@ class CustomFieldFilterSet(BaseFilterSet):
model = CustomField model = CustomField
fields = [ fields = [
'id', 'content_types', 'name', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visibility', 'id', 'content_types', 'name', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visibility',
'weight', 'is_cloneable', 'description', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable', 'description',
] ]
def search(self, queryset, name, value): def search(self, queryset, name, value):

View File

@ -54,6 +54,16 @@ class CustomFieldBulkEditForm(BulkEditForm):
required=False, required=False,
initial='' initial=''
) )
ui_visible = forms.ChoiceField(
label=_("UI visible"),
choices=add_blank_choice(CustomFieldUIVisibleChoices),
required=False
)
ui_editable = forms.ChoiceField(
label=_("UI editable"),
choices=add_blank_choice(CustomFieldUIEditableChoices),
required=False
)
is_cloneable = forms.NullBooleanField( is_cloneable = forms.NullBooleanField(
label=_('Is cloneable'), label=_('Is cloneable'),
required=False, required=False,

View File

@ -56,13 +56,25 @@ class CustomFieldImportForm(CSVModelForm):
choices=CustomFieldVisibilityChoices, choices=CustomFieldVisibilityChoices,
help_text=_('How the custom field is displayed in the user interface') help_text=_('How the custom field is displayed in the user interface')
) )
ui_visible = CSVChoiceField(
label=_('UI visible'),
choices=CustomFieldUIVisibleChoices,
required=False,
help_text=_('Whether the custom field is displayed in the UI')
)
ui_editable = CSVChoiceField(
label=_('UI editable'),
choices=CustomFieldUIEditableChoices,
required=False,
help_text=_('Whether the custom field is editable in the UI')
)
class Meta: class Meta:
model = CustomField model = CustomField
fields = ( fields = (
'name', 'label', 'group_name', 'type', 'content_types', 'object_type', 'required', 'description', 'name', 'label', 'group_name', 'type', 'content_types', 'object_type', 'required', 'description',
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum', 'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
'validation_maximum', 'validation_regex', 'ui_visibility', 'is_cloneable', 'validation_maximum', 'validation_regex', 'ui_visibility', 'ui_visible', 'ui_editable', 'is_cloneable',
) )

View File

@ -40,7 +40,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
(None, ('q', 'filter_id')), (None, ('q', 'filter_id')),
(_('Attributes'), ( (_('Attributes'), (
'type', 'content_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visibility', 'type', 'content_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visibility',
'is_cloneable', 'ui_visible', 'ui_editable', 'is_cloneable',
)), )),
) )
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
@ -78,6 +78,16 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
required=False, required=False,
label=_('UI visibility') label=_('UI visibility')
) )
ui_visible = forms.ChoiceField(
choices=add_blank_choice(CustomFieldUIVisibleChoices),
required=False,
label=_('UI visible')
)
ui_editable = forms.ChoiceField(
choices=add_blank_choice(CustomFieldUIEditableChoices),
required=False,
label=_('UI editable')
)
is_cloneable = forms.NullBooleanField( is_cloneable = forms.NullBooleanField(
label=_('Is cloneable'), label=_('Is cloneable'),
required=False, required=False,

View File

@ -2,7 +2,7 @@ from django import forms
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 _
from extras.choices import CustomFieldVisibilityChoices from extras.choices import *
from extras.models import * from extras.models import *
from utilities.forms.fields import DynamicModelMultipleChoiceField from utilities.forms.fields import DynamicModelMultipleChoiceField
@ -40,7 +40,7 @@ class CustomFieldsMixin:
def _get_custom_fields(self, content_type): def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type).exclude( return CustomField.objects.filter(content_types=content_type).exclude(
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN ui_visible=CustomFieldUIVisibleChoices.HIDDEN
) )
def _get_form_field(self, customfield): def _get_form_field(self, customfield):
@ -51,9 +51,6 @@ 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)

View File

@ -61,7 +61,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
(_('Custom Field'), ( (_('Custom Field'), (
'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description', 'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
)), )),
(_('Behavior'), ('search_weight', 'filter_logic', 'ui_visibility', 'weight', 'is_cloneable')), (_('Behavior'), ('search_weight', 'filter_logic', 'ui_visibility', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable')),
(_('Values'), ('default', 'choice_set')), (_('Values'), ('default', 'choice_set')),
(_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')), (_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')),
) )

View File

@ -0,0 +1,21 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('extras', '0099_cachedvalue_ordering'),
]
operations = [
migrations.AddField(
model_name='customfield',
name='ui_editable',
field=models.CharField(default='yes', max_length=50),
),
migrations.AddField(
model_name='customfield',
name='ui_visible',
field=models.CharField(default='always', max_length=50),
),
]

View File

@ -10,7 +10,6 @@ from django.contrib.postgres.fields import ArrayField
from django.core.validators import RegexValidator, ValidationError from django.core.validators import RegexValidator, ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -187,6 +186,20 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
verbose_name=_('UI visibility'), verbose_name=_('UI visibility'),
help_text=_('Specifies the visibility of custom field in the UI') help_text=_('Specifies the visibility of custom field in the UI')
) )
ui_visible = models.CharField(
max_length=50,
choices=CustomFieldUIVisibleChoices,
default=CustomFieldUIVisibleChoices.ALWAYS,
verbose_name=_('UI visible'),
help_text=_('Specifies whether the custom field is displayed in the UI')
)
ui_editable = models.CharField(
max_length=50,
choices=CustomFieldUIEditableChoices,
default=CustomFieldUIEditableChoices.YES,
verbose_name=_('UI editable'),
help_text=_('Specifies whether the custom field value can be edited in the UI')
)
is_cloneable = models.BooleanField( is_cloneable = models.BooleanField(
default=False, default=False,
verbose_name=_('is cloneable'), verbose_name=_('is cloneable'),
@ -198,7 +211,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
clone_fields = ( clone_fields = (
'content_types', 'type', 'object_type', 'group_name', 'description', 'required', 'search_weight', 'content_types', 'type', 'object_type', 'group_name', 'description', 'required', 'search_weight',
'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
'choice_set', 'ui_visibility', 'is_cloneable', 'choice_set', 'ui_visibility', 'ui_visible', 'ui_editable', 'is_cloneable',
) )
class Meta: class Meta:
@ -232,6 +245,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
return self.choice_set.choices return self.choice_set.choices
return [] return []
def get_ui_visible_color(self):
return CustomFieldUIVisibleChoices.colors.get(self.ui_visible)
def get_ui_editable_color(self):
return CustomFieldUIEditableChoices.colors.get(self.ui_editable)
def get_choice_label(self, value): def get_choice_label(self, value):
if not hasattr(self, '_choice_map'): if not hasattr(self, '_choice_map'):
self._choice_map = dict(self.choices) self._choice_map = dict(self.choices)
@ -382,7 +401,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
set_initial: Set initial data for the field. This should be False when generating a field for bulk editing. set_initial: Set initial data for the field. This should be False when generating a field for bulk editing.
enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing. enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing.
enforce_visibility: Honor the value of CustomField.ui_visibility. Set to False for filtering. enforce_visibility: Honor the value of CustomField.ui_visible. Set to False for filtering.
for_csv_import: Return a form field suitable for bulk import of objects in CSV format. for_csv_import: Return a form field suitable for bulk import of objects in CSV format.
""" """
initial = self.default if set_initial else None initial = self.default if set_initial else None
@ -507,10 +526,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
field.help_text = render_markdown(self.description) field.help_text = render_markdown(self.description)
# Annotate read-only fields # Annotate read-only fields
if enforce_visibility and self.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY: if enforce_visibility and self.ui_editable != CustomFieldUIEditableChoices.YES:
field.disabled = True field.disabled = True
prepend = '<br />' if field.help_text else '' prepend = '<br />' if field.help_text else ''
field.help_text += f'{prepend}<i class="mdi mdi-alert-circle-outline"></i> ' + _('Field is set to read-only.') field.help_text += f'{prepend}<i class="mdi mdi-alert-circle-outline"></i> ' + _('Field is not editable.')
return field return field

View File

@ -74,6 +74,12 @@ class CustomFieldTable(NetBoxTable):
ui_visibility = columns.ChoiceFieldColumn( ui_visibility = columns.ChoiceFieldColumn(
verbose_name=_('UI Visibility') verbose_name=_('UI Visibility')
) )
ui_visible = columns.ChoiceFieldColumn(
verbose_name=_('Visible')
)
ui_editable = columns.ChoiceFieldColumn(
verbose_name=_('Editable')
)
description = columns.MarkdownColumn( description = columns.MarkdownColumn(
verbose_name=_('Description') verbose_name=_('Description')
) )
@ -94,8 +100,8 @@ class CustomFieldTable(NetBoxTable):
model = CustomField model = CustomField
fields = ( fields = (
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'default', 'description', 'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'default', 'description',
'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'weight', 'choice_set', 'choices', 'search_weight', 'filter_logic', 'ui_visibility', 'ui_visible', 'ui_editable', 'is_cloneable', 'weight',
'created', 'last_updated', 'choice_set', 'choices', 'created', 'last_updated',
) )
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

@ -40,7 +40,8 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
required=True, required=True,
weight=100, weight=100,
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE, filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE,
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE ui_visible=CustomFieldUIVisibleChoices.ALWAYS,
ui_editable=CustomFieldUIEditableChoices.YES
), ),
CustomField( CustomField(
name='Custom Field 2', name='Custom Field 2',
@ -48,7 +49,8 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
required=False, required=False,
weight=200, weight=200,
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT, filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT,
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY ui_visible=CustomFieldUIVisibleChoices.IF_SET,
ui_editable=CustomFieldUIEditableChoices.NO
), ),
CustomField( CustomField(
name='Custom Field 3', name='Custom Field 3',
@ -56,7 +58,8 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
required=False, required=False,
weight=300, weight=300,
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED, filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN ui_visible=CustomFieldUIVisibleChoices.HIDDEN,
ui_editable=CustomFieldUIEditableChoices.HIDDEN
), ),
CustomField( CustomField(
name='Custom Field 4', name='Custom Field 4',
@ -64,7 +67,8 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
required=False, required=False,
weight=400, weight=400,
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED, filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN, ui_visible=CustomFieldUIVisibleChoices.HIDDEN,
ui_editable=CustomFieldUIEditableChoices.HIDDEN,
choice_set=choice_sets[0] choice_set=choice_sets[0]
), ),
CustomField( CustomField(
@ -73,7 +77,8 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
required=False, required=False,
weight=500, weight=500,
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED, filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN, ui_visible=CustomFieldUIVisibleChoices.HIDDEN,
ui_editable=CustomFieldUIEditableChoices.HIDDEN,
choice_set=choice_sets[1] choice_set=choice_sets[1]
), ),
) )
@ -106,8 +111,12 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
params = {'filter_logic': CustomFieldFilterLogicChoices.FILTER_LOOSE} params = {'filter_logic': CustomFieldFilterLogicChoices.FILTER_LOOSE}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_ui_visibility(self): def test_ui_visible(self):
params = {'ui_visibility': CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE} params = {'ui_visible': CustomFieldUIVisibleChoices.ALWAYS}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_ui_editable(self):
params = {'ui_editable': CustomFieldUIEditableChoices.YES}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_choice_set(self): def test_choice_set(self):

View File

@ -50,15 +50,16 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'default': None, 'default': None,
'weight': 200, 'weight': 200,
'required': True, 'required': True,
'ui_visibility': CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE, 'ui_visible': CustomFieldUIVisibleChoices.ALWAYS,
'ui_editable': CustomFieldUIEditableChoices.YES,
} }
cls.csv_data = ( cls.csv_data = (
'name,label,type,content_types,object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visibility', 'name,label,type,content_types,object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},read-write', 'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,read-write', 'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,read-write', 'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
'field7,Field 7,object,dcim.site,dcim.region,100,4000,exact,,,,,read-write', 'field7,Field 7,object,dcim.site,dcim.region,100,4000,exact,,,,,always,yes',
) )
cls.csv_update_data = ( cls.csv_update_data = (

View File

@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices from extras.choices import *
from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin, TagsMixin from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin, TagsMixin
from extras.models import CustomField, Tag from extras.models import CustomField, Tag
from utilities.forms import CSVModelForm from utilities.forms import CSVModelForm
@ -76,11 +76,9 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
) )
def _get_custom_fields(self, content_type): def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type).filter( return CustomField.objects.filter(
ui_visibility__in=[ content_types=content_type,
CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE, ui_editable=CustomFieldUIEditableChoices.YES
CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET,
]
) )
def _get_form_field(self, customfield): def _get_form_field(self, customfield):
@ -131,7 +129,8 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
def _extend_nullable_fields(self): def _extend_nullable_fields(self):
nullable_custom_fields = [ nullable_custom_fields = [
name for name, customfield in self.custom_fields.items() if (not customfield.required and customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE) name for name, customfield in self.custom_fields.items()
if (not customfield.required and customfield.ui_editable == CustomFieldUIEditableChoices.YES)
] ]
self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields) self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields)

View File

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from core.choices import JobStatusChoices from core.choices import JobStatusChoices
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices from extras.choices import *
from extras.utils import is_taggable, register_features from extras.utils import is_taggable, register_features
from netbox.registry import registry from netbox.registry import registry
from netbox.signals import post_clean from netbox.signals import post_clean
@ -205,12 +205,11 @@ class CustomFieldsMixin(models.Model):
for field in CustomField.objects.get_for_model(self): for field in CustomField.objects.get_for_model(self):
value = self.custom_field_data.get(field.name) value = self.custom_field_data.get(field.name)
# Skip fields that are hidden if 'omit_hidden' is set # Skip hidden fields if 'omit_hidden' is True
if omit_hidden: if omit_hidden and field.ui_visible == CustomFieldUIVisibleChoices.HIDDEN:
if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN: continue
continue elif omit_hidden and field.ui_visible == CustomFieldUIVisibleChoices.IF_SET and not value:
if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET and not value: continue
continue
data[field] = field.deserialize(value) data[field] = field.deserialize(value)
@ -232,12 +231,12 @@ class CustomFieldsMixin(models.Model):
from extras.models import CustomField from extras.models import CustomField
groups = defaultdict(dict) groups = defaultdict(dict)
visible_custom_fields = CustomField.objects.get_for_model(self).exclude( visible_custom_fields = CustomField.objects.get_for_model(self).exclude(
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN ui_visible=CustomFieldUIVisibleChoices.HIDDEN
) )
for cf in visible_custom_fields: for cf in visible_custom_fields:
value = self.custom_field_data.get(cf.name) value = self.custom_field_data.get(cf.name)
if value in (None, []) and cf.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET: if value in (None, []) and cf.ui_visible == CustomFieldUIVisibleChoices.IF_SET:
continue continue
value = cf.deserialize(value) value = cf.deserialize(value)
groups[cf.group_name][cf] = value groups[cf.group_name][cf] = value

View File

@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
from django_tables2.data import TableQuerysetData from django_tables2.data import TableQuerysetData
from extras.models import CustomField, CustomLink from extras.models import CustomField, CustomLink
from extras.choices import CustomFieldVisibilityChoices from extras.choices import *
from netbox.tables import columns from netbox.tables import columns
from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.utils import get_viewname, highlight_string, title from utilities.utils import get_viewname, highlight_string, title
@ -195,7 +195,7 @@ class NetBoxTable(BaseTable):
content_type = ContentType.objects.get_for_model(self._meta.model) content_type = ContentType.objects.get_for_model(self._meta.model)
custom_fields = CustomField.objects.filter( custom_fields = CustomField.objects.filter(
content_types=content_type content_types=content_type
).exclude(ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN) ).exclude(ui_visible=CustomFieldUIVisibleChoices.HIDDEN)
extra_columns.extend([ extra_columns.extend([
(f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields

View File

@ -79,8 +79,12 @@
<td>{{ object.weight }}</td> <td>{{ object.weight }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans "UI Visibility" %}</th> <th scope="row">{% trans "UI Visible" %}</th>
<td>{{ object.get_ui_visibility_display }}</td> <td>{{ object.get_ui_visible_display }}</td>
</tr>
<tr>
<th scope="row">{% trans "UI Editable" %}</th>
<td>{{ object.get_ui_editable_display }}</td>
</tr> </tr>
</table> </table>
</div> </div>