From 70a05b42806e06687b4bed3f14c70d77aeaaf3f1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Mar 2017 14:45:23 -0500 Subject: [PATCH 01/11] 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 51a5fed7f2b..856fee9ff12 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ except ImportError: "the documentation.") -VERSION = '1.9.1' +VERSION = '1.9.2-dev' # Import local configuration for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: From d68b34cefeeff72576478eb0920956064e8d8559 Mon Sep 17 00:00:00 2001 From: Peter Russell Date: Wed, 8 Mar 2017 13:30:20 +0000 Subject: [PATCH 02/11] Force Unix line endings on shell scripts When following the quickstart docker instructions on Windows (using Docker for Windows), an error is encountered when running the netbox container. This is caused by git converting the line endings of the docker-entrypoint.sh script to Windows format, which are then copied into the container image. By setting .gitattributes, we force LF rather than CRLF line endings on shell scripts on Windows. Other files are left as is. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..dfdb8b771ce --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf From 87ff433ef8f68d9f111569ee3def40a324d17b88 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 10:06:32 -0400 Subject: [PATCH 03/11] Fixes #956: Correct bug affecting unnamed rackless devices --- netbox/dcim/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 03d5b25874d..b7a0b4492f2 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1059,8 +1059,10 @@ class Device(CreatedUpdatedModel, CustomFieldModel): return self.name elif self.position: return u"{} ({} U{})".format(self.device_type, self.rack.name, self.position) - else: + elif self.rack: return u"{} ({})".format(self.device_type, self.rack.name) + else: + return u"{} ({})".format(self.device_type, self.site.name) @property def identifier(self): From 6a2a2d5d11708af275f605e6a0a78d7e55fca845 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 10:13:04 -0400 Subject: [PATCH 04/11] Fixes #957: Correct device site filter count to include unracked devices --- netbox/dcim/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index dc2b296ee62..9bfe8e84daf 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -733,7 +733,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Device q = forms.CharField(required=False, label='Search') site = FilterChoiceField( - queryset=Site.objects.annotate(filter_count=Count('racks__devices')), + queryset=Site.objects.annotate(filter_count=Count('devices')), to_field_name='slug', ) rack_group_id = FilterChoiceField( From f661c233be6144a943aed1310367bf38207373dc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:18:33 -0400 Subject: [PATCH 05/11] Fixes #950: Fix site_id error on child device import --- netbox/dcim/forms.py | 16 ++++++++++++---- netbox/dcim/models.py | 21 +++++++++++---------- netbox/dcim/views.py | 5 ++++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 9bfe8e84daf..abe98e51ad0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -680,13 +680,21 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm): class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm): - parent = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name', required=False, - error_messages={'invalid_choice': 'Parent device not found.'}) + parent = FlexibleModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + required=False, + error_messages={ + 'invalid_choice': 'Parent device not found.' + } + ) device_bay_name = forms.CharField(required=False) class Meta(BaseDeviceFromCSVForm.Meta): - fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', - 'parent', 'device_bay_name'] + fields = [ + 'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'parent', + 'device_bay_name', + ] def clean(self): diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index b7a0b4492f2..73678ae1c02 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -975,25 +975,26 @@ class Device(CreatedUpdatedModel, CustomFieldModel): # Child devices cannot be assigned to a rack face/unit if self.device_type.is_child_device and self.face is not None: raise ValidationError({ - 'face': "Child device types cannot be assigned to a rack face. This is an attribute of the parent " - "device." + 'face': "Child device types cannot be assigned to a rack face. This is an attribute of the " + "parent device." }) if self.device_type.is_child_device and self.position: raise ValidationError({ - 'position': "Child device types cannot be assigned to a rack position. This is an attribute of the " - "parent device." + 'position': "Child device types cannot be assigned to a rack position. This is an attribute of " + "the parent device." }) # Validate rack space rack_face = self.face if not self.device_type.is_full_depth else None exclude_list = [self.pk] if self.pk else [] try: - available_units = self.rack.get_available_units(u_height=self.device_type.u_height, rack_face=rack_face, - exclude=exclude_list) + available_units = self.rack.get_available_units( + u_height=self.device_type.u_height, rack_face=rack_face, exclude=exclude_list + ) if self.position and self.position not in available_units: raise ValidationError({ - 'position': "U{} is already occupied or does not have sufficient space to accommodate a(n) {} " - "({}U).".format(self.position, self.device_type, self.device_type.u_height) + 'position': "U{} is already occupied or does not have sufficient space to accommodate a(n) " + "{} ({}U).".format(self.position, self.device_type, self.device_type.u_height) }) except Rack.DoesNotExist: pass @@ -1034,8 +1035,8 @@ class Device(CreatedUpdatedModel, CustomFieldModel): self.device_type.device_bay_templates.all()] ) - # Update Rack assignment for any child Devices - Device.objects.filter(parent_bay__device=self).update(rack=self.rack) + # Update Site and Rack assignment for any child Devices + Device.objects.filter(parent_bay__device=self).update(site=self.site, rack=self.rack) def to_csv(self): return csv_format([ diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 73de16e5336..cb307324e24 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -763,9 +763,12 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView): default_return_url = 'dcim:device_list' def save_obj(self, obj): - # Inherent rack from parent device + + # Inherit site and rack from parent device + obj.site = obj.parent_bay.device.site obj.rack = obj.parent_bay.device.rack obj.save() + # Save the reverse relation device_bay = obj.parent_bay device_bay.installed_device = obj From cd97b2fb96a5c0d70a9648d2f2d7bb8d4375ec31 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:25:06 -0400 Subject: [PATCH 06/11] Fix parent device position display --- netbox/templates/dcim/device.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 081397774a9..d38f60cb397 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -43,8 +43,10 @@ {% if device.parent_bay %} {% with device.parent_bay.device as parent %} - U{{ parent.position }} / {{ parent.get_face_display }} - ({{ parent }} - {{ device.parent_bay.name }}) + {{ parent }} {{ device.parent_bay.name }} + {% if parent.position %} + (U{{ parent.position }} / {{ parent.get_face_display }}) + {% endif %} {% endwith %} {% elif device.rack and device.position %} U{{ device.position }} / {{ device.get_face_display }} From f3fd82a24a0c7ccb68455d810b4fa4ddc0d9322b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:31:28 -0400 Subject: [PATCH 07/11] Allow assigning child devices to rackless parents --- netbox/dcim/forms.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index abe98e51ad0..c79f65d538a 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1618,20 +1618,23 @@ class DeviceBayCreateForm(DeviceComponentForm): class PopulateDeviceBayForm(BootstrapMixin, forms.Form): - installed_device = forms.ModelChoiceField(queryset=Device.objects.all(), label='Child Device', - help_text="Child devices must first be created within the rack occupied " - "by the parent device. Then they can be assigned to a bay.") + installed_device = forms.ModelChoiceField( + queryset=Device.objects.all(), + label='Child Device', + help_text="Child devices must first be created and assigned to the site/rack of the parent device." + ) def __init__(self, device_bay, *args, **kwargs): super(PopulateDeviceBayForm, self).__init__(*args, **kwargs) - children_queryset = Device.objects.filter(rack=device_bay.device.rack, - parent_bay__isnull=True, - device_type__u_height=0, - device_type__subdevice_role=SUBDEVICE_ROLE_CHILD)\ - .exclude(pk=device_bay.device.pk) - self.fields['installed_device'].queryset = children_queryset + self.fields['installed_device'].queryset = Device.objects.filter( + site=device_bay.device.site, + rack=device_bay.device.rack, + parent_bay__isnull=True, + device_type__u_height=0, + device_type__subdevice_role=SUBDEVICE_ROLE_CHILD + ).exclude(pk=device_bay.device.pk) # From d9f1bcbf153d9e4a781d12cc166091ce453189dd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Mar 2017 12:36:44 -0400 Subject: [PATCH 08/11] Renamed user URL namespace --- netbox/netbox/urls.py | 2 +- netbox/secrets/decorators.py | 4 ++-- netbox/templates/_base.html | 2 +- netbox/templates/users/_user.html | 8 ++++---- netbox/templates/users/change_password.html | 2 +- netbox/templates/users/userkey.html | 4 ++-- netbox/templates/users/userkey_edit.html | 2 +- netbox/users/urls.py | 8 ++++---- netbox/users/views.py | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index bbfdee58ddb..9b1f81dad73 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -23,7 +23,7 @@ _patterns = [ url(r'^ipam/', include('ipam.urls', namespace='ipam')), url(r'^secrets/', include('secrets.urls', namespace='secrets')), url(r'^tenancy/', include('tenancy.urls', namespace='tenancy')), - url(r'^profile/', include('users.urls', namespace='users')), + url(r'^user/', include('users.urls', namespace='user')), # API url(r'^api/circuits/', include('circuits.api.urls', namespace='circuits-api')), diff --git a/netbox/secrets/decorators.py b/netbox/secrets/decorators.py index 41af204da18..683805124ca 100644 --- a/netbox/secrets/decorators.py +++ b/netbox/secrets/decorators.py @@ -15,10 +15,10 @@ def userkey_required(): uk = UserKey.objects.get(user=request.user) except UserKey.DoesNotExist: messages.warning(request, u"This operation requires an active user key, but you don't have one.") - return redirect('users:userkey') + return redirect('user:userkey') if not uk.is_active(): messages.warning(request, u"This operation is not available. Your user key has not been activated.") - return redirect('users:userkey') + return redirect('user:userkey') return view(request, *args, **kwargs) return wrapped_view return _decorator diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index d1090a5fbbc..6f0dfced6fe 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -245,7 +245,7 @@ {% if request.user.is_staff %}
  • Admin
  • {% endif %} -
  • {{ request.user }}
  • +
  • {{ request.user }}
  • Log out
  • {% else %}
  • Log in
  • diff --git a/netbox/templates/users/_user.html b/netbox/templates/users/_user.html index 01978a59e49..a2e18daed4c 100644 --- a/netbox/templates/users/_user.html +++ b/netbox/templates/users/_user.html @@ -9,10 +9,10 @@
    diff --git a/netbox/templates/users/change_password.html b/netbox/templates/users/change_password.html index 80528c98110..700bf682dcd 100644 --- a/netbox/templates/users/change_password.html +++ b/netbox/templates/users/change_password.html @@ -24,7 +24,7 @@
    - Cancel + Cancel
    {% endblock %} diff --git a/netbox/templates/users/userkey.html b/netbox/templates/users/userkey.html index 08c519c2ddc..df5e55be9c7 100644 --- a/netbox/templates/users/userkey.html +++ b/netbox/templates/users/userkey.html @@ -15,7 +15,7 @@

    Your public key is below.

    {{ userkey.public_key }}
    - + Edit user key @@ -24,7 +24,7 @@ {% else %}

    You don't have a user key on file.

    - + Create a User Key diff --git a/netbox/templates/users/userkey_edit.html b/netbox/templates/users/userkey_edit.html index 550ade4c227..45bac193870 100644 --- a/netbox/templates/users/userkey_edit.html +++ b/netbox/templates/users/userkey_edit.html @@ -23,7 +23,7 @@

    - Cancel + Cancel
    diff --git a/netbox/users/urls.py b/netbox/users/urls.py index d33d14bebe1..a9f7a1d7f9d 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -7,9 +7,9 @@ urlpatterns = [ # User profiles url(r'^profile/$', views.profile, name='profile'), - url(r'^profile/password/$', views.change_password, name='change_password'), - url(r'^profile/user-key/$', views.userkey, name='userkey'), - url(r'^profile/user-key/edit/$', views.userkey_edit, name='userkey_edit'), - url(r'^profile/recent-activity/$', views.recent_activity, name='recent_activity'), + url(r'^password/$', views.change_password, name='change_password'), + url(r'^user-key/$', views.userkey, name='userkey'), + url(r'^user-key/edit/$', views.userkey_edit, name='userkey_edit'), + url(r'^recent-activity/$', views.recent_activity, name='recent_activity'), ] diff --git a/netbox/users/views.py b/netbox/users/views.py index 95f5f3bfc8a..12a71469652 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -69,7 +69,7 @@ def change_password(request): form.save() update_session_auth_hash(request, form.user) messages.success(request, u"Your password has been changed successfully.") - return redirect('users:profile') + return redirect('user:profile') else: form = PasswordChangeForm(user=request.user) @@ -109,7 +109,7 @@ def userkey_edit(request): uk.user = request.user uk.save() messages.success(request, u"Your user key has been saved.") - return redirect('users:userkey') + return redirect('user:userkey') else: form = UserKeyForm(instance=userkey) From 33c5ea1f4edf33951d29159db1f0c28535a17eaf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Mar 2017 15:06:34 -0400 Subject: [PATCH 09/11] Fixes #963: Fix bug in IPv6 address range expansion --- netbox/utilities/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 76ce1796c93..dd6235f453d 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -57,7 +57,7 @@ def parse_numeric_range(string, base=10): begin, end = dash_range.split('-') except ValueError: begin, end = dash_range, dash_range - begin, end = int(begin.strip()), int(end.strip(), base=base) + 1 + begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1 values.extend(range(begin, end)) return list(set(values)) From f4784412dea6a2003b4a139dae35a9cbede7656d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Mar 2017 15:22:08 -0400 Subject: [PATCH 10/11] Fixes #964: Fix bug when bulk editing/deleting filtered set of objects --- netbox/utilities/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 4d6ec333250..f38d9a0ab65 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -434,7 +434,7 @@ class BulkEditView(View): # Are we editing *all* objects in the queryset or just a selected subset? if request.POST.get('_all') and self.filter is not None: - pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk'))] + pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk')).qs] else: pk_list = [int(pk) for pk in request.POST.getlist('pk')] @@ -572,7 +572,7 @@ class BulkDeleteView(View): # Are we deleting *all* objects in the queryset or just a selected subset? if request.POST.get('_all') and self.filter is not None: - pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk'))] + pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk')).qs] else: pk_list = [int(pk) for pk in request.POST.getlist('pk')] From b22c6a0078ba59ab4ba11888e69726a749281497 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Mar 2017 17:07:04 -0400 Subject: [PATCH 11/11] Release v1.9.2 --- 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 856fee9ff12..66065e9e790 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ except ImportError: "the documentation.") -VERSION = '1.9.2-dev' +VERSION = '1.9.2' # Import local configuration for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: