2023-03-28 06:53:57 -07:00
|
|
|
import json
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
from django import forms
|
2023-06-22 11:04:24 -07:00
|
|
|
from django.conf import settings
|
2023-04-10 22:10:13 +05:30
|
|
|
from django.db.models import Q
|
2021-09-28 10:44:53 -04:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2022-11-03 11:58:26 -07:00
|
|
|
from django.utils.translation import gettext as _
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2023-04-18 15:18:19 -04:00
|
|
|
from core.forms.mixins import SyncedDataMixin
|
2022-06-22 16:10:48 -04:00
|
|
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
2021-09-28 10:44:53 -04:00
|
|
|
from extras.choices import *
|
|
|
|
from extras.models import *
|
|
|
|
from extras.utils import FeatureQuery
|
2023-06-22 11:04:24 -07:00
|
|
|
from netbox.config import get_config, PARAMS
|
2022-03-31 11:40:02 -04:00
|
|
|
from netbox.forms import NetBoxModelForm
|
2021-09-28 10:44:53 -04:00
|
|
|
from tenancy.models import Tenant, TenantGroup
|
2023-06-29 14:36:11 -04:00
|
|
|
from utilities.forms import BootstrapMixin, add_blank_choice
|
2023-04-14 10:33:53 -04:00
|
|
|
from utilities.forms.fields import (
|
2023-07-19 10:26:24 -04:00
|
|
|
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
|
|
|
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
2023-07-19 10:26:24 -04:00
|
|
|
from utilities.forms.widgets import ArrayWidget
|
2021-12-23 14:20:03 -05:00
|
|
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2023-06-22 11:04:24 -07:00
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
__all__ = (
|
2023-06-29 14:36:11 -04:00
|
|
|
'BookmarkForm',
|
2021-09-28 10:44:53 -04:00
|
|
|
'ConfigContextForm',
|
2023-06-22 11:04:24 -07:00
|
|
|
'ConfigRevisionForm',
|
2023-02-17 08:33:08 -05:00
|
|
|
'ConfigTemplateForm',
|
2023-07-19 10:26:24 -04:00
|
|
|
'CustomFieldChoiceSetForm',
|
2021-09-28 10:44:53 -04:00
|
|
|
'CustomFieldForm',
|
|
|
|
'CustomLinkForm',
|
|
|
|
'ExportTemplateForm',
|
|
|
|
'ImageAttachmentForm',
|
|
|
|
'JournalEntryForm',
|
2022-11-02 12:27:53 -04:00
|
|
|
'SavedFilterForm',
|
2021-09-28 10:44:53 -04:00
|
|
|
'TagForm',
|
|
|
|
'WebhookForm',
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
content_types = ContentTypeMultipleChoiceField(
|
|
|
|
queryset=ContentType.objects.all(),
|
2022-02-11 11:33:16 -05:00
|
|
|
limit_choices_to=FeatureQuery('custom_fields'),
|
|
|
|
)
|
|
|
|
object_type = ContentTypeChoiceField(
|
|
|
|
queryset=ContentType.objects.all(),
|
2022-03-22 13:12:31 -04:00
|
|
|
# TODO: Come up with a canonical way to register suitable models
|
2023-04-10 22:10:13 +05:30
|
|
|
limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
|
2022-02-11 11:33:16 -05:00
|
|
|
required=False,
|
2022-11-03 11:58:26 -07:00
|
|
|
help_text=_("Type of the related object (for object/multi-object fields only)")
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
2023-07-19 10:26:24 -04:00
|
|
|
choice_set = DynamicModelChoiceField(
|
|
|
|
queryset=CustomFieldChoiceSet.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2022-01-31 15:52:36 -05:00
|
|
|
fieldsets = (
|
2022-04-15 14:45:28 -04:00
|
|
|
('Custom Field', (
|
2022-10-21 13:16:16 -04:00
|
|
|
'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
|
2022-04-15 14:45:28 -04:00
|
|
|
)),
|
2023-03-14 15:18:03 -04:00
|
|
|
('Behavior', ('search_weight', 'filter_logic', 'ui_visibility', 'weight', 'is_cloneable')),
|
2023-07-19 10:26:24 -04:00
|
|
|
('Values', ('default', 'choice_set')),
|
2022-01-31 15:52:36 -05:00
|
|
|
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
|
|
|
)
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class Meta:
|
|
|
|
model = CustomField
|
|
|
|
fields = '__all__'
|
2022-03-28 13:34:37 -04:00
|
|
|
help_texts = {
|
2023-03-01 17:31:54 -05:00
|
|
|
'type': _(
|
|
|
|
"The type of data stored in this field. For object/multi-object fields, select the related object "
|
|
|
|
"type below."
|
|
|
|
)
|
2022-03-28 13:34:37 -04:00
|
|
|
}
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2023-04-10 16:13:08 +02:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Disable changing the type of a CustomField as it almost universally causes errors if custom field data is already present.
|
|
|
|
if self.instance.pk:
|
|
|
|
self.fields['type'].disabled = True
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2023-07-19 10:26:24 -04:00
|
|
|
class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
extra_choices = forms.CharField(
|
|
|
|
widget=ArrayWidget(),
|
|
|
|
help_text=_('Enter one choice per line.')
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = CustomFieldChoiceSet
|
|
|
|
fields = ('name', 'description', 'extra_choices', 'order_alphabetically')
|
|
|
|
|
|
|
|
def clean_extra_choices(self):
|
|
|
|
return self.cleaned_data['extra_choices'].splitlines()
|
|
|
|
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
2022-10-26 11:33:11 -04:00
|
|
|
content_types = ContentTypeMultipleChoiceField(
|
2021-09-28 10:44:53 -04:00
|
|
|
queryset=ContentType.objects.all(),
|
|
|
|
limit_choices_to=FeatureQuery('custom_links')
|
|
|
|
)
|
|
|
|
|
2022-01-31 15:52:36 -05:00
|
|
|
fieldsets = (
|
2022-10-26 11:33:11 -04:00
|
|
|
('Custom Link', ('name', 'content_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
|
2022-01-31 15:52:36 -05:00
|
|
|
('Templates', ('link_text', 'link_url')),
|
|
|
|
)
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class Meta:
|
|
|
|
model = CustomLink
|
|
|
|
fields = '__all__'
|
|
|
|
widgets = {
|
|
|
|
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
}
|
|
|
|
help_texts = {
|
2023-03-01 17:31:54 -05:00
|
|
|
'link_text': _(
|
|
|
|
"Jinja2 template code for the link text. Reference the object as <code>{{ object }}</code>. Links "
|
|
|
|
"which render as empty text will not be displayed."
|
|
|
|
),
|
|
|
|
'link_url': _("Jinja2 template code for the link URL. Reference the object as <code>{{ object }}</code>."),
|
2021-09-28 10:44:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-21 14:15:37 -04:00
|
|
|
class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
|
2022-10-26 13:30:45 -04:00
|
|
|
content_types = ContentTypeMultipleChoiceField(
|
2021-09-28 10:44:53 -04:00
|
|
|
queryset=ContentType.objects.all(),
|
2021-11-11 16:16:54 -05:00
|
|
|
limit_choices_to=FeatureQuery('export_templates')
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
2023-02-08 18:24:18 -05:00
|
|
|
template_code = forms.CharField(
|
|
|
|
required=False,
|
|
|
|
widget=forms.Textarea(attrs={'class': 'font-monospace'})
|
|
|
|
)
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2022-01-31 15:52:36 -05:00
|
|
|
fieldsets = (
|
2023-03-21 14:15:37 -04:00
|
|
|
('Export Template', ('name', 'content_types', 'description', 'template_code')),
|
2023-04-17 10:35:17 -04:00
|
|
|
('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
|
2022-01-31 15:52:36 -05:00
|
|
|
('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
|
|
|
|
)
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class Meta:
|
|
|
|
model = ExportTemplate
|
|
|
|
fields = '__all__'
|
2023-02-08 18:24:18 -05:00
|
|
|
|
2023-03-21 14:15:37 -04:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Disable data field when a DataFile has been set
|
|
|
|
if self.instance.data_file:
|
|
|
|
self.fields['template_code'].widget.attrs['readonly'] = True
|
|
|
|
self.fields['template_code'].help_text = _(
|
|
|
|
'Template content is populated from the remote source selected below.'
|
|
|
|
)
|
|
|
|
|
2023-02-08 18:24:18 -05:00
|
|
|
def clean(self):
|
|
|
|
super().clean()
|
|
|
|
|
|
|
|
if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
|
|
|
|
raise forms.ValidationError("Must specify either local content or a data file")
|
|
|
|
|
|
|
|
return self.cleaned_data
|
2021-09-28 10:44:53 -04:00
|
|
|
|
|
|
|
|
2022-11-02 12:27:53 -04:00
|
|
|
class SavedFilterForm(BootstrapMixin, forms.ModelForm):
|
2022-11-15 10:44:12 -05:00
|
|
|
slug = SlugField()
|
2022-11-02 12:27:53 -04:00
|
|
|
content_types = ContentTypeMultipleChoiceField(
|
|
|
|
queryset=ContentType.objects.all()
|
|
|
|
)
|
2022-12-12 12:56:38 -05:00
|
|
|
parameters = JSONField()
|
2022-11-02 12:27:53 -04:00
|
|
|
|
|
|
|
fieldsets = (
|
2022-11-15 10:44:12 -05:00
|
|
|
('Saved Filter', ('name', 'slug', 'content_types', 'description', 'weight', 'enabled', 'shared')),
|
2022-11-02 12:27:53 -04:00
|
|
|
('Parameters', ('parameters',)),
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = SavedFilter
|
|
|
|
exclude = ('user',)
|
|
|
|
|
|
|
|
def __init__(self, *args, initial=None, **kwargs):
|
|
|
|
|
2023-03-28 06:53:57 -07:00
|
|
|
# Convert any parameters delivered via initial data to JSON data
|
2022-11-02 12:27:53 -04:00
|
|
|
if initial and 'parameters' in initial:
|
|
|
|
if type(initial['parameters']) is str:
|
2023-03-28 06:53:57 -07:00
|
|
|
initial['parameters'] = json.loads(initial['parameters'])
|
2022-11-02 12:27:53 -04:00
|
|
|
|
|
|
|
super().__init__(*args, initial=initial, **kwargs)
|
|
|
|
|
|
|
|
|
2023-06-29 14:36:11 -04:00
|
|
|
class BookmarkForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
object_type = ContentTypeChoiceField(
|
|
|
|
queryset=ContentType.objects.all(),
|
|
|
|
limit_choices_to=FeatureQuery('bookmarks').get_query()
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Bookmark
|
|
|
|
fields = ('object_type', 'object_id')
|
|
|
|
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class WebhookForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
content_types = ContentTypeMultipleChoiceField(
|
|
|
|
queryset=ContentType.objects.all(),
|
|
|
|
limit_choices_to=FeatureQuery('webhooks')
|
|
|
|
)
|
|
|
|
|
2022-01-31 15:52:36 -05:00
|
|
|
fieldsets = (
|
|
|
|
('Webhook', ('name', 'content_types', 'enabled')),
|
2023-02-28 14:29:00 -05:00
|
|
|
('Events', ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
|
2022-01-31 15:52:36 -05:00
|
|
|
('HTTP Request', (
|
|
|
|
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
|
|
|
)),
|
|
|
|
('Conditions', ('conditions',)),
|
|
|
|
('SSL', ('ssl_verification', 'ca_file_path')),
|
|
|
|
)
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class Meta:
|
|
|
|
model = Webhook
|
|
|
|
fields = '__all__'
|
2022-01-04 15:01:16 -05:00
|
|
|
labels = {
|
|
|
|
'type_create': 'Creations',
|
|
|
|
'type_update': 'Updates',
|
|
|
|
'type_delete': 'Deletions',
|
2023-02-28 14:29:00 -05:00
|
|
|
'type_job_start': 'Job executions',
|
|
|
|
'type_job_end': 'Job terminations',
|
2022-01-04 15:01:16 -05:00
|
|
|
}
|
2021-09-28 10:44:53 -04:00
|
|
|
widgets = {
|
|
|
|
'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'body_template': forms.Textarea(attrs={'class': 'font-monospace'}),
|
2022-07-27 15:40:25 -04:00
|
|
|
'conditions': forms.Textarea(attrs={'class': 'font-monospace'}),
|
2021-09-28 10:44:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class TagForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
slug = SlugField()
|
2023-06-23 14:08:14 -04:00
|
|
|
object_types = ContentTypeMultipleChoiceField(
|
|
|
|
queryset=ContentType.objects.all(),
|
|
|
|
limit_choices_to=FeatureQuery('tags'),
|
|
|
|
required=False
|
|
|
|
)
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2022-01-31 15:52:36 -05:00
|
|
|
fieldsets = (
|
2023-06-23 14:08:14 -04:00
|
|
|
('Tag', ('name', 'slug', 'color', 'description', 'object_types')),
|
2022-01-31 15:52:36 -05:00
|
|
|
)
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class Meta:
|
|
|
|
model = Tag
|
|
|
|
fields = [
|
2023-06-23 14:08:14 -04:00
|
|
|
'name', 'slug', 'color', 'description', 'object_types',
|
2021-09-28 10:44:53 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2023-02-07 16:44:05 -05:00
|
|
|
class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
|
2021-09-28 10:44:53 -04:00
|
|
|
regions = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Region.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
site_groups = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=SiteGroup.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
sites = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Site.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2022-06-22 16:10:48 -04:00
|
|
|
locations = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Location.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2021-09-28 10:44:53 -04:00
|
|
|
device_types = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=DeviceType.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
roles = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=DeviceRole.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
platforms = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Platform.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2021-12-23 14:20:03 -05:00
|
|
|
cluster_types = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=ClusterType.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2021-09-28 10:44:53 -04:00
|
|
|
cluster_groups = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=ClusterGroup.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
clusters = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Cluster.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
tenant_groups = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=TenantGroup.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
tenants = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Tenant.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
tags = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Tag.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
2023-02-07 16:44:05 -05:00
|
|
|
data = JSONField(
|
|
|
|
required=False
|
|
|
|
)
|
2022-06-22 16:10:48 -04:00
|
|
|
|
|
|
|
fieldsets = (
|
|
|
|
('Config Context', ('name', 'weight', 'description', 'data', 'is_active')),
|
2023-04-17 10:35:17 -04:00
|
|
|
('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
|
2022-06-22 16:10:48 -04:00
|
|
|
('Assignment', (
|
|
|
|
'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
|
|
|
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
|
|
|
|
)),
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ConfigContext
|
|
|
|
fields = (
|
2022-06-22 16:10:48 -04:00
|
|
|
'name', 'weight', 'description', 'data', 'is_active', 'regions', 'site_groups', 'sites', 'locations',
|
|
|
|
'roles', 'device_types', 'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups',
|
2023-04-17 10:35:17 -04:00
|
|
|
'tenants', 'tags', 'data_source', 'data_file', 'auto_sync_enabled',
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
|
|
|
|
2023-03-28 10:00:15 -04:00
|
|
|
def __init__(self, *args, initial=None, **kwargs):
|
|
|
|
|
|
|
|
# Convert data delivered via initial data to JSON data
|
|
|
|
if initial and 'data' in initial:
|
|
|
|
if type(initial['data']) is str:
|
|
|
|
initial['data'] = json.loads(initial['data'])
|
|
|
|
|
|
|
|
super().__init__(*args, initial=initial, **kwargs)
|
2023-03-20 15:40:49 -04:00
|
|
|
|
|
|
|
# Disable data field when a DataFile has been set
|
|
|
|
if self.instance.data_file:
|
|
|
|
self.fields['data'].widget.attrs['readonly'] = True
|
|
|
|
self.fields['data'].help_text = _('Data is populated from the remote source selected below.')
|
|
|
|
|
2023-02-07 16:44:05 -05:00
|
|
|
def clean(self):
|
|
|
|
super().clean()
|
|
|
|
|
2023-02-08 18:24:18 -05:00
|
|
|
if not self.cleaned_data.get('data') and not self.cleaned_data.get('data_file'):
|
|
|
|
raise forms.ValidationError("Must specify either local data or a data file")
|
2023-02-07 16:44:05 -05:00
|
|
|
|
|
|
|
return self.cleaned_data
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
|
2023-02-17 08:33:08 -05:00
|
|
|
class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
|
|
|
|
tags = DynamicModelMultipleChoiceField(
|
|
|
|
queryset=Tag.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
template_code = forms.CharField(
|
|
|
|
required=False,
|
|
|
|
widget=forms.Textarea(attrs={'class': 'font-monospace'})
|
|
|
|
)
|
|
|
|
|
|
|
|
fieldsets = (
|
|
|
|
('Config Template', ('name', 'description', 'environment_params', 'tags')),
|
2023-03-21 17:00:06 -04:00
|
|
|
('Content', ('template_code',)),
|
2023-04-17 10:35:17 -04:00
|
|
|
('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
|
2023-02-17 08:33:08 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ConfigTemplate
|
|
|
|
fields = '__all__'
|
2023-03-21 17:00:06 -04:00
|
|
|
widgets = {
|
|
|
|
'environment_params': forms.Textarea(attrs={'rows': 5})
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Disable content field when a DataFile has been set
|
|
|
|
if self.instance.data_file:
|
|
|
|
self.fields['template_code'].widget.attrs['readonly'] = True
|
|
|
|
self.fields['template_code'].help_text = _(
|
|
|
|
'Template content is populated from the remote source selected below.'
|
|
|
|
)
|
2023-02-17 08:33:08 -05:00
|
|
|
|
|
|
|
def clean(self):
|
|
|
|
super().clean()
|
|
|
|
|
|
|
|
if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
|
|
|
|
raise forms.ValidationError("Must specify either local content or a data file")
|
|
|
|
|
|
|
|
return self.cleaned_data
|
|
|
|
|
|
|
|
|
2021-09-28 10:44:53 -04:00
|
|
|
class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ImageAttachment
|
|
|
|
fields = [
|
|
|
|
'name', 'image',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2022-03-31 11:40:02 -04:00
|
|
|
class JournalEntryForm(NetBoxModelForm):
|
2021-09-28 10:44:53 -04:00
|
|
|
kind = forms.ChoiceField(
|
|
|
|
choices=add_blank_choice(JournalEntryKindChoices),
|
2023-02-16 10:25:51 -05:00
|
|
|
required=False
|
2021-09-28 10:44:53 -04:00
|
|
|
)
|
2022-03-31 11:40:02 -04:00
|
|
|
comments = CommentField()
|
2021-09-28 10:44:53 -04:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = JournalEntry
|
2022-03-31 11:40:02 -04:00
|
|
|
fields = ['assigned_object_type', 'assigned_object_id', 'kind', 'tags', 'comments']
|
2021-09-28 10:44:53 -04:00
|
|
|
widgets = {
|
|
|
|
'assigned_object_type': forms.HiddenInput,
|
|
|
|
'assigned_object_id': forms.HiddenInput,
|
|
|
|
}
|
2023-06-22 11:04:24 -07:00
|
|
|
|
|
|
|
|
|
|
|
EMPTY_VALUES = ('', None, [], ())
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigFormMetaclass(forms.models.ModelFormMetaclass):
|
|
|
|
|
|
|
|
def __new__(mcs, name, bases, attrs):
|
|
|
|
|
|
|
|
# Emulate a declared field for each supported configuration parameter
|
|
|
|
param_fields = {}
|
|
|
|
for param in PARAMS:
|
|
|
|
field_kwargs = {
|
|
|
|
'required': False,
|
|
|
|
'label': param.label,
|
|
|
|
'help_text': param.description,
|
|
|
|
}
|
|
|
|
field_kwargs.update(**param.field_kwargs)
|
|
|
|
param_fields[param.name] = param.field(**field_kwargs)
|
|
|
|
attrs.update(param_fields)
|
|
|
|
|
|
|
|
return super().__new__(mcs, name, bases, attrs)
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMetaclass):
|
|
|
|
"""
|
|
|
|
Form for creating a new ConfigRevision.
|
|
|
|
"""
|
|
|
|
|
|
|
|
fieldsets = (
|
|
|
|
('Rack Elevations', ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH')),
|
|
|
|
('Power', ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')),
|
|
|
|
('IPAM', ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4')),
|
|
|
|
('Security', ('ALLOWED_URL_SCHEMES',)),
|
|
|
|
('Banners', ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')),
|
|
|
|
('Pagination', ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')),
|
|
|
|
('Validation', ('CUSTOM_VALIDATORS',)),
|
|
|
|
('User Preferences', ('DEFAULT_USER_PREFERENCES',)),
|
|
|
|
('Miscellaneous', ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL')),
|
|
|
|
('Config Revision', ('comment',))
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ConfigRevision
|
|
|
|
fields = '__all__'
|
|
|
|
widgets = {
|
|
|
|
'BANNER_LOGIN': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'BANNER_MAINTENANCE': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'BANNER_TOP': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'BANNER_BOTTOM': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'CUSTOM_VALIDATORS': forms.Textarea(attrs={'class': 'font-monospace'}),
|
|
|
|
'comment': forms.Textarea(),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Append current parameter values to form field help texts and check for static configurations
|
|
|
|
config = get_config()
|
|
|
|
for param in PARAMS:
|
|
|
|
value = getattr(config, param.name)
|
|
|
|
is_static = hasattr(settings, param.name)
|
|
|
|
if value:
|
|
|
|
help_text = self.fields[param.name].help_text
|
|
|
|
if help_text:
|
|
|
|
help_text += '<br />' # Line break
|
|
|
|
help_text += f'Current value: <strong>{value}</strong>'
|
|
|
|
if is_static:
|
|
|
|
help_text += ' (defined statically)'
|
|
|
|
elif value == param.default:
|
|
|
|
help_text += ' (default)'
|
|
|
|
self.fields[param.name].help_text = help_text
|
|
|
|
self.fields[param.name].initial = value
|
|
|
|
if is_static:
|
|
|
|
self.fields[param.name].disabled = True
|
|
|
|
|
|
|
|
def save(self, commit=True):
|
|
|
|
instance = super().save(commit=False)
|
|
|
|
|
|
|
|
# Populate JSON data on the instance
|
|
|
|
instance.data = self.render_json()
|
|
|
|
|
|
|
|
if commit:
|
|
|
|
instance.save()
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def render_json(self):
|
|
|
|
json = {}
|
|
|
|
|
|
|
|
# Iterate through each field and populate non-empty values
|
|
|
|
for field_name in self.declared_fields:
|
|
|
|
if field_name in self.cleaned_data and self.cleaned_data[field_name] not in EMPTY_VALUES:
|
|
|
|
json[field_name] = self.cleaned_data[field_name]
|
|
|
|
|
|
|
|
return json
|