Skip to content

Commit

Permalink
Merge pull request #625 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v1.6.3
  • Loading branch information
jeremystretch authored Oct 19, 2016
2 parents 7055292 + 493b7d5 commit c171547
Show file tree
Hide file tree
Showing 25 changed files with 336 additions and 203 deletions.
11 changes: 8 additions & 3 deletions netbox/circuits/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.forms import bulkedit_tenant_choices
from tenancy.models import Tenant
from utilities.forms import (
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
Expand Down Expand Up @@ -57,6 +56,9 @@ class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
comments = CommentField()

class Meta:
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']


class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Provider
Expand Down Expand Up @@ -86,7 +88,7 @@ class CircuitForm(BootstrapMixin, CustomFieldForm):
attrs={'filter-for': 'device'}))
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
attrs={'filter-for': 'interface'}))
display_field='display_name', attrs={'filter-for': 'interface'}))
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
)
Expand Down Expand Up @@ -178,11 +180,14 @@ class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
comments = CommentField()

class Meta:
nullable_fields = ['tenant', 'port_speed', 'commit_rate', 'comments']


class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit
Expand Down
101 changes: 52 additions & 49 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
@@ -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
)


Expand All @@ -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))


#
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -279,7 +261,7 @@ class Meta:
'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)
Expand Down Expand Up @@ -334,6 +316,14 @@ class Meta:
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')

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
)
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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')
)
Expand Down Expand Up @@ -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')
)
Expand Down Expand Up @@ -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
#
Expand All @@ -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')
Expand Down Expand Up @@ -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):

Expand Down
6 changes: 2 additions & 4 deletions netbox/dcim/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
from secrets.views import secret_add

from . import views
from .models import (
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, PowerPortTemplate, PowerOutletTemplate,
InterfaceTemplate,
)


urlpatterns = [
Expand Down Expand Up @@ -75,6 +71,7 @@

# Interface templates
url(r'^device-types/(?P<pk>\d+)/interfaces/add/$', views.InterfaceTemplateAddView.as_view(), name='devicetype_add_interface'),
url(r'^device-types/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceTemplateBulkEditView.as_view(), name='devicetype_bulkedit_interface'),
url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceTemplateBulkDeleteView.as_view(), name='devicetype_delete_interface'),

# Device bay templates
Expand Down Expand Up @@ -159,6 +156,7 @@
# Interfaces
url(r'^devices/interfaces/add/$', views.InterfaceBulkAddView.as_view(), name='interface_add_multi'),
url(r'^devices/(?P<pk>\d+)/interfaces/add/$', views.interface_add, name='interface_add'),
url(r'^devices/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),
Expand Down
16 changes: 16 additions & 0 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,14 @@ class InterfaceTemplateAddView(ComponentTemplateCreateView):
form = forms.InterfaceTemplateForm


class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_interfacetemplate'
cls = InterfaceTemplate
parent_cls = DeviceType
form = forms.InterfaceTemplateBulkEditForm
template_name = 'dcim/interfacetemplate_bulk_edit.html'


class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_interfacetemplate'
cls = InterfaceTemplate
Expand Down Expand Up @@ -1425,6 +1433,14 @@ def update_objects(self, pk_list, form, fields):
len(selected_devices)))


class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_interface'
cls = Interface
parent_cls = Device
form = forms.InterfaceBulkEditForm
template_name = 'dcim/interface_bulk_edit.html'


class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_interface'
cls = Interface
Expand Down
Loading

0 comments on commit c171547

Please sign in to comment.