reindex.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.core.management.base import BaseCommand, CommandError
  3. from django.utils.translation import gettext as _
  4. from netbox.registry import registry
  5. from netbox.search.backends import search_backend
  6. class Command(BaseCommand):
  7. help = 'Reindex objects for search'
  8. def add_arguments(self, parser):
  9. parser.add_argument(
  10. 'args',
  11. metavar='app_label[.ModelName]',
  12. nargs='*',
  13. help='One or more apps or models to reindex',
  14. )
  15. parser.add_argument(
  16. '--lazy',
  17. action='store_true',
  18. help="For each model, reindex objects only if no cache entries already exist"
  19. )
  20. def _get_indexers(self, *model_names):
  21. indexers = {}
  22. # No models specified; pull in all registered indexers
  23. if not model_names:
  24. for idx in registry['search'].values():
  25. indexers[idx.model] = idx
  26. # Return only indexers for the specified models
  27. else:
  28. for label in model_names:
  29. labels = label.lower().split('.')
  30. # Label specifies an exact model
  31. if len(labels) == 2:
  32. app_label, model_name = labels
  33. try:
  34. idx = registry['search'][f'{app_label}.{model_name}']
  35. indexers[idx.model] = idx
  36. except KeyError:
  37. raise CommandError(f"No indexer registered for {label}")
  38. # Label specifies all the models of an app
  39. elif len(labels) == 1:
  40. app_label = labels[0] + '.'
  41. for indexer_label, idx in registry['search'].items():
  42. if indexer_label.startswith(app_label):
  43. indexers[idx.model] = idx
  44. else:
  45. raise CommandError(
  46. f"Invalid model: {label}. Model names must be in the format <app_label> or <app_label>.<model_name>."
  47. )
  48. return indexers
  49. def handle(self, *model_labels, **kwargs):
  50. # Determine which models to reindex
  51. indexers = self._get_indexers(*model_labels)
  52. if not indexers:
  53. raise CommandError(_("No indexers found!"))
  54. self.stdout.write(f'Reindexing {len(indexers)} models.')
  55. # Clear cached values for the specified models (if not being lazy)
  56. if not kwargs['lazy']:
  57. if model_labels:
  58. content_types = [ContentType.objects.get_for_model(model) for model in indexers.keys()]
  59. else:
  60. content_types = None
  61. self.stdout.write('Clearing cached values... ', ending='')
  62. self.stdout.flush()
  63. deleted_count = search_backend.clear(object_types=content_types)
  64. self.stdout.write(f'{deleted_count} entries deleted.')
  65. # Index models
  66. self.stdout.write('Indexing models')
  67. for model, idx in indexers.items():
  68. app_label = model._meta.app_label
  69. model_name = model._meta.model_name
  70. self.stdout.write(f' {app_label}.{model_name}... ', ending='')
  71. self.stdout.flush()
  72. if kwargs['lazy']:
  73. content_type = ContentType.objects.get_for_model(model)
  74. if cached_count := search_backend.count(object_types=[content_type]):
  75. self.stdout.write(f'Skipping (found {cached_count} existing).')
  76. continue
  77. i = search_backend.cache(model.objects.iterator(), remove_existing=False)
  78. if i:
  79. self.stdout.write(f'{i} entries cached.')
  80. else:
  81. self.stdout.write('No objects found.')
  82. msg = 'Completed.'
  83. if total_count := search_backend.size:
  84. msg += f' Total entries: {total_count}'
  85. self.stdout.write(msg, self.style.SUCCESS)