Просмотр исходного кода

Merge pull request #459 from digitalocean/develop

Release v1.5.1
Jeremy Stretch 9 лет назад
Родитель
Сommit
6a48b310d2

+ 2 - 1
docs/installation/netbox.md

@@ -10,9 +10,10 @@ NetBox requires following system dependencies:
 * libffi-dev
 * graphviz
 * libpq-dev
+* libssl-dev
 
 ```
-# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev
+# sudo apt-get install -y python2.7 python-dev python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
 ```
 
 You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.

+ 2 - 1
netbox/dcim/admin.py

@@ -183,7 +183,8 @@ class DeviceAdmin(admin.ModelAdmin):
         DeviceBayAdmin,
         ModuleAdmin,
     ]
-    list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'serial']
+    list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
+                    'serial']
     list_filter = ['device_role']
 
     def get_queryset(self, request):

+ 3 - 2
netbox/dcim/api/serializers.py

@@ -250,8 +250,9 @@ class DeviceSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Device
-        fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'rack',
-                  'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
+        fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',
+                  'asset_tag', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
+                  'primary_ip6', 'comments']
 
     def get_parent_device(self, obj):
         try:

+ 6 - 5
netbox/dcim/filters.py

@@ -239,15 +239,16 @@ class DeviceFilter(django_filters.FilterSet):
 
     class Meta:
         model = Device
-        fields = ['q', 'name', 'site_id', 'site', 'rack_id', 'role_id', 'role', 'device_type_id', 'manufacturer_id',
-                  'manufacturer', 'model', 'platform_id', 'platform', 'status', 'is_console_server', 'is_pdu',
-                  'is_network_device']
+        fields = ['q', 'name', 'serial', 'asset_tag', 'site_id', 'site', 'rack_id', 'role_id', 'role', 'device_type_id',
+                  'manufacturer_id', 'manufacturer', 'model', 'platform_id', 'platform', 'status', 'is_console_server',
+                  'is_pdu', 'is_network_device']
 
     def search(self, queryset, value):
         return queryset.filter(
             Q(name__icontains=value) |
-            Q(serial__icontains=value) |
-            Q(modules__serial__icontains=value) |
+            Q(serial__icontains=value.strip()) |
+            Q(modules__serial__icontains=value.strip()) |
+            Q(asset_tag=value.strip()) |
             Q(comments__icontains=value)
         ).distinct()
 

+ 6 - 6
netbox/dcim/forms.py

@@ -425,8 +425,8 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = Device
-        fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
-                  'platform', 'primary_ip4', 'primary_ip6', 'comments']
+        fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position',
+                  'face', 'status', 'platform', 'primary_ip4', 'primary_ip6', 'comments']
         help_texts = {
             'device_role': "The function this device serves",
             'serial': "Chassis serial number",
@@ -546,8 +546,8 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
     face = forms.CharField(required=False)
 
     class Meta(BaseDeviceFromCSVForm.Meta):
-        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'site',
-                  'rack_name', 'position', 'face']
+        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag',
+                  'site', 'rack_name', 'position', 'face']
 
     def clean(self):
 
@@ -582,8 +582,8 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
     device_bay_name = forms.CharField(required=False)
 
     class Meta(BaseDeviceFromCSVForm.Meta):
-        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
-                  'device_bay_name']
+        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag',
+                  'parent', 'device_bay_name']
 
     def clean(self):
 

+ 21 - 0
netbox/dcim/migrations/0018_device_add_asset_tag.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-11 15:42
+from __future__ import unicode_literals
+
+from django.db import migrations
+import utilities.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0017_rack_add_role'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='device',
+            name='asset_tag',
+            field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'),
+        ),
+    ]

+ 4 - 1
netbox/dcim/models.py

@@ -373,7 +373,7 @@ class Rack(CreatedUpdatedModel):
             self.tenant.name if self.tenant else '',
             self.role.name if self.role else '',
             self.get_type_display() if self.type else '',
-            self.width,
+            str(self.width),
             str(self.u_height),
         ])
 
@@ -737,6 +737,8 @@ class Device(CreatedUpdatedModel):
     platform = models.ForeignKey('Platform', related_name='devices', blank=True, null=True, on_delete=models.SET_NULL)
     name = NullableCharField(max_length=50, blank=True, null=True, unique=True)
     serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number')
+    asset_tag = NullableCharField(max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag',
+                                  help_text='A unique tag used to identify this device')
     rack = models.ForeignKey('Rack', related_name='devices', on_delete=models.PROTECT)
     position = models.PositiveSmallIntegerField(blank=True, null=True, validators=[MinValueValidator(1)],
                                                 verbose_name='Position (U)',
@@ -832,6 +834,7 @@ class Device(CreatedUpdatedModel):
             self.device_type.model,
             self.platform.name if self.platform else '',
             self.serial,
+            self.asset_tag if self.asset_tag else '',
             self.rack.site.name,
             self.rack.name,
             str(self.position) if self.position else '',

+ 21 - 5
netbox/dcim/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ColorColumn, ToggleColumn
+from utilities.tables import BaseTable, ToggleColumn
 
 from .models import (
     ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
@@ -10,6 +10,10 @@ from .models import (
 )
 
 
+COLOR_LABEL = """
+<label class="label {{ record.color }}">{{ record }}</label>
+"""
+
 DEVICE_LINK = """
 <a href="{% url 'dcim:device' pk=record.pk %}">
     {{ record.name|default:'<span class="label label-info">Unnamed device</span>' }}
@@ -28,6 +32,14 @@ RACKROLE_ACTIONS = """
 {% endif %}
 """
 
+RACK_ROLE = """
+{% if record.role %}
+    <label class="label {{ record.role.color }}">{{ value }}</label>
+{% else %}
+    &mdash;
+{% endif %}
+"""
+
 DEVICEROLE_ACTIONS = """
 {% if perms.dcim.change_devicerole %}
     <a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
@@ -46,6 +58,10 @@ PLATFORM_ACTIONS = """
 {% endif %}
 """
 
+DEVICE_ROLE = """
+<label class="label {{ record.device_role.color }}">{{ value }}</label>
+"""
+
 STATUS_ICON = """
 {% if record.status %}
     <span class="glyphicon glyphicon-ok-sign text-success" title="Active" aria-hidden="true"></span>
@@ -108,7 +124,7 @@ class RackRoleTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn(verbose_name='Name')
     rack_count = tables.Column(verbose_name='Racks')
-    color = ColorColumn(verbose_name='Color')
+    color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
     slug = tables.Column(verbose_name='Slug')
     actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
                                     verbose_name='')
@@ -129,7 +145,7 @@ class RackTable(BaseTable):
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
     facility_id = tables.Column(verbose_name='Facility ID')
     tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    role = tables.Column(verbose_name='Role')
+    role = tables.TemplateColumn(RACK_ROLE, verbose_name='Role')
     u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
     devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
     u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
@@ -258,7 +274,7 @@ class DeviceRoleTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn(verbose_name='Name')
     device_count = tables.Column(verbose_name='Devices')
-    color = ColorColumn(verbose_name='Color')
+    color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
     slug = tables.Column(verbose_name='Slug')
     actions = tables.TemplateColumn(template_code=DEVICEROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
                                     verbose_name='')
@@ -295,7 +311,7 @@ class DeviceTable(BaseTable):
     tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
     site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
-    device_role = tables.Column(verbose_name='Role')
+    device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
     device_type = tables.Column(verbose_name='Type')
     primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
                                        template_code="{{ record.primary_ip.address.ip }}")

+ 2 - 0
netbox/dcim/tests/test_apis.py

@@ -327,6 +327,7 @@ class DeviceTest(APITestCase):
         'tenant',
         'platform',
         'serial',
+        'asset_tag',
         'rack',
         'position',
         'face',
@@ -370,6 +371,7 @@ class DeviceTest(APITestCase):
     def test_get_list_flat(self, endpoint='/api/dcim/devices/?format=json_flat'):
 
         flat_fields = [
+            'asset_tag',
             'comments',
             'device_role_id',
             'device_role_name',

+ 1 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ except ImportError:
                                "the documentation.")
 
 
-VERSION = '1.5.0'
+VERSION = '1.5.1'
 
 # Import local configuration
 for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:

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

@@ -60,7 +60,7 @@
                     </td>
                 </tr>
                 <tr>
-                    <td>Serial</td>
+                    <td>Serial Number</td>
                     <td>
                         {% if device.serial %}
                             <span>{{ device.serial }}</span>
@@ -69,6 +69,16 @@
                         {% endif %}
                     </td>
                 </tr>
+                <tr>
+                    <td>Asset Tag</td>
+                    <td>
+                        {% if device.asset_tag %}
+                            <span>{{ device.asset_tag }}</span>
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
                 <tr>
                     <td>Created</td>
                     <td>{{ device.created }}</td>
@@ -87,7 +97,7 @@
                 <tr>
                     <td>Role</td>
                     <td>
-                        <a href="{% url 'dcim:device_list' %}?role={{ device.device_role.slug }}">{{ device.device_role }}</a>
+                        <a href="{{ device.device_role.get_absolute_url }}">{{ device.device_role }}</a>
                     </td>
                 </tr>
                 <tr>

+ 1 - 0
netbox/templates/dcim/device_edit.html

@@ -16,6 +16,7 @@
             {% render_field form.manufacturer %}
             {% render_field form.device_type %}
             {% render_field form.serial %}
+            {% render_field form.asset_tag %}
         </div>
     </div>
     <div class="panel panel-default">

+ 8 - 3
netbox/templates/dcim/device_import.html

@@ -57,10 +57,15 @@
 					<td>Juniper Junos</td>
 				</tr>
 				<tr>
-					<td>Serial</td>
-					<td>Serial number (optional)</td>
+					<td>Serial number</td>
+					<td>Physical serial number (optional)</td>
 					<td>CAB00577291</td>
 				</tr>
+				<tr>
+					<td>Asset tag</td>
+					<td>Unique alphanumeric tag (optional)</td>
+					<td>ABC123456</td>
+				</tr>
 				<tr>
 					<td>Site</td>
 					<td>Site name</td>
@@ -84,7 +89,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
+		<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,ABC123456,Ashburn-VA,R101,21,Rear</pre>
 	</div>
 </div>
 {% endblock %}

+ 8 - 3
netbox/templates/dcim/device_import_child.html

@@ -57,10 +57,15 @@
 					<td>Linux</td>
 				</tr>
 				<tr>
-					<td>Serial</td>
-					<td>Serial number (optional)</td>
+					<td>Serial number</td>
+					<td>Physical serial number (optional)</td>
 					<td>CAB00577291</td>
 				</tr>
+				<tr>
+					<td>Asset tag</td>
+					<td>Unique alphanumeric tag (optional)</td>
+					<td>ABC123456</td>
+				</tr>
 				<tr>
 					<td>Parent device</td>
 					<td>Parent device</td>
@@ -74,7 +79,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,Server101,Slot4</pre>
+		<pre>Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,ABC123456,Server101,Slot4</pre>
 	</div>
 </div>
 {% endblock %}

+ 17 - 1
netbox/templates/dcim/device_inventory.html

@@ -17,7 +17,23 @@
                 </tr>
                 <tr>
                     <td>Serial Number</td>
-                    <td>{{ device.serial }}</td>
+                    <td>
+                        {% if device.serial %}
+                            <span>{{ device.serial }}</span>
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Asset Tag</td>
+                    <td>
+                        {% if device.asset_tag %}
+                            <span>{{ device.asset_tag }}</span>
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
                 </tr>
             </table>
         </div>

+ 10 - 0
netbox/templates/dcim/rack.html

@@ -96,6 +96,16 @@
                         {% endif %}
                     </td>
                 </tr>
+                <tr>
+                    <td>Role</td>
+                    <td>
+                        {% if rack.role %}
+                            <a href="{{ rack.role.get_absolute_url }}">{{ rack.role }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
                 <tr>
                     <td>Type</td>
                     <td>

+ 1 - 0
netbox/templates/dcim/rack_edit.html

@@ -10,6 +10,7 @@
             {% render_field form.name %}
             {% render_field form.facility_id %}
             {% render_field form.tenant %}
+            {% render_field form.role %}
             {% render_field form.type %}
             {% render_field form.width %}
             {% render_field form.u_height %}

+ 20 - 6
netbox/templates/home.html

@@ -3,9 +3,9 @@
 
 {% block content %}
 <div class="row home-search" style="padding: 15px 0px 20px">
-	<div class="col-md-4">
+	<div class="col-md-3">
 		<form action="{% url 'dcim:device_list' %}" method="get">
-			<div class="input-group input-group-lg">
+			<div class="input-group">
 				<input type="text" name="q" placeholder="Search devices" class="form-control" />
 				<span class="input-group-btn">
 					<button type="submit" class="btn btn-primary">
@@ -17,9 +17,9 @@
 		</form>
 		<p></p>
 	</div>
-	<div class="col-md-4">
+	<div class="col-md-3">
 		<form action="{% url 'ipam:prefix_list' %}" method="get">
-			<div class="input-group input-group-lg">
+			<div class="input-group">
 				<input type="text" name="q" placeholder="Search prefixes" class="form-control" />
 				<span class="input-group-btn">
 					<button type="submit" class="btn btn-primary">
@@ -31,9 +31,23 @@
 		</form>
 		<p></p>
 	</div>
-	<div class="col-md-4">
+	<div class="col-md-3">
+		<form action="{% url 'ipam:ipaddress_list' %}" method="get">
+			<div class="input-group">
+				<input type="text" name="q" placeholder="Search IPs" class="form-control" />
+				<span class="input-group-btn">
+					<button type="submit" class="btn btn-primary">
+						<span class="fa fa-search" aria-hidden="true"></span>
+						IPs
+					</button>
+				</span>
+			</div>
+		</form>
+		<p></p>
+	</div>
+	<div class="col-md-3">
 		<form action="{% url 'circuits:circuit_list' %}" method="get">
-			<div class="input-group input-group-lg">
+			<div class="input-group">
 				<input type="text" name="q" placeholder="Search circuits" class="form-control" />
 				<span class="input-group-btn">
 					<button type="submit" class="btn btn-primary">

+ 0 - 7
netbox/utilities/tables.py

@@ -28,10 +28,3 @@ class ToggleColumn(tables.CheckBoxColumn):
     @property
     def header(self):
         return mark_safe('<input type="checkbox" name="_all" title="Select all" />')
-
-
-class ColorColumn(tables.Column):
-
-    def render(self, record):
-        html = '<label class="label {}">{}</label>'.format(record.color, record.get_color_display())
-        return mark_safe(html)