41
41
get_model_features ,
42
42
)
43
43
from netbox .registry import registry
44
+ from netbox .search import SearchIndex
44
45
from utilities import filters
45
46
from utilities .datetime import datetime_from_timestamp
46
47
from utilities .object_types import object_type_name
@@ -291,6 +292,7 @@ def get_or_create_content_type(self):
291
292
def _fetch_and_generate_field_attrs (
292
293
self ,
293
294
fields ,
295
+ skip_object_fields = False ,
294
296
):
295
297
field_attrs = {
296
298
"_primary_field_id" : - 1 ,
@@ -307,6 +309,10 @@ def _fetch_and_generate_field_attrs(
307
309
308
310
for field in fields :
309
311
field_type = FIELD_TYPE_CLASS [field .type ]()
312
+ if skip_object_fields :
313
+ if field .type in [CustomFieldTypeChoices .TYPE_OBJECT , CustomFieldTypeChoices .TYPE_MULTIOBJECT ]:
314
+ continue
315
+
310
316
field_name = field .name
311
317
312
318
field_attrs ["_field_objects" ][field .id ] = {
@@ -356,11 +362,32 @@ def get_content_type_label(custom_object_type_id):
356
362
custom_object_type = CustomObjectType .objects .get (pk = custom_object_type_id )
357
363
return f"Custom Objects > { custom_object_type .name } "
358
364
365
+ def register_custom_object_search_index (self , model ):
366
+ # model must be an instance of this CustomObjectType's get_model() generated class
367
+ fields = []
368
+ for field in self .fields .filter (search_weight__gt = 0 ):
369
+ fields .append ((field .name , field .search_weight ))
370
+
371
+ attrs = {
372
+ "model" : model ,
373
+ "fields" : tuple (fields ),
374
+ "display_attrs" : tuple (),
375
+ }
376
+ search_index = type (
377
+ f"{ self .name } SearchIndex" ,
378
+ (SearchIndex ,),
379
+ attrs ,
380
+ )
381
+ label = f"{ APP_LABEL } .{ self .get_table_model_name (self .id ).lower ()} "
382
+ registry ["search" ][label ] = search_index
383
+
359
384
def get_model (
360
385
self ,
361
386
fields = None ,
362
387
manytomany_models = None ,
363
388
app_label = None ,
389
+ skip_object_fields = False ,
390
+ no_cache = False ,
364
391
):
365
392
"""
366
393
Generates a temporary Django model based on available fields that belong to
@@ -376,12 +403,16 @@ def get_model(
376
403
have the same app_label. If passed along in this parameter, then the
377
404
generated model will use that one instead of generating a unique one.
378
405
:type app_label: Optional[String]
406
+ :param skip_object_fields: Don't add object or multiobject fields to the model
407
+ :type skip_object_fields: bool
408
+ :param no_cache: Don't cache the generated model or attempt to pull from cache
409
+ :type no_cache: bool
379
410
:return: The generated model.
380
411
:rtype: Model
381
412
"""
382
413
383
414
# Check if we have a cached model for this CustomObjectType
384
- if self .is_model_cached (self .id ):
415
+ if self .is_model_cached (self .id ) and not no_cache :
385
416
model = self .get_cached_model (self .id )
386
417
# Ensure the serializer is registered even for cached models
387
418
from netbox_custom_objects .api .serializers import get_serializer_class
@@ -422,7 +453,7 @@ def get_model(
422
453
"custom_object_type_id" : self .id ,
423
454
}
424
455
425
- field_attrs = self ._fetch_and_generate_field_attrs (fields )
456
+ field_attrs = self ._fetch_and_generate_field_attrs (fields , skip_object_fields = skip_object_fields )
426
457
427
458
attrs .update (** field_attrs )
428
459
@@ -471,14 +502,18 @@ def wrapped_post_through_setup(self, cls):
471
502
self ._after_model_generation (attrs , model )
472
503
473
504
# Cache the generated model
474
- self ._model_cache [self .id ] = model
505
+ if not no_cache :
506
+ self ._model_cache [self .id ] = model
475
507
476
508
# Register the serializer for this model
477
509
if not manytomany_models :
478
510
from netbox_custom_objects .api .serializers import get_serializer_class
479
511
480
512
get_serializer_class (model )
481
513
514
+ # Register the global SearchIndex for this model
515
+ self .register_custom_object_search_index (model )
516
+
482
517
return model
483
518
484
519
def create_model (self ):
@@ -487,7 +522,6 @@ def create_model(self):
487
522
488
523
# Ensure the ContentType exists and is immediately available
489
524
ct = self .get_or_create_content_type ()
490
- model = self .get_model ()
491
525
features = get_model_features (model )
492
526
ct .public = True
493
527
ct .features = features
@@ -496,6 +530,8 @@ def create_model(self):
496
530
with connection .schema_editor () as schema_editor :
497
531
schema_editor .create_model (model )
498
532
533
+ self .register_custom_object_search_index (model )
534
+
499
535
def save (self , * args , ** kwargs ):
500
536
needs_db_create = self ._state .adding
501
537
super ().save (* args , ** kwargs )
@@ -532,9 +568,10 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
532
568
max_length = 50 ,
533
569
choices = CustomFieldTypeChoices ,
534
570
default = CustomFieldTypeChoices .TYPE_TEXT ,
535
- help_text = _ ("The type of data this custom field holds" ),
571
+ help_text = _ ("The type of data this custom object field holds" ),
536
572
)
537
573
primary = models .BooleanField (
574
+ verbose_name = _ ("primary name field" ),
538
575
default = False ,
539
576
help_text = _ (
540
577
"Indicates that this field's value will be used as the object's displayed name"
@@ -550,7 +587,7 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
550
587
name = models .CharField (
551
588
verbose_name = _ ("name" ),
552
589
max_length = 50 ,
553
- help_text = _ ("Internal field name" ),
590
+ help_text = _ ("Internal field name, e.g. \" vendor_label \" " ),
554
591
validators = (
555
592
RegexValidator (
556
593
regex = r"^[a-z0-9_]+$" ,
@@ -560,7 +597,7 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
560
597
RegexValidator (
561
598
regex = r"__" ,
562
599
message = _ (
563
- "Double underscores are not permitted in custom field names."
600
+ "Double underscores are not permitted in custom object field names."
564
601
),
565
602
flags = re .IGNORECASE ,
566
603
inverse_match = True ,
@@ -579,7 +616,7 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
579
616
verbose_name = _ ("group name" ),
580
617
max_length = 50 ,
581
618
blank = True ,
582
- help_text = _ ("Custom fields within the same group will be displayed together" ),
619
+ help_text = _ ("Custom object fields within the same group will be displayed together" ),
583
620
)
584
621
description = models .CharField (
585
622
verbose_name = _ ("description" ), max_length = 200 , blank = True
@@ -598,9 +635,9 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
598
635
)
599
636
search_weight = models .PositiveSmallIntegerField (
600
637
verbose_name = _ ("search weight" ),
601
- default = 1000 ,
638
+ default = 500 ,
602
639
help_text = _ (
603
- "Weighting for search. Lower values are considered more important. Fields with a search weight of zero "
640
+ "Weighting for search. Lower values are considered more important. Fields with a search weight of 0 "
604
641
"will be ignored."
605
642
),
606
643
)
@@ -1274,6 +1311,9 @@ def save(self, *args, **kwargs):
1274
1311
1275
1312
super ().save (* args , ** kwargs )
1276
1313
1314
+ # Reregister SearchIndex with new set of searchable fields
1315
+ self .custom_object_type .register_custom_object_search_index (model )
1316
+
1277
1317
def delete (self , * args , ** kwargs ):
1278
1318
field_type = FIELD_TYPE_CLASS [self .type ]()
1279
1319
model_field = field_type .get_model_field (self )
@@ -1292,6 +1332,9 @@ def delete(self, *args, **kwargs):
1292
1332
1293
1333
super ().delete (* args , ** kwargs )
1294
1334
1335
+ # Reregister SearchIndex with new set of searchable fields
1336
+ self .custom_object_type .register_custom_object_search_index (model )
1337
+
1295
1338
1296
1339
class CustomObjectObjectTypeManager (ObjectTypeManager ):
1297
1340
0 commit comments