mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #10052: The cf attribute now returns deserialized custom field data
This commit is contained in:
@@ -1022,7 +1022,7 @@ class CustomFieldModelTest(TestCase):
|
||||
site = Site(name='Test Site', slug='test-site')
|
||||
|
||||
# Check custom field data on new instance
|
||||
site.cf['foo'] = 'abc'
|
||||
site.custom_field_data['foo'] = 'abc'
|
||||
self.assertEqual(site.cf['foo'], 'abc')
|
||||
|
||||
# Check custom field data from database
|
||||
@@ -1037,12 +1037,12 @@ class CustomFieldModelTest(TestCase):
|
||||
site = Site(name='Test Site', slug='test-site')
|
||||
|
||||
# Set custom field data
|
||||
site.cf['foo'] = 'abc'
|
||||
site.cf['bar'] = 'def'
|
||||
site.custom_field_data['foo'] = 'abc'
|
||||
site.custom_field_data['bar'] = 'def'
|
||||
with self.assertRaises(ValidationError):
|
||||
site.clean()
|
||||
|
||||
del site.cf['bar']
|
||||
del site.custom_field_data['bar']
|
||||
site.clean()
|
||||
|
||||
def test_missing_required_field(self):
|
||||
@@ -1056,11 +1056,11 @@ class CustomFieldModelTest(TestCase):
|
||||
site = Site(name='Test Site', slug='test-site')
|
||||
|
||||
# Set custom field data with a required field omitted
|
||||
site.cf['foo'] = 'abc'
|
||||
site.custom_field_data['foo'] = 'abc'
|
||||
with self.assertRaises(ValidationError):
|
||||
site.clean()
|
||||
|
||||
site.cf['baz'] = 'def'
|
||||
site.custom_field_data['baz'] = 'def'
|
||||
site.clean()
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.db.models.signals import class_prepared
|
||||
@@ -133,18 +134,35 @@ class CustomFieldsMixin(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def cf(self):
|
||||
"""
|
||||
A pass-through convenience alias for accessing `custom_field_data` (read-only).
|
||||
Return a dictionary mapping each custom field for this instance to its deserialized value.
|
||||
|
||||
```python
|
||||
>>> tenant = Tenant.objects.first()
|
||||
>>> tenant.cf
|
||||
{'cust_id': 'CYB01'}
|
||||
{'primary_site': <Site: DM-NYC>, 'cust_id': 'DMI01', 'is_active': True}
|
||||
```
|
||||
"""
|
||||
return self.custom_field_data
|
||||
return {
|
||||
cf.name: cf.deserialize(self.custom_field_data.get(cf.name))
|
||||
for cf in self.custom_fields
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def custom_fields(self):
|
||||
"""
|
||||
Return the QuerySet of CustomFields assigned to this model.
|
||||
|
||||
```python
|
||||
>>> tenant = Tenant.objects.first()
|
||||
>>> tenant.custom_fields
|
||||
<RestrictedQuerySet [<CustomField: Primary site>, <CustomField: Customer ID>, <CustomField: Is active>]>
|
||||
```
|
||||
"""
|
||||
from extras.models import CustomField
|
||||
return CustomField.objects.get_for_model(self)
|
||||
|
||||
def get_custom_fields(self, omit_hidden=False):
|
||||
"""
|
||||
@@ -155,10 +173,13 @@ class CustomFieldsMixin(models.Model):
|
||||
>>> tenant.get_custom_fields()
|
||||
{<CustomField: Customer ID>: 'CYB01'}
|
||||
```
|
||||
|
||||
Args:
|
||||
omit_hidden: If True, custom fields with no UI visibility will be omitted.
|
||||
"""
|
||||
from extras.models import CustomField
|
||||
|
||||
data = {}
|
||||
|
||||
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:
|
||||
@@ -172,12 +193,28 @@ class CustomFieldsMixin(models.Model):
|
||||
def get_custom_fields_by_group(self):
|
||||
"""
|
||||
Return a dictionary of custom field/value mappings organized by group. Hidden fields are omitted.
|
||||
"""
|
||||
grouped_custom_fields = defaultdict(dict)
|
||||
for cf, value in self.get_custom_fields(omit_hidden=True).items():
|
||||
grouped_custom_fields[cf.group_name][cf] = value
|
||||
|
||||
return dict(grouped_custom_fields)
|
||||
```python
|
||||
>>> tenant = Tenant.objects.first()
|
||||
>>> tenant.get_custom_fields_by_group()
|
||||
{
|
||||
'': {<CustomField: Primary site>: <Site: DM-NYC>},
|
||||
'Billing': {<CustomField: Customer ID>: 'DMI01', <CustomField: Is active>: True}
|
||||
}
|
||||
```
|
||||
"""
|
||||
from extras.models import CustomField
|
||||
groups = defaultdict(dict)
|
||||
visible_custom_fields = CustomField.objects.get_for_model(self).exclude(
|
||||
ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN
|
||||
)
|
||||
|
||||
for cf in visible_custom_fields:
|
||||
value = self.custom_field_data.get(cf.name)
|
||||
value = cf.deserialize(value)
|
||||
groups[cf.group_name][cf] = value
|
||||
|
||||
return dict(groups)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@@ -82,7 +82,7 @@ class SearchIndex:
|
||||
# Capture custom fields
|
||||
if getattr(instance, 'custom_field_data', None):
|
||||
if custom_fields is None:
|
||||
custom_fields = instance.get_custom_fields().keys()
|
||||
custom_fields = instance.custom_fields
|
||||
for cf in custom_fields:
|
||||
type_ = cf.search_type
|
||||
value = instance.custom_field_data.get(cf.name)
|
||||
|
||||
Reference in New Issue
Block a user