scripts.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. from collections import OrderedDict
  2. import inspect
  3. import pkgutil
  4. from django import forms
  5. from django.conf import settings
  6. from django.core.validators import RegexValidator
  7. from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING
  8. from .forms import ScriptForm
  9. __all__ = [
  10. 'Script',
  11. 'StringVar',
  12. 'IntegerVar',
  13. 'BooleanVar',
  14. 'ObjectVar',
  15. ]
  16. #
  17. # Script variables
  18. #
  19. class ScriptVariable:
  20. """
  21. Base model for script variables
  22. """
  23. form_field = forms.CharField
  24. def __init__(self, label='', description='', default=None, required=True):
  25. # Default field attributes
  26. self.field_attrs = {
  27. 'help_text': description,
  28. 'required': required
  29. }
  30. if label:
  31. self.field_attrs['label'] = label
  32. if default:
  33. self.field_attrs['initial'] = default
  34. def as_field(self):
  35. """
  36. Render the variable as a Django form field.
  37. """
  38. return self.form_field(**self.field_attrs)
  39. class StringVar(ScriptVariable):
  40. """
  41. Character string representation. Can enforce minimum/maximum length and/or regex validation.
  42. """
  43. def __init__(self, min_length=None, max_length=None, regex=None, *args, **kwargs):
  44. super().__init__(*args, **kwargs)
  45. # Optional minimum/maximum lengths
  46. if min_length:
  47. self.field_attrs['min_length'] = min_length
  48. if max_length:
  49. self.field_attrs['max_length'] = max_length
  50. # Optional regular expression validation
  51. if regex:
  52. self.field_attrs['validators'] = [
  53. RegexValidator(
  54. regex=regex,
  55. message='Invalid value. Must match regex: {}'.format(regex),
  56. code='invalid'
  57. )
  58. ]
  59. class IntegerVar(ScriptVariable):
  60. """
  61. Integer representation. Can enforce minimum/maximum values.
  62. """
  63. form_field = forms.IntegerField
  64. def __init__(self, min_value=None, max_value=None, *args, **kwargs):
  65. super().__init__(*args, **kwargs)
  66. # Optional minimum/maximum values
  67. if min_value:
  68. self.field_attrs['min_value'] = min_value
  69. if max_value:
  70. self.field_attrs['max_value'] = max_value
  71. class BooleanVar(ScriptVariable):
  72. """
  73. Boolean representation (true/false). Renders as a checkbox.
  74. """
  75. form_field = forms.BooleanField
  76. def __init__(self, *args, **kwargs):
  77. super().__init__(*args, **kwargs)
  78. # Boolean fields cannot be required
  79. self.field_attrs['required'] = False
  80. class ObjectVar(ScriptVariable):
  81. """
  82. NetBox object representation. The provided QuerySet will determine the choices available.
  83. """
  84. form_field = forms.ModelChoiceField
  85. def __init__(self, queryset, *args, **kwargs):
  86. super().__init__(*args, **kwargs)
  87. # Queryset for field choices
  88. self.field_attrs['queryset'] = queryset
  89. class Script:
  90. """
  91. Custom scripts inherit this object.
  92. """
  93. class Meta:
  94. pass
  95. def __init__(self):
  96. # Initiate the log
  97. self.log = []
  98. # Grab some info about the script
  99. self.filename = inspect.getfile(self.__class__)
  100. self.source = inspect.getsource(self.__class__)
  101. def __str__(self):
  102. return getattr(self.Meta, 'name', self.__class__.__name__)
  103. def _get_vars(self):
  104. vars = OrderedDict()
  105. # Infer order from Meta.fields (Python 3.5 and lower)
  106. fields = getattr(self.Meta, 'fields')
  107. for name in fields:
  108. vars[name] = getattr(self, name)
  109. # Default to order of declaration on class
  110. for name, attr in self.__class__.__dict__.items():
  111. if name not in vars and issubclass(attr.__class__, ScriptVariable):
  112. vars[name] = attr
  113. return vars
  114. def run(self, data):
  115. raise NotImplementedError("The script must define a run() method.")
  116. def as_form(self, data=None):
  117. """
  118. Return a Django form suitable for populating the context data required to run this Script.
  119. """
  120. vars = self._get_vars()
  121. form = ScriptForm(vars, data)
  122. return form
  123. # Logging
  124. def log_debug(self, message):
  125. self.log.append((LOG_DEFAULT, message))
  126. def log_success(self, message):
  127. self.log.append((LOG_SUCCESS, message))
  128. def log_info(self, message):
  129. self.log.append((LOG_INFO, message))
  130. def log_warning(self, message):
  131. self.log.append((LOG_WARNING, message))
  132. def log_failure(self, message):
  133. self.log.append((LOG_FAILURE, message))
  134. #
  135. # Functions
  136. #
  137. def is_script(obj):
  138. """
  139. Returns True if the object is a Script.
  140. """
  141. return obj in Script.__subclasses__()
  142. def is_variable(obj):
  143. """
  144. Returns True if the object is a ScriptVariable.
  145. """
  146. return isinstance(obj, ScriptVariable)
  147. def get_scripts():
  148. scripts = OrderedDict()
  149. # Iterate through all modules within the reports path. These are the user-created files in which reports are
  150. # defined.
  151. for importer, module_name, _ in pkgutil.iter_modules([settings.SCRIPTS_ROOT]):
  152. module = importer.find_module(module_name).load_module(module_name)
  153. module_scripts = OrderedDict()
  154. for name, cls in inspect.getmembers(module, is_script):
  155. module_scripts[name] = cls
  156. scripts[module_name] = module_scripts
  157. return scripts