From 40ea662ade3f1858da8d6541fd31e5a8a9f42a64 Mon Sep 17 00:00:00 2001 From: Peter Russell Date: Wed, 8 Mar 2017 13:30:20 +0000 Subject: [PATCH 1/7] 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 000000000..dfdb8b771 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf From c6c3c46ed699abbbd446f1900e4677f2e7ec7340 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 10:06:32 -0400 Subject: [PATCH 2/7] 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 03d5b2587..b7a0b4492 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 487b6121cbf45df123127001466affd9d126d043 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 10:13:04 -0400 Subject: [PATCH 3/7] 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 dc2b296ee..9bfe8e84d 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 50e790c2d84ad51ceced691cc2f91ee56eca79e1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:18:33 -0400 Subject: [PATCH 4/7] 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 9bfe8e84d..abe98e51a 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 b7a0b4492..73678ae1c 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 73de16e53..cb307324e 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 f0c251c76e2377a976bd9d1588a53326c118d0ee Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:25:06 -0400 Subject: [PATCH 5/7] 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 081397774..d38f60cb3 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 b3a95bbab56929e7cdf5a61371d91f621aa94c82 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Mar 2017 11:31:28 -0400 Subject: [PATCH 6/7] 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 abe98e51a..c79f65d53 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 cfb1287cd5094b39a63d3e1c5b44f4698ee16529 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 14 Mar 2017 12:36:44 -0400 Subject: [PATCH 7/7] 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 bbfdee58d..9b1f81dad 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 41af204da..683805124 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 d1090a5fb..6f0dfced6 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 01978a59e..a2e18daed 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 80528c981..700bf682d 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 08c519c2d..df5e55be9 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 550ade4c2..45bac1938 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 d33d14beb..a9f7a1d7f 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 95f5f3bfc..12a714696 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)