Browse Source

Merge branch 'develop' into api2

Conflicts:
	netbox/templates/users/_user.html
	netbox/users/urls.py
Jeremy Stretch 8 years ago
parent
commit
04aedcc056

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+*.sh text eol=lf

+ 25 - 14
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):
 
@@ -733,7 +741,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(
@@ -1610,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)
 
 
 #

+ 14 - 11
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([
@@ -1059,8 +1060,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):

+ 4 - 1
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

+ 1 - 1
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/$', APIRootView.as_view()),

+ 2 - 2
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

+ 1 - 1
netbox/templates/_base.html

@@ -245,7 +245,7 @@
                         {% if request.user.is_staff %}
                             <li><a href="{% url 'admin:index' %}"><i class="fa fa-cogs" aria-hidden="true"></i> Admin</a></li>
                         {% endif %}
-                        <li><a href="{% url 'users:profile' %}"><i class="fa fa-user" aria-hidden="true"></i> {{ request.user }}</a></li>
+                        <li><a href="{% url 'user:profile' %}"><i class="fa fa-user" aria-hidden="true"></i> {{ request.user }}</a></li>
                         <li><a href="{% url 'logout' %}"><i class="fa fa-sign-out" aria-hidden="true"></i> Log out</a></li>
                     {% else %}
                         <li><a href="{% url 'login' %}?next={{ request.path }}"><i class="fa fa-sign-in" aria-hidden="true"></i> Log in</a></li>

+ 4 - 2
netbox/templates/dcim/device.html

@@ -43,8 +43,10 @@
                     <td>
                         {% if device.parent_bay %}
                             {% with device.parent_bay.device as parent %}
-                                <span>U{{ parent.position }} / {{ parent.get_face_display }}
-                                (<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> - {{ device.parent_bay.name }})</span>
+                                <a href="{{ parent.get_absolute_url }}">{{ parent }}</a> <i class="fa fa-angle-right"></i> {{ device.parent_bay.name }}
+                                {% if parent.position %}
+                                    (U{{ parent.position }} / {{ parent.get_face_display }})
+                                {% endif %}
                             {% endwith %}
                         {% elif device.rack and device.position %}
                             <span>U{{ device.position }} / {{ device.get_face_display }}</span>

+ 5 - 5
netbox/templates/users/_user.html

@@ -10,19 +10,19 @@
     <div class="col-sm-3 col-md-2 col-md-offset-2">
         <ul class="nav nav-pills nav-stacked">
             <li{% ifequal active_tab "profile" %} class="active"{% endifequal %}>
-                <a href="{% url 'users:profile' %}">Profile</a>
+                <a href="{% url 'user:profile' %}">Profile</a>
             </li>
             <li{% ifequal active_tab "change_password" %} class="active"{% endifequal %}>
-                <a href="{% url 'users:change_password' %}">Change Password</a>
+                <a href="{% url 'user:change_password' %}">Change Password</a>
             </li>
             <li{% ifequal active_tab "api_tokens" %} class="active"{% endifequal %}>
-                <a href="{% url 'users:token_list' %}">API Tokens</a>
+                <a href="{% url 'user:token_list' %}">API Tokens</a>
             </li>
             <li{% ifequal active_tab "userkey" %} class="active"{% endifequal %}>
-                <a href="{% url 'users:userkey' %}">User Key</a>
+                <a href="{% url 'user:userkey' %}">User Key</a>
             </li>
             <li{% ifequal active_tab "recent_activity" %} class="active"{% endifequal %}>
-                <a href="{% url 'users:recent_activity' %}">Recent Activity</a>
+                <a href="{% url 'user:recent_activity' %}">Recent Activity</a>
             </li>
         </ul>
     </div>

+ 1 - 1
netbox/templates/users/change_password.html

@@ -24,7 +24,7 @@
         </div>
         <div class="text-right">
             <button type="submit" name="_update" class="btn btn-primary">Update</button>
-            <a href="{% url 'users:profile' %}" class="btn btn-default">Cancel</a>
+            <a href="{% url 'user:profile' %}" class="btn btn-default">Cancel</a>
         </div>
     </form>
 {% endblock %}

+ 2 - 2
netbox/templates/users/userkey.html

@@ -15,7 +15,7 @@
         <p>Your public key is below.</p>
         <pre>{{ userkey.public_key }}</pre>
         <div class="pull-right">
-            <a href="{% url 'users:userkey_edit' %}" class="btn btn-warning">
+            <a href="{% url 'user:userkey_edit' %}" class="btn btn-warning">
                 <span class="fa fa-pencil" aria-hidden="true"></span>
                 Edit user key
             </a>
@@ -24,7 +24,7 @@
     {% else %}
         <p>You don't have a user key on file.</p>
         <p>
-            <a href="{% url 'users:userkey_edit' %}" class="btn btn-primary">
+            <a href="{% url 'user:userkey_edit' %}" class="btn btn-primary">
                 <span class="fa fa-plus" aria-hidden="true"></span>
                 Create a User Key
             </a>

+ 1 - 1
netbox/templates/users/userkey_edit.html

@@ -23,7 +23,7 @@
                 </div>
                 <div class="col-sm-6 col-md-6 text-right">
                     <button type="submit" name="_update" class="btn btn-primary">Save</button>
-                    <a href="{% url 'users:userkey' %}" class="btn btn-default">Cancel</a>
+                    <a href="{% url 'user:userkey' %}" class="btn btn-default">Cancel</a>
                 </div>
             </div>
         </div>

+ 8 - 8
netbox/users/urls.py

@@ -7,13 +7,13 @@ urlpatterns = [
 
     # User profiles
     url(r'^profile/$', views.profile, name='profile'),
-    url(r'^profile/password/$', views.change_password, name='change_password'),
-    url(r'^profile/api-tokens/$', views.TokenListView.as_view(), name='token_list'),
-    url(r'^profile/api-tokens/add/$', views.TokenEditView.as_view(), name='token_add'),
-    url(r'^profile/api-tokens/(?P<pk>\d+)/edit/$', views.TokenEditView.as_view(), name='token_edit'),
-    url(r'^profile/api-tokens/(?P<pk>\d+)/delete/$', views.TokenDeleteView.as_view(), name='token_delete'),
-    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'^api-tokens/$', views.TokenListView.as_view(), name='token_list'),
+    url(r'^api-tokens/add/$', views.TokenEditView.as_view(), name='token_add'),
+    url(r'^api-tokens/(?P<pk>\d+)/edit/$', views.TokenEditView.as_view(), name='token_edit'),
+    url(r'^api-tokens/(?P<pk>\d+)/delete/$', views.TokenDeleteView.as_view(), name='token_delete'),
+    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'),
 
 ]

+ 2 - 2
netbox/users/views.py

@@ -72,7 +72,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)
@@ -112,7 +112,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)