From 69debfdefa992ca365638dd876f96cbcb6ceb581 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Sat, 6 Aug 2016 16:32:54 -0400 Subject: [PATCH 01/22] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index d1272f4a268..22fcfe3f0b6 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ "the documentation.") -VERSION = '1.4.2' +VERSION = '1.4.3-dev' # Import local configuration for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: From 324a5e10d74abe852ef6775f66b711e263baf8e7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 09:45:44 -0400 Subject: [PATCH 02/22] Fixes #433: Correct form validation when editing child devices --- netbox/dcim/forms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index fef87ded079..5fa133bf5f3 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -402,7 +402,7 @@ def __init__(self, *args, **kwargs): self.fields['primary_ip6'].widget.attrs['readonly'] = True # Limit rack choices - if self.is_bound: + if self.is_bound and self.data.get('site'): self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site']) elif self.initial.get('site'): self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site']) @@ -443,6 +443,8 @@ def __init__(self, *args, **kwargs): if pk and self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'): self.fields['site'].disabled = True self.fields['rack'].disabled = True + self.initial['site'] = self.instance.parent_bay.device.rack.site_id + self.initial['rack'] = self.instance.parent_bay.device.rack_id class BaseDeviceFromCSVForm(forms.ModelForm): From b131fbd7744375dfb5572a0d9ca04513e11b94d9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 12:04:20 -0400 Subject: [PATCH 03/22] Corred typo in HTML --- netbox/templates/circuits/circuit_import.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/templates/circuits/circuit_import.html b/netbox/templates/circuits/circuit_import.html index a6cd33ecde6..3679dd5f3de 100644 --- a/netbox/templates/circuits/circuit_import.html +++ b/netbox/templates/circuits/circuit_import.html @@ -60,7 +60,7 @@

CSV Format

Port Speed - Physical speed in Kbps/td> + Physical speed in Kbps 10000 From 5116db33443dc40fe80f3c556f1d4d89d9509c42 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 12:28:38 -0400 Subject: [PATCH 04/22] Fixes #442: Correct child device import instructions --- netbox/templates/dcim/device_import_child.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/templates/dcim/device_import_child.html b/netbox/templates/dcim/device_import_child.html index 5b9a145411a..d01c376536c 100644 --- a/netbox/templates/dcim/device_import_child.html +++ b/netbox/templates/dcim/device_import_child.html @@ -36,6 +36,11 @@

CSV Format

Functional role of device Blade Server + + Tenant + Name of tenant (optional) + Pied Piper + Device manufacturer Hardware manufacturer @@ -69,7 +74,7 @@

CSV Format

Example

-
Blade12,Blade Server,Dell,BS2000T,Linux,CAB00577291,Server101,Slot4
+
Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,Server101,Slot4
{% endblock %} From d463161619d5749a6d7f629f5aa32d2c5272ccf7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 16:51:19 -0400 Subject: [PATCH 05/22] Closes #149: Added upstream_speed field to Circuit --- netbox/circuits/admin.py | 4 ++-- netbox/circuits/api/serializers.py | 2 +- netbox/circuits/forms.py | 6 +++--- .../0005_circuit_add_upstream_speed.py | 20 +++++++++++++++++++ netbox/circuits/models.py | 13 ++++++++++-- netbox/templates/circuits/circuit.html | 9 +++++---- netbox/templates/circuits/circuit_edit.html | 1 + netbox/templates/circuits/circuit_import.html | 9 +++++++-- 8 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 netbox/circuits/migrations/0005_circuit_add_upstream_speed.py diff --git a/netbox/circuits/admin.py b/netbox/circuits/admin.py index 31caecdec03..97711b7a8bf 100644 --- a/netbox/circuits/admin.py +++ b/netbox/circuits/admin.py @@ -21,8 +21,8 @@ class CircuitTypeAdmin(admin.ModelAdmin): @admin.register(Circuit) class CircuitAdmin(admin.ModelAdmin): - list_display = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'commit_rate', - 'xconnect_id'] + list_display = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed_human', + 'upstream_speed_human', 'commit_rate_human', 'xconnect_id'] list_filter = ['provider', 'type', 'tenant'] exclude = ['interface'] diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 0efedd04c1c..ed60339634d 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -53,7 +53,7 @@ class CircuitSerializer(serializers.ModelSerializer): class Meta: model = Circuit fields = ['id', 'cid', 'provider', 'type', 'tenant', 'site', 'interface', 'install_date', 'port_speed', - 'commit_rate', 'xconnect_id', 'comments'] + 'upstream_speed', 'commit_rate', 'xconnect_id', 'comments'] class CircuitNestedSerializer(CircuitSerializer): diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index b5152e08c26..a44dd763ef1 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -102,7 +102,7 @@ class Meta: model = Circuit fields = [ 'cid', 'type', 'provider', 'tenant', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date', - 'port_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments' + 'port_speed', 'upstream_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments' ] help_texts = { 'cid': "Unique circuit ID", @@ -169,8 +169,8 @@ class CircuitFromCSVForm(forms.ModelForm): class Meta: model = Circuit - fields = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'commit_rate', - 'xconnect_id', 'pp_info'] + fields = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'upstream_speed', + 'commit_rate', 'xconnect_id', 'pp_info'] class CircuitImportForm(BulkImportForm, BootstrapMixin): diff --git a/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py b/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py new file mode 100644 index 00000000000..f309cb2d819 --- /dev/null +++ b/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.8 on 2016-08-08 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0004_circuit_add_tenant'), + ] + + operations = [ + migrations.AddField( + model_name='circuit', + name='upstream_speed', + field=models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)'), + ), + ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 15dbea21649..00367a27abe 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -72,6 +72,8 @@ class Circuit(CreatedUpdatedModel): interface = models.OneToOneField(Interface, related_name='circuit', blank=True, null=True) install_date = models.DateField(blank=True, null=True, verbose_name='Date installed') port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)') + upstream_speed = models.PositiveIntegerField(blank=True, null=True, verbose_name='Upstream speed (Kbps)', + help_text='Upstream speed, if different from port speed') commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)') xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID') pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)') @@ -96,6 +98,7 @@ def to_csv(self): self.site.name, self.install_date.isoformat() if self.install_date else '', str(self.port_speed), + str(self.upstream_speed), str(self.commit_rate) if self.commit_rate else '', self.xconnect_id, self.pp_info, @@ -116,12 +119,18 @@ def _humanize_speed(self, speed): else: return '{} Kbps'.format(speed) - @property def port_speed_human(self): return self._humanize_speed(self.port_speed) + port_speed_human.admin_order_field = 'port_speed' + + def upstream_speed_human(self): + if not self.upstream_speed: + return '' + return self._humanize_speed(self.upstream_speed) + upstream_speed_human.admin_order_field = 'upstream_speed' - @property def commit_rate_human(self): if not self.commit_rate: return '' return self._humanize_speed(self.commit_rate) + commit_rate_human.admin_order_field = 'commit_rate' diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 481953af6f5..099832054b7 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -82,12 +82,13 @@

{{ circuit.provider }} - {{ circuit.cid }}

- Port Speed + Speed - {% if circuit.port_speed %} - {{ circuit.port_speed_human }} + {% if circuit.upstream_speed %} + {{ circuit.port_speed_human }}   + {{ circuit.upstream_speed_human }} {% else %} - N/A + {{ circuit.port_speed_human }} {% endif %} diff --git a/netbox/templates/circuits/circuit_edit.html b/netbox/templates/circuits/circuit_edit.html index c489db332b3..94eead67367 100644 --- a/netbox/templates/circuits/circuit_edit.html +++ b/netbox/templates/circuits/circuit_edit.html @@ -19,6 +19,7 @@
Bandwidth
{% render_field form.port_speed %} + {% render_field form.upstream_speed %} {% render_field form.commit_rate %}
diff --git a/netbox/templates/circuits/circuit_import.html b/netbox/templates/circuits/circuit_import.html index 3679dd5f3de..26900521831 100644 --- a/netbox/templates/circuits/circuit_import.html +++ b/netbox/templates/circuits/circuit_import.html @@ -61,7 +61,12 @@

CSV Format

Port Speed Physical speed in Kbps - 10000 + 100000 + + + Upstream Speed + Upstream speed in Kbps (optional) + 20000 Commit rate @@ -81,7 +86,7 @@

CSV Format

Example

-
IC-603122,TeliaSonera,Transit,Strickland Propane,ASH-4,2016-02-23,10000,2000,937649,PP8371 ports 13/14
+
IC-603122,TeliaSonera,Transit,Strickland Propane,ASH-4,2016-02-23,100000,,2000,937649,PP8371 ports 13/14
{% endblock %} From e7116b81a4a45d553e3c60ecc1a18dc47353582c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 18:01:15 -0400 Subject: [PATCH 06/22] #180: Added type and width fields to Rack model --- netbox/dcim/admin.py | 2 +- netbox/dcim/api/serializers.py | 7 +++--- netbox/dcim/forms.py | 25 ++++++++++++++++--- .../migrations/0014_rack_add_type_width.py | 25 +++++++++++++++++++ netbox/dcim/models.py | 23 +++++++++++++++++ netbox/dcim/tests/test_apis.py | 6 +++++ netbox/dcim/views.py | 2 +- netbox/templates/dcim/rack.html | 14 +++++++++++ netbox/templates/dcim/rack_bulk_edit.html | 15 +++++++++-- netbox/templates/dcim/rack_edit.html | 2 ++ netbox/templates/dcim/rack_import.html | 12 ++++++++- netbox/utilities/forms.py | 7 ++++++ 12 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 netbox/dcim/migrations/0014_rack_add_type_width.py diff --git a/netbox/dcim/admin.py b/netbox/dcim/admin.py index c5210987b3f..95a84be00db 100644 --- a/netbox/dcim/admin.py +++ b/netbox/dcim/admin.py @@ -26,7 +26,7 @@ class RackGroupAdmin(admin.ModelAdmin): @admin.register(Rack) class RackAdmin(admin.ModelAdmin): - list_display = ['name', 'facility_id', 'site', 'u_height'] + list_display = ['name', 'facility_id', 'site', 'type', 'width', 'u_height'] # diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 7a6693c375a..c36612c495d 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -58,7 +58,8 @@ class RackSerializer(serializers.ModelSerializer): class Meta: model = Rack - fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments'] + fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'type', 'width', 'u_height', + 'comments'] class RackNestedSerializer(RackSerializer): @@ -72,8 +73,8 @@ class RackDetailSerializer(RackSerializer): rear_units = serializers.SerializerMethodField() class Meta(RackSerializer.Meta): - fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments', - 'front_units', 'rear_units'] + fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'type', 'width', 'u_height', + 'comments', 'front_units', 'rear_units'] def get_front_units(self, obj): units = obj.get_rack_units(face=RACK_FACE_FRONT) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 5fa133bf5f3..15b2dd6bef9 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -7,7 +7,7 @@ from tenancy.forms import bulkedit_tenant_choices from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField, + APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField, ) @@ -15,7 +15,8 @@ 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, RackGroup, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD + PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, Site, + STATUS_CHOICES, SUBDEVICE_ROLE_CHILD ) @@ -135,7 +136,7 @@ class RackForm(forms.ModelForm, BootstrapMixin): class Meta: model = Rack - fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'u_height', 'comments'] + fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height', 'comments'] help_texts = { 'site': "The site at which the rack exists", 'name': "Organizational rack name", @@ -165,10 +166,11 @@ class RackFromCSVForm(forms.ModelForm): group_name = forms.CharField(required=False) tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False, error_messages={'invalid_choice': 'Tenant not found.'}) + type = forms.CharField(required=False) class Meta: model = Rack - fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'u_height'] + fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height'] def clean(self): @@ -182,6 +184,19 @@ def clean(self): except RackGroup.DoesNotExist: self.add_error('group_name', "Invalid rack group ({})".format(group)) + def clean_type(self): + rack_type = self.cleaned_data['type'] + if not rack_type: + return None + try: + choices = {v.lower(): k for k, v in RACK_TYPE_CHOICES} + return choices[rack_type.lower()] + except KeyError: + raise forms.ValidationError('Invalid rack type ({}). Valid choices are: {}.'.format( + rack_type, + ', '.join({v: k for k, v in RACK_TYPE_CHOICES}), + )) + class RackImportForm(BulkImportForm, BootstrapMixin): csv = CSVDataField(csv_form=RackFromCSVForm) @@ -192,6 +207,8 @@ class RackBulkEditForm(forms.Form, BootstrapMixin): site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False) group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False) tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant') + 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() diff --git a/netbox/dcim/migrations/0014_rack_add_type_width.py b/netbox/dcim/migrations/0014_rack_add_type_width.py new file mode 100644 index 00000000000..c14768c0f53 --- /dev/null +++ b/netbox/dcim/migrations/0014_rack_add_type_width.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.8 on 2016-08-08 21:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0013_add_interface_form_factors'), + ] + + operations = [ + migrations.AddField( + model_name='rack', + name='type', + field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'), + ), + migrations.AddField( + model_name='rack', + name='width', + field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'), + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 119336569a1..6d9d0e29063 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -16,6 +16,26 @@ from .fields import ASNField, MACAddressField +RACK_TYPE_2POST = 100 +RACK_TYPE_4POST = 200 +RACK_TYPE_CABINET = 300 +RACK_TYPE_WALLFRAME = 1000 +RACK_TYPE_WALLCABINET = 1100 +RACK_TYPE_CHOICES = ( + (RACK_TYPE_2POST, '2-post frame'), + (RACK_TYPE_4POST, '4-post frame'), + (RACK_TYPE_CABINET, '4-post cabinet'), + (RACK_TYPE_WALLFRAME, 'Wall-mounted frame'), + (RACK_TYPE_WALLCABINET, 'Wall-mounted cabinet'), +) + +RACK_WIDTH_19IN = 19 +RACK_WIDTH_23IN = 23 +RACK_WIDTH_CHOICES = ( + (RACK_WIDTH_19IN, '19 inches'), + (RACK_WIDTH_23IN, '23 inches'), +) + RACK_FACE_FRONT = 0 RACK_FACE_REAR = 1 RACK_FACE_CHOICES = [ @@ -284,6 +304,9 @@ class Rack(CreatedUpdatedModel): site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT) group = models.ForeignKey('RackGroup', related_name='racks', blank=True, null=True, on_delete=models.SET_NULL) tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='racks', on_delete=models.PROTECT) + type = models.PositiveSmallIntegerField(choices=RACK_TYPE_CHOICES, blank=True, null=True, verbose_name='Type') + width = models.PositiveSmallIntegerField(choices=RACK_WIDTH_CHOICES, default=RACK_WIDTH_19IN, verbose_name='Width', + help_text='Rail-to-rail width') u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)') comments = models.TextField(blank=True) diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py index 8b0d8ca53f1..fb4377ce4c3 100644 --- a/netbox/dcim/tests/test_apis.py +++ b/netbox/dcim/tests/test_apis.py @@ -42,6 +42,8 @@ class SiteTest(APITestCase): 'site', 'group', 'tenant', + 'type', + 'width', 'u_height', 'comments' ] @@ -118,6 +120,8 @@ class RackTest(APITestCase): 'site', 'group', 'tenant', + 'type', + 'width', 'u_height', 'comments' ] @@ -130,6 +134,8 @@ class RackTest(APITestCase): 'site', 'group', 'tenant', + 'type', + 'width', 'u_height', 'comments', 'front_units', diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index cc5ea9af32d..928c11f5182 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -227,7 +227,7 @@ def update_objects(self, pk_list, form): fields_to_update['tenant'] = None elif form.cleaned_data['tenant']: fields_to_update['tenant'] = form.cleaned_data['tenant'] - for field in ['site', 'group', 'tenant', 'u_height', 'comments']: + for field in ['site', 'group', 'tenant', 'type', 'width', 'u_height', 'comments']: if form.cleaned_data[field]: fields_to_update[field] = form.cleaned_data[field] diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index cf6ebf56735..4996a80c269 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -96,6 +96,20 @@

Rack {{ rack.name }}

{% endif %} + + Type + + {% if rack.type %} + {{ rack.get_type_display }} + {% else %} + None + {% endif %} + + + + Width + {{ rack.get_width_display }} + Height {{ rack.u_height }}U diff --git a/netbox/templates/dcim/rack_bulk_edit.html b/netbox/templates/dcim/rack_bulk_edit.html index 1085dd1e278..5ffa0058253 100644 --- a/netbox/templates/dcim/rack_bulk_edit.html +++ b/netbox/templates/dcim/rack_bulk_edit.html @@ -4,13 +4,24 @@ {% block title %}Rack Bulk Edit{% endblock %} {% block select_objects_table %} + + Name + Site + Group + Tenant + Type + Width + Height + {% for rack in selected_objects %} {{ rack }} - {{ rack.facility_id }} {{ rack.site }} + {{ rack.group }} {{ rack.tenant }} - {{ rack.u_height }} + {{ rack.get_type_display }} + {{ rack.get_width_display }} + {{ rack.u_height }}U {% endfor %} {% endblock %} diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index ab055e8ee45..166c052f6e8 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -10,6 +10,8 @@ {% render_field form.name %} {% render_field form.facility_id %} {% render_field form.tenant %} + {% render_field form.type %} + {% render_field form.width %} {% render_field form.u_height %} diff --git a/netbox/templates/dcim/rack_import.html b/netbox/templates/dcim/rack_import.html index b6e797d3944..e088f080944 100644 --- a/netbox/templates/dcim/rack_import.html +++ b/netbox/templates/dcim/rack_import.html @@ -53,6 +53,16 @@

CSV Format

Name of tenant (optional) Pied Piper + + Type + Rack type (optional) + 4-post cabinet + + + Width + Rail-to-rail width (19 or 23 inches) + 19 + Height Height in rack units @@ -61,7 +71,7 @@

CSV Format

Example

-
DC-4,Cage 1400,R101,J12.100,Pied Piper,42
+
DC-4,Cage 1400,R101,J12.100,Pied Piper,4-post cabinet,19,42
{% endblock %} diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 979bdd0ad93..f1c942fe65b 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -27,6 +27,13 @@ def expand_pattern(string): yield "{}{}{}".format(lead, i, remnant) +def add_blank_choice(choices): + """ + Add a blank choice to the beginning of a choices list. + """ + return ((None, '---------'),) + choices + + # # Widgets # From bddd29c99f6ddc8fb3c9d22cf0d1924767d865c3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Aug 2016 21:44:54 -0400 Subject: [PATCH 07/22] Fixes #443: Correctly display and initialize VRF for creation of new IP addresses --- netbox/ipam/tables.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 4c5bbee3ab2..c669362c51a 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -43,12 +43,22 @@ {% if record.pk %} {{ record.address }} {% elif perms.ipam.add_ipaddress %} - {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Lots of{% endif %} free IP{{ record.0|pluralize }} + {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Lots of{% endif %} free IP{{ record.0|pluralize }} {% else %} {{ record.0 }} {% endif %} """ +VRF_LINK = """ +{% if record.vrf %} + {{ record.vrf }} +{% elif prefix.vrf %} + {{ prefix.vrf }} +{% else %} + Global +{% endif %} +""" + STATUS_LABEL = """ {% if record.pk %} {{ record.get_status_display }} @@ -149,7 +159,7 @@ class PrefixTable(BaseTable): pk = ToggleColumn() status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status') prefix = tables.TemplateColumn(PREFIX_LINK, verbose_name='Prefix') - vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF') + vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant') site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site') role = tables.Column(verbose_name='Role') @@ -183,7 +193,7 @@ class Meta(BaseTable.Meta): class IPAddressTable(BaseTable): pk = ToggleColumn() address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address') - vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF') + vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant') device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False, verbose_name='Device') From 31ebbb3324f3c316fcdfe1b130a28fb531a54896 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Aug 2016 09:50:50 -0400 Subject: [PATCH 08/22] Fixes #444: Corrected prefix model validation --- netbox/ipam/forms.py | 8 ++------ netbox/ipam/models.py | 13 +++++++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 666b2ee81a4..00374ef368d 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -182,18 +182,14 @@ def __init__(self, *args, **kwargs): self.fields['vlan'].choices = [] def clean_prefix(self): - data = self.cleaned_data['prefix'] - try: - prefix = IPNetwork(data) - except: - raise + prefix = self.cleaned_data['prefix'] if prefix.version == 4 and prefix.prefixlen == 32: raise forms.ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 " "addresses instead.") elif prefix.version == 6 and prefix.prefixlen == 128: raise forms.ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 " "addresses instead.") - return data + return prefix class PrefixFromCSVForm(forms.ModelForm): diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 7c981a8cbc4..bd49feef10e 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -254,12 +254,13 @@ def get_absolute_url(self): def clean(self): # Disallow host masks - if self.prefix.version == 4 and self.prefix.prefixlen == 32: - raise ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 addresses " - "instead.") - elif self.prefix.version == 6 and self.prefix.prefixlen == 128: - raise ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 addresses " - "instead.") + if self.prefix: + if self.prefix.version == 4 and self.prefix.prefixlen == 32: + raise ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 addresses " + "instead.") + elif self.prefix.version == 6 and self.prefix.prefixlen == 128: + raise ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 addresses " + "instead.") def save(self, *args, **kwargs): if self.prefix: From 474b19d927ba16d98b132c329d4e3a0cece119ef Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Aug 2016 14:57:14 -0400 Subject: [PATCH 09/22] We have a logo! --- README.md | 2 +- docs/netbox_logo.png | Bin 0 -> 3903 bytes netbox/project-static/css/base.css | 3 +++ netbox/project-static/img/netbox.ico | Bin 0 -> 1174 bytes netbox/project-static/img/netbox_logo.png | Bin 0 -> 2257 bytes netbox/templates/_base.html | 5 ++++- 6 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 docs/netbox_logo.png create mode 100644 netbox/project-static/img/netbox.ico create mode 100644 netbox/project-static/img/netbox_logo.png diff --git a/README.md b/README.md index 2cf8f0193d4..28c73b2ccd4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NetBox +![NetBox](docs/netbox_logo.png "NetBox logo") NetBox is an IP address management (IPAM) and data center infrastructure management (DCIM) tool. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers. diff --git a/docs/netbox_logo.png b/docs/netbox_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c6e0a58e6259f957f2427afe1035e8e37a6d713c GIT binary patch literal 3903 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 N002ovPDHLkV1j7+9Wwv` literal 0 HcmV?d00001 diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 13f4e14554a..25f39946142 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -21,6 +21,9 @@ body { margin: 0 auto -61px; /* the bottom margin is the negative value of the footer's height */ padding-bottom: 30px; } +.navbar-brand { + padding: 12px 15px 8px; +} .footer, .push { height: 60px; /* .push must be the same height as .footer */ } diff --git a/netbox/project-static/img/netbox.ico b/netbox/project-static/img/netbox.ico new file mode 100644 index 0000000000000000000000000000000000000000..bb1051549be3493bd24ca9e9015214ca5766acb7 GIT binary patch literal 1174 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 literal 0 HcmV?d00001 diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 570f73a4d8a..b3b52741b67 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -8,6 +8,7 @@ +