1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #13299: Improve options for controlling custom field visibility (#14289)

* Add ui_visible and ui_editable fields

* Extend migration to map new visible/editable values

* Remove ui_visibility field

* Update docs
This commit is contained in:
Jeremy Stretch
2023-11-20 13:06:34 -05:00
committed by GitHub
parent 549b0ea107
commit a73ba00aa0
19 changed files with 204 additions and 93 deletions

View File

@ -40,14 +40,22 @@ Related custom fields can be grouped together within the UI by assigning each th
This parameter has no effect on the API representation of custom field data.
### Visibility
### Visibility & Editing
When creating a custom field, there are three options for UI visibility. These control how and whether the custom field is displayed within the NetBox UI.
!!! info "This feature was improved in NetBox v3.7."
* **Read/write** (default): The custom field is included when viewing and editing objects.
* **Read-only**: The custom field is displayed when viewing an object, but it cannot be edited via the UI. (It will appear in the form as a read-only field.)
When creating a custom field, users can control the conditions under which it may be displayed and edited within the NetBox user interface. The following choices are available for controlling the display of a custom field on an object:
* **Always** (default): The custom field is included when viewing an object.
* **If Set**: The custom field is included only if a value has been defined for the object.
* **Hidden**: The custom field will never be displayed within the UI. This option is recommended for fields which are not intended for use by human users.
Additionally, the following options are available for controlling whether custom field values can be altered within the NetBox UI:
* **Yes** (default): The custom field's value may be modified when editing an object.
* **No**: The custom field is displayed for reference when editing an object, but its value may not be modified.
* **Hidden**: The custom field is not displayed when editing an object.
Note that this setting has no impact on the REST or GraphQL APIs: Custom field data will always be available via either API.
### Validation

View File

@ -64,16 +64,25 @@ Defines how filters are evaluated against custom field values.
| Loose | Match any occurrence of the value |
| Exact | Match only the complete field value |
### UI Visibility
### UI Visible
Controls how and whether the custom field is displayed within the NetBox user interface.
Controls whether the custom field is displayed for objects within the NetBox user interface.
| Option | Description |
|-------------------|--------------------------------------------------|
| Read/write | Display and permit editing (default) |
| Read-only | Display field but disallow editing |
| Hidden | Do not display field in the UI |
| Hidden (if unset) | Display in the UI only when a value has been set |
| Option | Description |
|--------|----------------------------------------------------------------|
| Always | The field is always displayed when viewing an object (default) |
| If set | The field is displayed only if a value has been defined |
| Hidden | The field is not displayed when viewing an object |
### UI Editable
Controls whether the custom field is editable on objects within the NetBox user interface.
| Option | Description |
|--------|------------------------------------------------------------------------------|
| Yes | The field's value may be changed when editing an object (default) |
| No | The field's value is displayed when editing an object but may not be altered |
| Hidden | The field is not displayed when editing an object |
### Default

View File

@ -95,15 +95,16 @@ class CustomFieldSerializer(ValidatedModelSerializer):
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
data_type = serializers.SerializerMethodField()
choice_set = NestedCustomFieldChoiceSetSerializer(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:
model = CustomField
fields = [
'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',
'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'created',
'last_updated',
'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
'created', 'last_updated',
]
def validate_type(self, value):

View File

@ -53,18 +53,29 @@ class CustomFieldFilterLogicChoices(ChoiceSet):
)
class CustomFieldVisibilityChoices(ChoiceSet):
class CustomFieldUIVisibleChoices(ChoiceSet):
VISIBILITY_READ_WRITE = 'read-write'
VISIBILITY_READ_ONLY = 'read-only'
VISIBILITY_HIDDEN = 'hidden'
VISIBILITY_HIDDEN_IFUNSET = 'hidden-ifunset'
ALWAYS = 'always'
IF_SET = 'if-set'
HIDDEN = 'hidden'
CHOICES = (
(VISIBILITY_READ_WRITE, _('Read/write')),
(VISIBILITY_READ_ONLY, _('Read-only')),
(VISIBILITY_HIDDEN, _('Hidden')),
(VISIBILITY_HIDDEN_IFUNSET, _('Hidden (if unset)')),
(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'),
)

View File

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

View File

@ -48,11 +48,15 @@ class CustomFieldBulkEditForm(BulkEditForm):
queryset=CustomFieldChoiceSet.objects.all(),
required=False
)
ui_visibility = forms.ChoiceField(
label=_("UI visibility"),
choices=add_blank_choice(CustomFieldVisibilityChoices),
required=False,
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(
label=_('Is cloneable'),

View File

@ -49,10 +49,17 @@ class CustomFieldImportForm(CSVModelForm):
required=False,
help_text=_('Choice set (for selection fields)')
)
ui_visibility = CSVChoiceField(
label=_('UI visibility'),
choices=CustomFieldVisibilityChoices,
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:
@ -60,7 +67,7 @@ class CustomFieldImportForm(CSVModelForm):
fields = (
'name', 'label', 'group_name', 'type', 'content_types', 'object_type', 'required', 'description',
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
'validation_maximum', 'validation_regex', 'ui_visibility', 'is_cloneable',
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
)

View File

@ -38,7 +38,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
(None, ('q', 'filter_id')),
(_('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_visible', 'ui_editable',
'is_cloneable',
)),
)
@ -72,10 +72,15 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
required=False,
label=_('Choice set')
)
ui_visibility = forms.ChoiceField(
choices=add_blank_choice(CustomFieldVisibilityChoices),
ui_visible = forms.ChoiceField(
choices=add_blank_choice(CustomFieldUIVisibleChoices),
required=False,
label=_('UI visibility')
label=_('UI visible')
)
ui_editable = forms.ChoiceField(
choices=add_blank_choice(CustomFieldUIEditableChoices),
required=False,
label=_('UI editable')
)
is_cloneable = forms.NullBooleanField(
label=_('Is cloneable'),

View File

@ -2,7 +2,7 @@ from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from extras.choices import CustomFieldVisibilityChoices
from extras.choices import *
from extras.models import *
from utilities.forms.fields import DynamicModelMultipleChoiceField
@ -40,7 +40,7 @@ class CustomFieldsMixin:
def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type).exclude(
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN
ui_visible=CustomFieldUIVisibleChoices.HIDDEN
)
def _get_form_field(self, customfield):
@ -51,9 +51,6 @@ class CustomFieldsMixin:
Append form fields for all CustomFields assigned to this object 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}'
self.fields[field_name] = self._get_form_field(customfield)

View File

@ -59,7 +59,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
(_('Custom Field'), (
'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_visible', 'ui_editable', 'weight', 'is_cloneable')),
(_('Values'), ('default', 'choice_set')),
(_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')),
)

View File

@ -0,0 +1,41 @@
from django.db import migrations, models
def update_ui_attrs(apps, schema_editor):
"""
Replicate legacy ui_visibility values to the new ui_visible and ui_editable fields.
"""
CustomField = apps.get_model('extras', 'CustomField')
CustomField.objects.filter(ui_visibility='read-write').update(ui_visible='always', ui_editable='yes')
CustomField.objects.filter(ui_visibility='read-only').update(ui_visible='always', ui_editable='no')
CustomField.objects.filter(ui_visibility='hidden').update(ui_visible='hidden', ui_editable='hidden')
CustomField.objects.filter(ui_visibility='hidden-ifunset').update(ui_visible='if-set', ui_editable='yes')
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),
),
migrations.RunPython(
code=update_ui_attrs,
reverse_code=migrations.RunPython.noop
),
migrations.RemoveField(
model_name='customfield',
name='ui_visibility',
),
]

View File

@ -177,12 +177,19 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
blank=True,
null=True
)
ui_visibility = models.CharField(
ui_visible = models.CharField(
max_length=50,
choices=CustomFieldVisibilityChoices,
default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
verbose_name=_('UI visibility'),
help_text=_('Specifies the visibility of custom field in the UI')
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(
default=False,
@ -195,7 +202,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
clone_fields = (
'content_types', 'type', 'object_type', 'group_name', 'description', 'required', 'search_weight',
'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
'choice_set', 'ui_visibility', 'is_cloneable',
'choice_set', 'ui_visible', 'ui_editable', 'is_cloneable',
)
class Meta:
@ -229,6 +236,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
return self.choice_set.choices
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):
if not hasattr(self, '_choice_map'):
self._choice_map = dict(self.choices)
@ -379,7 +392,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.
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.
"""
initial = self.default if set_initial else None
@ -504,10 +517,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
field.help_text = render_markdown(self.description)
# 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
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

View File

@ -71,8 +71,11 @@ class CustomFieldTable(NetBoxTable):
required = columns.BooleanColumn(
verbose_name=_('Required')
)
ui_visibility = columns.ChoiceFieldColumn(
verbose_name=_('UI Visibility')
ui_visible = columns.ChoiceFieldColumn(
verbose_name=_('Visible')
)
ui_editable = columns.ChoiceFieldColumn(
verbose_name=_('Editable')
)
description = columns.MarkdownColumn(
verbose_name=_('Description')
@ -94,8 +97,8 @@ class CustomFieldTable(NetBoxTable):
model = CustomField
fields = (
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'default', 'description',
'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'weight', 'choice_set', 'choices',
'created', 'last_updated',
'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable', 'weight', 'choice_set',
'choices', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')

View File

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

View File

@ -50,15 +50,16 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'default': None,
'weight': 200,
'required': True,
'ui_visibility': CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
'ui_visible': CustomFieldUIVisibleChoices.ALWAYS,
'ui_editable': CustomFieldUIEditableChoices.YES,
}
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',
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},read-write',
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,read-write',
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,read-write',
'field7,Field 7,object,dcim.site,dcim.region,100,4000,exact,,,,,read-write',
'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},always,yes',
'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,,,,always,yes',
'field7,Field 7,object,dcim.site,dcim.region,100,4000,exact,,,,,always,yes',
)
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.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.models import CustomField, Tag
from utilities.forms import CSVModelForm
@ -76,11 +76,9 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
)
def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type).filter(
ui_visibility__in=[
CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET,
]
return CustomField.objects.filter(
content_types=content_type,
ui_editable=CustomFieldUIEditableChoices.YES
)
def _get_form_field(self, customfield):
@ -131,7 +129,8 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
def _extend_nullable_fields(self):
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)

View File

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

View File

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

View File

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