1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

449 lines
13 KiB
Python
Raw Normal View History

from collections import OrderedDict
2016-08-12 17:20:01 -04:00
from django import forms
2018-06-20 13:52:54 -04:00
from django.contrib.auth.models import User
2016-08-12 17:20:01 -04:00
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
2018-07-10 10:00:21 -04:00
from taggit.forms import TagField
2016-08-12 17:20:01 -04:00
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from utilities.forms import (
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
2019-12-28 22:55:00 +00:00
CommentField, ContentTypeSelect, DatePicker, DateTimePicker, FilterChoiceField, LaxURLField, JSONField,
SlugField, StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES,
)
2019-10-04 12:08:48 -04:00
from .constants import *
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
2016-08-12 17:20:01 -04:00
2018-05-22 12:22:46 -04:00
#
# Custom fields
#
def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
2016-08-17 13:40:22 -04:00
"""
Retrieve all CustomFields applicable to the given ContentType
"""
field_dict = OrderedDict()
custom_fields = CustomField.objects.filter(obj_type=content_type)
if filterable_only:
custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
for cf in custom_fields:
field_name = 'cf_{}'.format(str(cf.name))
initial = cf.default if not bulk_edit else None
# Integer
if cf.type == CF_TYPE_INTEGER:
field = forms.IntegerField(required=cf.required, initial=initial)
# Boolean
elif cf.type == CF_TYPE_BOOLEAN:
choices = (
(None, '---------'),
(1, 'True'),
(0, 'False'),
)
if initial is not None and initial.lower() in ['true', 'yes', '1']:
initial = 1
elif initial is not None and initial.lower() in ['false', 'no', '0']:
initial = 0
2016-08-18 11:44:40 -04:00
else:
initial = None
field = forms.NullBooleanField(
2020-01-01 23:46:51 +00:00
required=cf.required, initial=initial, widget=StaticSelect2(choices=choices)
)
# Date
elif cf.type == CF_TYPE_DATE:
2019-12-28 22:55:00 +00:00
field = forms.DateField(required=cf.required, initial=initial, widget=DatePicker())
# Select
elif cf.type == CF_TYPE_SELECT:
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
if not cf.required or bulk_edit or filterable_only:
choices = [(None, '---------')] + choices
# Check for a default choice
default_choice = None
if initial:
try:
default_choice = cf.choices.get(value=initial).pk
except ObjectDoesNotExist:
pass
2020-01-01 23:46:51 +00:00
field = forms.TypedChoiceField(
choices=choices, coerce=int, required=cf.required, initial=default_choice, widget=StaticSelect2()
)
2016-08-12 17:20:01 -04:00
# URL
elif cf.type == CF_TYPE_URL:
field = LaxURLField(required=cf.required, initial=initial)
# Text
else:
field = forms.CharField(max_length=255, required=cf.required, initial=initial)
2016-08-12 17:20:01 -04:00
field.model = cf
2016-08-18 14:23:28 -04:00
field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize()
if cf.description:
field.help_text = cf.description
2016-08-12 17:20:01 -04:00
field_dict[field_name] = field
2016-08-12 17:20:01 -04:00
return field_dict
2016-08-12 17:20:01 -04:00
class CustomFieldForm(forms.ModelForm):
2016-08-12 17:20:01 -04:00
def __init__(self, *args, **kwargs):
2016-08-12 17:20:01 -04:00
self.custom_fields = []
self.obj_type = ContentType.objects.get_for_model(self._meta.model)
2016-08-12 17:20:01 -04:00
super().__init__(*args, **kwargs)
2016-08-12 17:20:01 -04:00
# Add all applicable CustomFields to the form
2016-08-17 13:40:22 -04:00
custom_fields = []
for name, field in get_custom_fields_for_model(self.obj_type).items():
self.fields[name] = field
2016-08-17 13:40:22 -04:00
custom_fields.append(name)
self.custom_fields = custom_fields
2016-08-15 15:24:23 -04:00
# If editing an existing object, initialize values for all custom fields
if self.instance.pk:
existing_values = CustomFieldValue.objects.filter(
obj_type=self.obj_type,
obj_id=self.instance.pk
).prefetch_related('field')
2016-08-15 15:24:23 -04:00
for cfv in existing_values:
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
2016-08-15 15:24:23 -04:00
def _save_custom_fields(self):
for field_name in self.custom_fields:
try:
cfv = CustomFieldValue.objects.prefetch_related('field').get(
field=self.fields[field_name].model,
obj_type=self.obj_type,
obj_id=self.instance.pk
)
except CustomFieldValue.DoesNotExist:
# Skip this field if none exists already and its value is empty
2017-05-24 11:33:11 -04:00
if self.cleaned_data[field_name] in [None, '']:
continue
cfv = CustomFieldValue(
field=self.fields[field_name].model,
obj_type=self.obj_type,
obj_id=self.instance.pk
)
cfv.value = self.cleaned_data[field_name]
cfv.save()
2016-08-15 15:24:23 -04:00
def save(self, commit=True):
obj = super().save(commit)
2016-08-15 15:24:23 -04:00
# Handle custom fields the same way we do M2M fields
if commit:
self._save_custom_fields()
else:
self.save_custom_fields = self._save_custom_fields
return obj
class CustomFieldBulkEditForm(BulkEditForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.custom_fields = []
self.obj_type = ContentType.objects.get_for_model(self.model)
# Add all applicable CustomFields to the form
custom_fields = get_custom_fields_for_model(self.obj_type, bulk_edit=True).items()
for name, field in custom_fields:
# Annotate non-required custom fields as nullable
if not field.required:
self.nullable_fields.append(name)
field.required = False
self.fields[name] = field
# Annotate this as a custom field
self.custom_fields.append(name)
class CustomFieldFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
self.obj_type = ContentType.objects.get_for_model(self.model)
super().__init__(*args, **kwargs)
# Add all applicable CustomFields to the form
custom_fields = get_custom_fields_for_model(self.obj_type, filterable_only=True).items()
for name, field in custom_fields:
field.required = False
self.fields[name] = field
2018-05-22 12:22:46 -04:00
#
# Tags
#
class TagForm(BootstrapMixin, forms.ModelForm):
slug = SlugField()
comments = CommentField()
2018-05-22 12:22:46 -04:00
class Meta:
model = Tag
2018-11-27 11:57:29 -05:00
fields = [
'name', 'slug', 'color', 'comments'
2018-11-27 11:57:29 -05:00
]
2018-05-22 12:22:46 -04:00
2018-07-10 10:00:21 -04:00
class AddRemoveTagsForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
2018-07-10 10:00:21 -04:00
# Add add/remove tags fields
self.fields['add_tags'] = TagField(required=False)
self.fields['remove_tags'] = TagField(required=False)
class TagFilterForm(BootstrapMixin, forms.Form):
model = Tag
2018-11-27 11:57:29 -05:00
q = forms.CharField(
required=False,
label='Search'
)
class TagBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(),
widget=forms.MultipleHiddenInput
)
color = forms.CharField(
max_length=6,
required=False,
widget=ColorSelect()
)
class Meta:
nullable_fields = []
2018-06-27 16:02:34 -04:00
#
# Config contexts
#
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
data = JSONField(
label=''
)
2018-06-27 16:02:34 -04:00
class Meta:
model = ConfigContext
2018-06-29 09:44:32 -04:00
fields = [
'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenant_groups',
'tenants', 'data',
2018-06-29 09:44:32 -04:00
]
widgets = {
'regions': APISelectMultiple(
api_url="/api/dcim/regions/"
),
'sites': APISelectMultiple(
api_url="/api/dcim/sites/"
),
'roles': APISelectMultiple(
api_url="/api/dcim/device-roles/"
),
'platforms': APISelectMultiple(
api_url="/api/dcim/platforms/"
),
'tenant_groups': APISelectMultiple(
api_url="/api/tenancy/tenant-groups/"
),
'tenants': APISelectMultiple(
api_url="/api/tenancy/tenants/"
)
}
2018-06-27 16:02:34 -04:00
class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ConfigContext.objects.all(),
widget=forms.MultipleHiddenInput
)
weight = forms.IntegerField(
required=False,
min_value=0
)
is_active = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect()
)
description = forms.CharField(
required=False,
max_length=100
)
class Meta:
2018-11-27 11:57:29 -05:00
nullable_fields = [
'description',
]
class ConfigContextFilterForm(BootstrapMixin, forms.Form):
q = forms.CharField(
required=False,
label='Search'
)
region = FilterChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/regions/",
value_field="slug",
)
)
site = FilterChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
)
)
role = FilterChoiceField(
queryset=DeviceRole.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/device-roles/",
value_field="slug",
)
)
platform = FilterChoiceField(
queryset=Platform.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/platforms/",
value_field="slug",
)
)
tenant_group = FilterChoiceField(
queryset=TenantGroup.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/tenancy/tenant-groups/",
value_field="slug",
)
)
tenant = FilterChoiceField(
queryset=Tenant.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
)
)
#
# Filter form for local config context data
#
class LocalConfigContextFilterForm(forms.Form):
local_context_data = forms.NullBooleanField(
required=False,
label='Has local config context data',
widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
2018-05-22 12:22:46 -04:00
#
# Image attachments
#
class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ImageAttachment
2018-11-27 11:57:29 -05:00
fields = [
'name', 'image',
]
2018-06-20 13:52:54 -04:00
#
# Change logging
#
class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
2018-06-20 13:52:54 -04:00
model = ObjectChange
q = forms.CharField(
required=False,
label='Search'
)
time_after = forms.DateTimeField(
2018-06-20 13:52:54 -04:00
label='After',
required=False,
2019-12-28 22:55:00 +00:00
widget=DateTimePicker()
2018-06-20 13:52:54 -04:00
)
time_before = forms.DateTimeField(
2018-06-20 13:52:54 -04:00
label='Before',
required=False,
2019-12-28 22:55:00 +00:00
widget=DateTimePicker()
2018-06-20 13:52:54 -04:00
)
action = forms.ChoiceField(
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
required=False
)
user = forms.ModelChoiceField(
queryset=User.objects.order_by('username'),
required=False
)
changed_object_type = forms.ModelChoiceField(
queryset=ContentType.objects.order_by('model'),
required=False,
widget=ContentTypeSelect(),
label='Object Type'
)
2019-08-09 12:33:33 -04:00
#
# Scripts
#
class ScriptForm(BootstrapMixin, forms.Form):
2019-08-12 14:28:06 -04:00
_commit = forms.BooleanField(
required=False,
initial=True,
label="Commit changes",
help_text="Commit changes to the database (uncheck for a dry-run)"
)
2019-08-09 12:33:33 -04:00
def __init__(self, vars, *args, commit_default=True, **kwargs):
2019-08-09 12:33:33 -04:00
super().__init__(*args, **kwargs)
# Dynamically populate fields for variables
2019-08-09 16:34:01 -04:00
for name, var in vars.items():
2019-08-09 12:33:33 -04:00
self.fields[name] = var.as_field()
2019-08-12 14:28:06 -04:00
# Toggle default commit behavior based on Meta option
if not commit_default:
self.fields['_commit'].initial = False
2019-08-12 14:28:06 -04:00
# Move _commit to the end of the form
self.fields.move_to_end('_commit', True)
@property
def requires_input(self):
"""
A boolean indicating whether the form requires user input (ignore the _commit field).
"""
return bool(len(self.fields) > 1)