|
|
@@ -1,23 +1,24 @@
|
|
|
import re
|
|
|
|
|
|
from django import forms
|
|
|
+from django.core.exceptions import ValidationError
|
|
|
from django.db.models import Count, Q
|
|
|
|
|
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
|
|
from ipam.models import IPAddress
|
|
|
-from tenancy.forms import bulkedit_tenant_choices
|
|
|
from tenancy.models import Tenant
|
|
|
from utilities.forms import (
|
|
|
- APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
|
|
- FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
|
|
+ APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkImportForm, CommentField, CSVDataField,
|
|
|
+ ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea,
|
|
|
+ SlugField,
|
|
|
)
|
|
|
|
|
|
from .models import (
|
|
|
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
|
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
|
|
- Interface, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
|
|
- PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackRole,
|
|
|
- Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
|
|
+ Interface, IFACE_FF_CHOICES, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module,
|
|
|
+ Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES,
|
|
|
+ Rack, RackGroup, RackRole, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -42,37 +43,12 @@ def get_device_by_name_or_pk(name):
|
|
|
return device
|
|
|
|
|
|
|
|
|
-def bulkedit_platform_choices():
|
|
|
- choices = [
|
|
|
- (None, '---------'),
|
|
|
- (0, 'None'),
|
|
|
- ]
|
|
|
- choices += [(p.pk, p.name) for p in Platform.objects.all()]
|
|
|
- return choices
|
|
|
-
|
|
|
-
|
|
|
-def bulkedit_rackgroup_choices():
|
|
|
- """
|
|
|
- Include an option to remove the currently assigned group from a rack.
|
|
|
- """
|
|
|
- choices = [
|
|
|
- (None, '---------'),
|
|
|
- (0, 'None'),
|
|
|
- ]
|
|
|
- choices += [(r.pk, r) for r in RackGroup.objects.all()]
|
|
|
- return choices
|
|
|
-
|
|
|
-
|
|
|
-def bulkedit_rackrole_choices():
|
|
|
+def validate_connection_status(value):
|
|
|
"""
|
|
|
- Include an option to remove the currently assigned role from a rack.
|
|
|
+ Custom validator for connection statuses. value must be either "planned" or "connected" (case-insensitive).
|
|
|
"""
|
|
|
- choices = [
|
|
|
- (None, '---------'),
|
|
|
- (0, 'None'),
|
|
|
- ]
|
|
|
- choices += [(r.pk, r.name) for r in RackRole.objects.all()]
|
|
|
- return choices
|
|
|
+ if value.lower() not in ['planned', 'connected']:
|
|
|
+ raise ValidationError('Invalid connection status ({}); must be either "planned" or "connected".'.format(value))
|
|
|
|
|
|
|
|
|
#
|
|
|
@@ -114,7 +90,10 @@ class SiteImportForm(BulkImportForm, BootstrapMixin):
|
|
|
|
|
|
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
- tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
+ tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = ['tenant']
|
|
|
|
|
|
|
|
|
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
@@ -234,14 +213,17 @@ class RackImportForm(BulkImportForm, BootstrapMixin):
|
|
|
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
|
|
|
- group = forms.TypedChoiceField(choices=bulkedit_rackgroup_choices, coerce=int, required=False, label='Group')
|
|
|
- tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
- role = forms.TypedChoiceField(choices=bulkedit_rackrole_choices, coerce=int, required=False, label='Role')
|
|
|
+ group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False, label='Group')
|
|
|
+ tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
|
+ role = forms.ModelChoiceField(queryset=RackRole.objects.all(), required=False)
|
|
|
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
|
|
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
|
|
u_height = forms.IntegerField(required=False, label='Height (U)')
|
|
|
comments = CommentField()
|
|
|
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = ['group', 'tenant', 'role', 'comments']
|
|
|
+
|
|
|
|
|
|
class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
model = Rack
|
|
|
@@ -279,7 +261,7 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
|
|
|
'is_pdu', 'is_network_device', 'subdevice_role']
|
|
|
|
|
|
|
|
|
-class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
|
|
+class DeviceTypeBulkEditForm(BulkEditForm, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
|
|
|
u_height = forms.IntegerField(min_value=1, required=False)
|
|
|
@@ -334,6 +316,14 @@ class InterfaceTemplateForm(forms.ModelForm, BootstrapMixin):
|
|
|
fields = ['name_pattern', 'form_factor', 'mgmt_only']
|
|
|
|
|
|
|
|
|
+class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
|
|
|
+ pk = forms.ModelMultipleChoiceField(queryset=InterfaceTemplate.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
+ form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = []
|
|
|
+
|
|
|
+
|
|
|
class DeviceBayTemplateForm(forms.ModelForm, BootstrapMixin):
|
|
|
name_pattern = ExpandableNameField(label='Name')
|
|
|
|
|
|
@@ -583,12 +573,14 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), required=False, label='Type')
|
|
|
device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False, label='Role')
|
|
|
- tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
- platform = forms.TypedChoiceField(choices=bulkedit_platform_choices, coerce=int, required=False,
|
|
|
- label='Platform')
|
|
|
+ tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
|
+ platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
|
|
|
status = forms.ChoiceField(choices=FORM_STATUS_CHOICES, required=False, initial='', label='Status')
|
|
|
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
|
|
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = ['tenant', 'platform']
|
|
|
+
|
|
|
|
|
|
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|
|
model = Device
|
|
|
@@ -631,7 +623,7 @@ class ConsoleConnectionCSVForm(forms.Form):
|
|
|
device = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Device not found'})
|
|
|
console_port = forms.CharField()
|
|
|
- status = forms.ChoiceField(choices=[('planned', 'Planned'), ('connected', 'Connected')])
|
|
|
+ status = forms.CharField(validators=[validate_connection_status])
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
@@ -695,6 +687,7 @@ class ConsolePortConnectionForm(forms.ModelForm, BootstrapMixin):
|
|
|
widget=forms.Select(attrs={'filter-for': 'console_server'}))
|
|
|
console_server = forms.ModelChoiceField(queryset=Device.objects.all(), label='Console Server', required=False,
|
|
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}&is_console_server=True',
|
|
|
+ display_field='display_name',
|
|
|
attrs={'filter-for': 'cs_port'}))
|
|
|
livesearch = forms.CharField(required=False, label='Console Server', widget=Livesearch(
|
|
|
query_key='q', query_url='dcim-api:device_list', field_to_update='console_server')
|
|
|
@@ -762,7 +755,7 @@ class ConsoleServerPortConnectionForm(forms.Form, BootstrapMixin):
|
|
|
widget=forms.Select(attrs={'filter-for': 'device'}))
|
|
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), label='Device', required=False,
|
|
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
|
|
|
- attrs={'filter-for': 'port'}))
|
|
|
+ display_field='display_name', attrs={'filter-for': 'port'}))
|
|
|
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
|
|
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
|
|
|
)
|
|
|
@@ -826,7 +819,7 @@ class PowerConnectionCSVForm(forms.Form):
|
|
|
device = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Device not found'})
|
|
|
power_port = forms.CharField()
|
|
|
- status = forms.ChoiceField(choices=[('planned', 'Planned'), ('connected', 'Connected')])
|
|
|
+ status = forms.CharField(validators=[validate_connection_status])
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
@@ -891,7 +884,7 @@ class PowerPortConnectionForm(forms.ModelForm, BootstrapMixin):
|
|
|
widget=forms.Select(attrs={'filter-for': 'pdu'}))
|
|
|
pdu = forms.ModelChoiceField(queryset=Device.objects.all(), label='PDU', required=False,
|
|
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}&is_pdu=True',
|
|
|
- attrs={'filter-for': 'power_outlet'}))
|
|
|
+ display_field='display_name', attrs={'filter-for': 'power_outlet'}))
|
|
|
livesearch = forms.CharField(required=False, label='PDU', widget=Livesearch(
|
|
|
query_key='q', query_url='dcim-api:device_list', field_to_update='pdu')
|
|
|
)
|
|
|
@@ -958,7 +951,7 @@ class PowerOutletConnectionForm(forms.Form, BootstrapMixin):
|
|
|
widget=forms.Select(attrs={'filter-for': 'device'}))
|
|
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), label='Device', required=False,
|
|
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
|
|
|
- attrs={'filter-for': 'port'}))
|
|
|
+ display_field='display_name', attrs={'filter-for': 'port'}))
|
|
|
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
|
|
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
|
|
|
)
|
|
|
@@ -1023,6 +1016,15 @@ class InterfaceBulkCreateForm(InterfaceCreateForm, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
|
|
|
+class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
|
|
+ pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
+ form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
|
|
|
+ description = forms.CharField(max_length=100, required=False)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ nullable_fields = ['description']
|
|
|
+
|
|
|
+
|
|
|
#
|
|
|
# Interface connections
|
|
|
#
|
|
|
@@ -1033,6 +1035,7 @@ class InterfaceConnectionForm(forms.ModelForm, BootstrapMixin):
|
|
|
widget=forms.Select(attrs={'filter-for': 'device_b'}))
|
|
|
device_b = forms.ModelChoiceField(queryset=Device.objects.all(), label='Device', required=False,
|
|
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack_b}}',
|
|
|
+ display_field='display_name',
|
|
|
attrs={'filter-for': 'interface_b'}))
|
|
|
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
|
|
query_key='q', query_url='dcim-api:device_list', field_to_update='device_b')
|
|
|
@@ -1087,7 +1090,7 @@ class InterfaceConnectionCSVForm(forms.Form):
|
|
|
device_b = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Device B not found.'})
|
|
|
interface_b = forms.CharField()
|
|
|
- status = forms.ChoiceField(choices=[('planned', 'Planned'), ('connected', 'Connected')])
|
|
|
+ status = forms.CharField(validators=[validate_connection_status])
|
|
|
|
|
|
def clean(self):
|
|
|
|