We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Empty search entries are being created for device asset tags
### Deployment Type

NetBox Cloud

### NetBox Version

v3.7.4

### Python Version

3.11

### Steps to Reproduce

1. Create a new device and note its database ID
2. In the NetBox shell, inspect all search entries associated with it:

```python
ct = ContentType.objects.get_for_model(Device)
device_id = 107
entries = CachedValue.objects.filter(object_type=ct, object_id=device_id)
for entry in entries:
    print(f'{entry.field}: {entry.value}')
```

### Expected Behavior

Only fields which have a meaningful value set should have search entries created.

### Observed Behavior

After creating a device with a description, I see three entries for it:

```
asset_tag: None
name: device1
description: asdasdasd
```

The value of `asset_tag` is null.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/netbox/search/__init__.py`
Content:
```
1 from collections import namedtuple
2 
3 from django.db import models
4 
5 from ipam.fields import IPAddressField, IPNetworkField
6 from netbox.registry import registry
7 
8 ObjectFieldValue = namedtuple('ObjectFieldValue', ('name', 'type', 'weight', 'value'))
9 
10 
11 class FieldTypes:
12     FLOAT = 'float'
13     INTEGER = 'int'
14     STRING = 'str'
15     INET = 'inet'
16     CIDR = 'cidr'
17 
18 
19 class LookupTypes:
20     PARTIAL = 'icontains'
21     EXACT = 'iexact'
22     STARTSWITH = 'istartswith'
23     ENDSWITH = 'iendswith'
24     REGEX = 'iregex'
25 
26 
27 class SearchIndex:
28     """
29     Base class for building search indexes.
30 
31     Attributes:
32         model: The model class for which this index is used.
33         category: The label of the group under which this indexer is categorized (for form field display). If none,
34             the name of the model's app will be used.
35         fields: An iterable of two-tuples defining the model fields to be indexed and the weight associated with each.
36         display_attrs: An iterable of additional object attributes to include when displaying search results.
37     """
38     model = None
39     category = None
40     fields = ()
41     display_attrs = ()
42 
43     @staticmethod
44     def get_field_type(instance, field_name):
45         """
46         Return the data type of the specified model field.
47         """
48         field_cls = instance._meta.get_field(field_name).__class__
49         if issubclass(field_cls, (models.FloatField, models.DecimalField)):
50             return FieldTypes.FLOAT
51         if issubclass(field_cls, IPAddressField):
52             return FieldTypes.INET
53         if issubclass(field_cls, IPNetworkField):
54             return FieldTypes.CIDR
55         if issubclass(field_cls, models.IntegerField):
56             return FieldTypes.INTEGER
57         return FieldTypes.STRING
58 
59     @staticmethod
60     def get_field_value(instance, field_name):
61         """
62         Return the value of the specified model field as a string.
63         """
64         return str(getattr(instance, field_name))
65 
66     @classmethod
67     def get_category(cls):
68         return cls.category or cls.model._meta.app_config.verbose_name
69 
70     @classmethod
71     def to_cache(cls, instance, custom_fields=None):
72         """
73         Return a list of ObjectFieldValue representing the instance fields to be cached.
74 
75         Args:
76             instance: The instance being cached.
77             custom_fields: An iterable of CustomFields to include when caching the instance. If None, all custom fields
78                 defined for the model will be included. (This can also be provided during bulk caching to avoid looking
79                 up the available custom fields for each instance.)
80         """
81         values = []
82 
83         # Capture built-in fields
84         for name, weight in cls.fields:
85             type_ = cls.get_field_type(instance, name)
86             value = cls.get_field_value(instance, name)
87             if type_ and value:
88                 values.append(
89                     ObjectFieldValue(name, type_, weight, value)
90                 )
91 
92         # Capture custom fields
93         if getattr(instance, 'custom_field_data', None):
94             if custom_fields is None:
95                 custom_fields = instance.custom_fields
96             for cf in custom_fields:
97                 type_ = cf.search_type
98                 value = instance.custom_field_data.get(cf.name)
99                 weight = cf.search_weight
100                 if type_ and value and weight:
101                     values.append(
102                         ObjectFieldValue(f'cf_{cf.name}', type_, weight, value)
103                     )
104 
105         return values
106 
107 
108 def get_indexer(model):
109     """
110     Get the SearchIndex class for the given model.
111     """
112     label = f'{model._meta.app_label}.{model._meta.model_name}'
113 
114     return registry['search'][label]
115 
116 
117 def register_search(cls):
118     """
119     Decorator for registering a SearchIndex class.
120     """
121     model = cls.model
122     label = f'{model._meta.app_label}.{model._meta.model_name}'
123     registry['search'][label] = cls
124 
125     return cls
126
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/netbox/netbox/search/__init__.py b/netbox/netbox/search/__init__.py
--- a/netbox/netbox/search/__init__.py
+++ b/netbox/netbox/search/__init__.py
@@ -59,9 +59,10 @@
     @staticmethod
     def get_field_value(instance, field_name):
         """
-        Return the value of the specified model field as a string.
+        Return the value of the specified model field as a string (or None).
         """
-        return str(getattr(instance, field_name))
+        if value := getattr(instance, field_name):
+            return str(value)
 
     @classmethod
     def get_category(cls):

{"golden_diff": "diff --git a/netbox/netbox/search/__init__.py b/netbox/netbox/search/__init__.py\n--- a/netbox/netbox/search/__init__.py\n+++ b/netbox/netbox/search/__init__.py\n@@ -59,9 +59,10 @@\n     @staticmethod\n     def get_field_value(instance, field_name):\n         \"\"\"\n-        Return the value of the specified model field as a string.\n+        Return the value of the specified model field as a string (or None).\n         \"\"\"\n-        return str(getattr(instance, field_name))\n+        if value := getattr(instance, field_name):\n+            return str(value)\n \n     @classmethod\n     def get_category(cls):\n", "issue": "Empty search entries are being created for device asset tags\n### Deployment Type\n\nNetBox Cloud\n\n### NetBox Version\n\nv3.7.4\n\n### Python Version\n\n3.11\n\n### Steps to Reproduce\n\n1. Create a new device and note its database ID\r\n2. In the NetBox shell, inspect all search entries associated with it:\r\n\r\n```python\r\nct = ContentType.objects.get_for_model(Device)\r\ndevice_id = 107\r\nentries = CachedValue.objects.filter(object_type=ct, object_id=device_id)\r\nfor entry in entries:\r\n    print(f'{entry.field}: {entry.value}')\r\n```\n\n### Expected Behavior\n\nOnly fields which have a meaningful value set should have search entries created.\n\n### Observed Behavior\n\nAfter creating a device with a description, I see three entries for it:\r\n\r\n```\r\nasset_tag: None\r\nname: device1\r\ndescription: asdasdasd\r\n```\r\n\r\nThe value of `asset_tag` is null.\n", "before_files": [{"content": "from collections import namedtuple\n\nfrom django.db import models\n\nfrom ipam.fields import IPAddressField, IPNetworkField\nfrom netbox.registry import registry\n\nObjectFieldValue = namedtuple('ObjectFieldValue', ('name', 'type', 'weight', 'value'))\n\n\nclass FieldTypes:\n    FLOAT = 'float'\n    INTEGER = 'int'\n    STRING = 'str'\n    INET = 'inet'\n    CIDR = 'cidr'\n\n\nclass LookupTypes:\n    PARTIAL = 'icontains'\n    EXACT = 'iexact'\n    STARTSWITH = 'istartswith'\n    ENDSWITH = 'iendswith'\n    REGEX = 'iregex'\n\n\nclass SearchIndex:\n    \"\"\"\n    Base class for building search indexes.\n\n    Attributes:\n        model: The model class for which this index is used.\n        category: The label of the group under which this indexer is categorized (for form field display). If none,\n            the name of the model's app will be used.\n        fields: An iterable of two-tuples defining the model fields to be indexed and the weight associated with each.\n        display_attrs: An iterable of additional object attributes to include when displaying search results.\n    \"\"\"\n    model = None\n    category = None\n    fields = ()\n    display_attrs = ()\n\n    @staticmethod\n    def get_field_type(instance, field_name):\n        \"\"\"\n        Return the data type of the specified model field.\n        \"\"\"\n        field_cls = instance._meta.get_field(field_name).__class__\n        if issubclass(field_cls, (models.FloatField, models.DecimalField)):\n            return FieldTypes.FLOAT\n        if issubclass(field_cls, IPAddressField):\n            return FieldTypes.INET\n        if issubclass(field_cls, IPNetworkField):\n            return FieldTypes.CIDR\n        if issubclass(field_cls, models.IntegerField):\n            return FieldTypes.INTEGER\n        return FieldTypes.STRING\n\n    @staticmethod\n    def get_field_value(instance, field_name):\n        \"\"\"\n        Return the value of the specified model field as a string.\n        \"\"\"\n        return str(getattr(instance, field_name))\n\n    @classmethod\n    def get_category(cls):\n        return cls.category or cls.model._meta.app_config.verbose_name\n\n    @classmethod\n    def to_cache(cls, instance, custom_fields=None):\n        \"\"\"\n        Return a list of ObjectFieldValue representing the instance fields to be cached.\n\n        Args:\n            instance: The instance being cached.\n            custom_fields: An iterable of CustomFields to include when caching the instance. If None, all custom fields\n                defined for the model will be included. (This can also be provided during bulk caching to avoid looking\n                up the available custom fields for each instance.)\n        \"\"\"\n        values = []\n\n        # Capture built-in fields\n        for name, weight in cls.fields:\n            type_ = cls.get_field_type(instance, name)\n            value = cls.get_field_value(instance, name)\n            if type_ and value:\n                values.append(\n                    ObjectFieldValue(name, type_, weight, value)\n                )\n\n        # Capture custom fields\n        if getattr(instance, 'custom_field_data', None):\n            if custom_fields is None:\n                custom_fields = instance.custom_fields\n            for cf in custom_fields:\n                type_ = cf.search_type\n                value = instance.custom_field_data.get(cf.name)\n                weight = cf.search_weight\n                if type_ and value and weight:\n                    values.append(\n                        ObjectFieldValue(f'cf_{cf.name}', type_, weight, value)\n                    )\n\n        return values\n\n\ndef get_indexer(model):\n    \"\"\"\n    Get the SearchIndex class for the given model.\n    \"\"\"\n    label = f'{model._meta.app_label}.{model._meta.model_name}'\n\n    return registry['search'][label]\n\n\ndef register_search(cls):\n    \"\"\"\n    Decorator for registering a SearchIndex class.\n    \"\"\"\n    model = cls.model\n    label = f'{model._meta.app_label}.{model._meta.model_name}'\n    registry['search'][label] = cls\n\n    return cls\n", "path": "netbox/netbox/search/__init__.py"}], "after_files": [{"content": "from collections import namedtuple\n\nfrom django.db import models\n\nfrom ipam.fields import IPAddressField, IPNetworkField\nfrom netbox.registry import registry\n\nObjectFieldValue = namedtuple('ObjectFieldValue', ('name', 'type', 'weight', 'value'))\n\n\nclass FieldTypes:\n    FLOAT = 'float'\n    INTEGER = 'int'\n    STRING = 'str'\n    INET = 'inet'\n    CIDR = 'cidr'\n\n\nclass LookupTypes:\n    PARTIAL = 'icontains'\n    EXACT = 'iexact'\n    STARTSWITH = 'istartswith'\n    ENDSWITH = 'iendswith'\n    REGEX = 'iregex'\n\n\nclass SearchIndex:\n    \"\"\"\n    Base class for building search indexes.\n\n    Attributes:\n        model: The model class for which this index is used.\n        category: The label of the group under which this indexer is categorized (for form field display). If none,\n            the name of the model's app will be used.\n        fields: An iterable of two-tuples defining the model fields to be indexed and the weight associated with each.\n        display_attrs: An iterable of additional object attributes to include when displaying search results.\n    \"\"\"\n    model = None\n    category = None\n    fields = ()\n    display_attrs = ()\n\n    @staticmethod\n    def get_field_type(instance, field_name):\n        \"\"\"\n        Return the data type of the specified model field.\n        \"\"\"\n        field_cls = instance._meta.get_field(field_name).__class__\n        if issubclass(field_cls, (models.FloatField, models.DecimalField)):\n            return FieldTypes.FLOAT\n        if issubclass(field_cls, IPAddressField):\n            return FieldTypes.INET\n        if issubclass(field_cls, IPNetworkField):\n            return FieldTypes.CIDR\n        if issubclass(field_cls, models.IntegerField):\n            return FieldTypes.INTEGER\n        return FieldTypes.STRING\n\n    @staticmethod\n    def get_field_value(instance, field_name):\n        \"\"\"\n        Return the value of the specified model field as a string (or None).\n        \"\"\"\n        if value := getattr(instance, field_name):\n            return str(value)\n\n    @classmethod\n    def get_category(cls):\n        return cls.category or cls.model._meta.app_config.verbose_name\n\n    @classmethod\n    def to_cache(cls, instance, custom_fields=None):\n        \"\"\"\n        Return a list of ObjectFieldValue representing the instance fields to be cached.\n\n        Args:\n            instance: The instance being cached.\n            custom_fields: An iterable of CustomFields to include when caching the instance. If None, all custom fields\n                defined for the model will be included. (This can also be provided during bulk caching to avoid looking\n                up the available custom fields for each instance.)\n        \"\"\"\n        values = []\n\n        # Capture built-in fields\n        for name, weight in cls.fields:\n            type_ = cls.get_field_type(instance, name)\n            value = cls.get_field_value(instance, name)\n            if type_ and value:\n                values.append(\n                    ObjectFieldValue(name, type_, weight, value)\n                )\n\n        # Capture custom fields\n        if getattr(instance, 'custom_field_data', None):\n            if custom_fields is None:\n                custom_fields = instance.custom_fields\n            for cf in custom_fields:\n                type_ = cf.search_type\n                value = instance.custom_field_data.get(cf.name)\n                weight = cf.search_weight\n                if type_ and value and weight:\n                    values.append(\n                        ObjectFieldValue(f'cf_{cf.name}', type_, weight, value)\n                    )\n\n        return values\n\n\ndef get_indexer(model):\n    \"\"\"\n    Get the SearchIndex class for the given model.\n    \"\"\"\n    label = f'{model._meta.app_label}.{model._meta.model_name}'\n\n    return registry['search'][label]\n\n\ndef register_search(cls):\n    \"\"\"\n    Decorator for registering a SearchIndex class.\n    \"\"\"\n    model = cls.model\n    label = f'{model._meta.app_label}.{model._meta.model_name}'\n    registry['search'][label] = cls\n\n    return cls\n", "path": "netbox/netbox/search/__init__.py"}]}