bulk_import.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import re
  2. from django import forms
  3. from django.contrib.postgres.forms import SimpleArrayField
  4. from django.core.exceptions import ObjectDoesNotExist
  5. from django.utils.translation import gettext_lazy as _
  6. from core.models import DataFile, DataSource, ObjectType
  7. from extras.choices import *
  8. from extras.models import *
  9. from netbox.events import get_event_type_choices
  10. from netbox.forms import NetBoxModelImportForm, OwnerCSVMixin, PrimaryModelImportForm
  11. from users.models import Group, User
  12. from utilities.forms import CSVModelForm
  13. from utilities.forms.fields import (
  14. CSVChoiceField,
  15. CSVContentTypeField,
  16. CSVModelChoiceField,
  17. CSVModelMultipleChoiceField,
  18. CSVMultipleChoiceField,
  19. CSVMultipleContentTypeField,
  20. SlugField,
  21. )
  22. __all__ = (
  23. 'ConfigContextProfileImportForm',
  24. 'ConfigTemplateImportForm',
  25. 'CustomFieldChoiceSetImportForm',
  26. 'CustomFieldImportForm',
  27. 'CustomLinkImportForm',
  28. 'EventRuleImportForm',
  29. 'ExportTemplateImportForm',
  30. 'JournalEntryImportForm',
  31. 'NotificationGroupImportForm',
  32. 'SavedFilterImportForm',
  33. 'TagImportForm',
  34. 'WebhookImportForm',
  35. )
  36. class CustomFieldImportForm(OwnerCSVMixin, CSVModelForm):
  37. object_types = CSVMultipleContentTypeField(
  38. label=_('Object types'),
  39. queryset=ObjectType.objects.with_feature('custom_fields'),
  40. help_text=_("One or more assigned object types")
  41. )
  42. type = CSVChoiceField(
  43. label=_('Type'),
  44. choices=CustomFieldTypeChoices,
  45. help_text=_('Field data type (e.g. text, integer, etc.)')
  46. )
  47. related_object_type = CSVContentTypeField(
  48. label=_('Object type'),
  49. queryset=ObjectType.objects.public(),
  50. required=False,
  51. help_text=_("Object type (for object or multi-object fields)")
  52. )
  53. choice_set = CSVModelChoiceField(
  54. label=_('Choice set'),
  55. queryset=CustomFieldChoiceSet.objects.all(),
  56. to_field_name='name',
  57. required=False,
  58. help_text=_('Choice set (for selection fields)')
  59. )
  60. ui_visible = CSVChoiceField(
  61. label=_('UI visible'),
  62. choices=CustomFieldUIVisibleChoices,
  63. required=False,
  64. help_text=_('Whether the custom field is displayed in the UI')
  65. )
  66. ui_editable = CSVChoiceField(
  67. label=_('UI editable'),
  68. choices=CustomFieldUIEditableChoices,
  69. required=False,
  70. help_text=_('Whether the custom field is editable in the UI')
  71. )
  72. class Meta:
  73. model = CustomField
  74. fields = (
  75. 'name', 'label', 'group_name', 'type', 'object_types', 'related_object_type', 'required', 'unique',
  76. 'description', 'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
  77. 'validation_maximum', 'validation_regex', 'validation_schema', 'ui_visible', 'ui_editable',
  78. 'is_cloneable', 'owner', 'comments',
  79. )
  80. class CustomFieldChoiceSetImportForm(OwnerCSVMixin, CSVModelForm):
  81. base_choices = CSVChoiceField(
  82. choices=CustomFieldChoiceSetBaseChoices,
  83. required=False,
  84. help_text=_('The base set of predefined choices to use (if any)')
  85. )
  86. extra_choices = SimpleArrayField(
  87. base_field=forms.CharField(),
  88. required=False,
  89. help_text=_(
  90. 'Quoted string of comma-separated field choices with optional labels separated by colon: '
  91. '"choice1:First Choice,choice2:Second Choice"'
  92. )
  93. )
  94. choice_colors = SimpleArrayField(
  95. base_field=forms.CharField(),
  96. required=False,
  97. help_text=_(
  98. 'Quoted string of comma-separated color mappings in the format '
  99. '"choice1:red,choice2:green". Supported colors: {colors}'
  100. ).format(colors=', '.join(CustomFieldChoiceColorChoices.values())),
  101. )
  102. class Meta:
  103. model = CustomFieldChoiceSet
  104. fields = (
  105. 'name', 'description', 'base_choices', 'extra_choices', 'choice_colors', 'order_alphabetically', 'owner',
  106. )
  107. def clean_extra_choices(self):
  108. if isinstance(self.cleaned_data['extra_choices'], list):
  109. data = []
  110. for line in self.cleaned_data['extra_choices']:
  111. try:
  112. value, label = re.split(r'(?<!\\):', line, maxsplit=1)
  113. value = value.replace('\\:', ':')
  114. label = label.replace('\\:', ':')
  115. except ValueError:
  116. value, label = line, line
  117. data.append((value, label))
  118. return data
  119. return None
  120. def clean_choice_colors(self):
  121. if isinstance(self.cleaned_data['choice_colors'], list):
  122. data = {}
  123. for line in self.cleaned_data['choice_colors']:
  124. try:
  125. value, color = re.split(r'(?<!\\):', line, maxsplit=1)
  126. value = value.replace('\\:', ':')
  127. except ValueError as e:
  128. raise forms.ValidationError(
  129. _("Invalid color mapping '{line}'. Use the format value:color.").format(line=line)
  130. ) from e
  131. value = value.strip()
  132. color = color.strip()
  133. if value in data:
  134. raise forms.ValidationError(
  135. _("Duplicate color mapping defined for choice '{value}'.").format(value=value)
  136. )
  137. data[value] = color
  138. return data
  139. return {}
  140. class CustomLinkImportForm(OwnerCSVMixin, CSVModelForm):
  141. object_types = CSVMultipleContentTypeField(
  142. label=_('Object types'),
  143. queryset=ObjectType.objects.with_feature('custom_links'),
  144. help_text=_("One or more assigned object types")
  145. )
  146. button_class = CSVChoiceField(
  147. label=_('button class'),
  148. required=False,
  149. choices=CustomLinkButtonClassChoices,
  150. help_text=_('The class of the first link in a group will be used for the dropdown button')
  151. )
  152. class Meta:
  153. model = CustomLink
  154. fields = (
  155. 'name', 'object_types', 'enabled', 'weight', 'group_name', 'button_class', 'new_window', 'link_text',
  156. 'link_url', 'owner',
  157. )
  158. class ExportTemplateImportForm(OwnerCSVMixin, CSVModelForm):
  159. object_types = CSVMultipleContentTypeField(
  160. label=_('Object types'),
  161. queryset=ObjectType.objects.with_feature('export_templates'),
  162. help_text=_("One or more assigned object types")
  163. )
  164. class Meta:
  165. model = ExportTemplate
  166. fields = (
  167. 'name', 'object_types', 'description', 'environment_params', 'mime_type', 'file_name', 'file_extension',
  168. 'as_attachment', 'template_code', 'owner',
  169. )
  170. class ConfigContextProfileImportForm(PrimaryModelImportForm):
  171. class Meta:
  172. model = ConfigContextProfile
  173. fields = [
  174. 'name', 'description', 'schema', 'owner', 'comments', 'tags',
  175. ]
  176. class ConfigTemplateImportForm(OwnerCSVMixin, CSVModelForm):
  177. data_source = CSVModelChoiceField(
  178. label=_('Data source'),
  179. queryset=DataSource.objects.all(),
  180. required=False,
  181. to_field_name='name',
  182. help_text=_('Data source which provides the data file')
  183. )
  184. data_file = CSVModelChoiceField(
  185. label=_('Data file'),
  186. queryset=DataFile.objects.all(),
  187. required=False,
  188. to_field_name='path',
  189. help_text=_('Data file containing the template code')
  190. )
  191. auto_sync_enabled = forms.BooleanField(
  192. required=False,
  193. label=_('Auto sync enabled'),
  194. help_text=_("Enable automatic synchronization of template content when the data file is updated")
  195. )
  196. class Meta:
  197. model = ConfigTemplate
  198. fields = (
  199. 'name', 'description', 'template_code', 'data_source', 'data_file', 'auto_sync_enabled',
  200. 'environment_params', 'mime_type', 'file_name', 'file_extension', 'as_attachment', 'debug', 'owner',
  201. 'tags',
  202. )
  203. def clean(self):
  204. super().clean()
  205. # Make sure template_code is None when it's not included in the uploaded data
  206. if not self.data.get('template_code') and not self.data.get('data_file'):
  207. raise forms.ValidationError(_("Must specify either local content or a data file"))
  208. return self.cleaned_data['template_code']
  209. class SavedFilterImportForm(OwnerCSVMixin, CSVModelForm):
  210. object_types = CSVMultipleContentTypeField(
  211. label=_('Object types'),
  212. queryset=ObjectType.objects.all(),
  213. help_text=_("One or more assigned object types")
  214. )
  215. class Meta:
  216. model = SavedFilter
  217. fields = (
  218. 'name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared', 'parameters', 'owner',
  219. )
  220. class WebhookImportForm(OwnerCSVMixin, NetBoxModelImportForm):
  221. class Meta:
  222. model = Webhook
  223. fields = (
  224. 'name', 'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template',
  225. 'secret', 'ssl_verification', 'ca_file_path', 'description', 'owner', 'tags'
  226. )
  227. class EventRuleImportForm(OwnerCSVMixin, NetBoxModelImportForm):
  228. object_types = CSVMultipleContentTypeField(
  229. label=_('Object types'),
  230. queryset=ObjectType.objects.with_feature('event_rules'),
  231. help_text=_("One or more assigned object types")
  232. )
  233. event_types = CSVMultipleChoiceField(
  234. choices=get_event_type_choices(),
  235. label=_('Event types'),
  236. help_text=_('The event type(s) which will trigger this rule')
  237. )
  238. action_object = forms.CharField(
  239. label=_('Action object'),
  240. required=True,
  241. help_text=_('Webhook name or script as dotted path module.Class')
  242. )
  243. class Meta:
  244. model = EventRule
  245. fields = (
  246. 'name', 'description', 'enabled', 'conditions', 'object_types', 'event_types', 'action_type',
  247. 'owner', 'comments', 'tags'
  248. )
  249. def clean(self):
  250. super().clean()
  251. action_object = self.cleaned_data.get('action_object')
  252. action_type = self.cleaned_data.get('action_type')
  253. if action_object and action_type:
  254. # Webhook
  255. if action_type == EventRuleActionChoices.WEBHOOK:
  256. try:
  257. webhook = Webhook.objects.get(name=action_object)
  258. except Webhook.DoesNotExist:
  259. raise forms.ValidationError(_("Webhook {name} not found").format(name=action_object))
  260. self.instance.action_object = webhook
  261. # Script
  262. elif action_type == EventRuleActionChoices.SCRIPT:
  263. from extras.scripts import get_module_and_script
  264. module_name, script_name = action_object.split('.', 1)
  265. try:
  266. script = get_module_and_script(module_name, script_name)[1]
  267. except ObjectDoesNotExist:
  268. raise forms.ValidationError(_("Script {name} not found").format(name=action_object))
  269. self.instance.action_object = script
  270. self.instance.action_object_type = ObjectType.objects.get_for_model(script, for_concrete_model=False)
  271. class TagImportForm(OwnerCSVMixin, CSVModelForm):
  272. slug = SlugField()
  273. object_types = CSVMultipleContentTypeField(
  274. label=_('Object types'),
  275. queryset=ObjectType.objects.with_feature('tags'),
  276. help_text=_("One or more assigned object types"),
  277. required=False,
  278. )
  279. class Meta:
  280. model = Tag
  281. fields = (
  282. 'name', 'slug', 'color', 'weight', 'description', 'object_types', 'owner',
  283. )
  284. class JournalEntryImportForm(NetBoxModelImportForm):
  285. assigned_object_type = CSVContentTypeField(
  286. queryset=ObjectType.objects.all(),
  287. label=_('Assigned object type'),
  288. )
  289. kind = CSVChoiceField(
  290. label=_('Kind'),
  291. choices=JournalEntryKindChoices,
  292. help_text=_('The classification of entry')
  293. )
  294. comments = forms.CharField(
  295. label=_('Comments'),
  296. required=True
  297. )
  298. class Meta:
  299. model = JournalEntry
  300. fields = (
  301. 'assigned_object_type', 'assigned_object_id', 'created_by', 'kind', 'comments', 'tags'
  302. )
  303. def __init__(self, *args, **kwargs):
  304. super().__init__(*args, **kwargs)
  305. # If not creating a new JournalEntry, disable the created_by field
  306. if self.instance and not self.instance._state.adding:
  307. self.fields['created_by'].disabled = True
  308. class NotificationGroupImportForm(CSVModelForm):
  309. users = CSVModelMultipleChoiceField(
  310. label=_('Users'),
  311. queryset=User.objects.all(),
  312. required=False,
  313. to_field_name='username',
  314. help_text=_('User names separated by commas, encased with double quotes')
  315. )
  316. groups = CSVModelMultipleChoiceField(
  317. label=_('Groups'),
  318. queryset=Group.objects.all(),
  319. required=False,
  320. to_field_name='name',
  321. help_text=_('Group names separated by commas, encased with double quotes')
  322. )
  323. class Meta:
  324. model = NotificationGroup
  325. fields = ('name', 'description', 'users', 'groups')