From d2157a3423f9a620610b778b08bf1d5ecadf5a38 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 20 Feb 2020 12:11:59 -0500 Subject: [PATCH] Add front/rear images for device types; include in rack elevations --- netbox/dcim/forms.py | 4 +- .../dcim/migrations/0098_devicetype_images.py | 23 +++++++++ netbox/dcim/models/__init__.py | 47 +++++++++++++++++++ netbox/project-static/css/rack_elevation.css | 4 +- netbox/templates/dcim/devicetype.html | 24 ++++++++++ netbox/templates/dcim/devicetype_edit.html | 7 +++ netbox/templates/dcim/inc/rack_elevation.html | 5 +- netbox/templates/dcim/rack.html | 23 ++++++++- 8 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 netbox/dcim/migrations/0098_devicetype_images.py diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 9ec2c443e..37aecbd53 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -930,8 +930,8 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = DeviceType fields = [ - 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'comments', - 'tags', + 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', + 'front_image', 'rear_image', 'comments', 'tags', ] widgets = { 'subdevice_role': StaticSelect2() diff --git a/netbox/dcim/migrations/0098_devicetype_images.py b/netbox/dcim/migrations/0098_devicetype_images.py new file mode 100644 index 000000000..837a2b73a --- /dev/null +++ b/netbox/dcim/migrations/0098_devicetype_images.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.9 on 2020-02-20 15:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0097_interfacetemplate_type_other'), + ] + + operations = [ + migrations.AddField( + model_name='devicetype', + name='front_image', + field=models.ImageField(blank=True, upload_to='devicetype-images'), + ), + migrations.AddField( + model_name='devicetype', + name='rear_image', + field=models.ImageField(blank=True, upload_to='devicetype-images'), + ), + ] diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 29afef1f1..bee33b7e5 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -9,6 +9,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelatio from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField, JSONField from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.core.files.storage import default_storage from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Count, F, ProtectedError, Sum @@ -409,6 +410,13 @@ class RackElevationHelperMixin: hex_color = '#{}'.format(foreground_color(color)) link.add(drawing.text(str(name), insert=text, fill=hex_color)) + # Embed front device type image if one exists + if device.device_type.front_image: + url = device.device_type.front_image.url + image = drawing.image(href=url, insert=start, size=end, class_='device-image') + image.stretch() + link.add(image) + @staticmethod def _draw_device_rear(drawing, device, start, end, text): rect = drawing.rect(start, end, class_="slot blocked") @@ -419,6 +427,13 @@ class RackElevationHelperMixin: drawing.add(rect) drawing.add(drawing.text(str(device), insert=text)) + # Embed rear device type image if one exists + if device.device_type.front_image: + url = device.device_type.rear_image.url + image = drawing.image(href=url, insert=start, size=end, class_='device-image') + image.stretch() + drawing.add(image) + @staticmethod def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation): link = drawing.add( @@ -1025,6 +1040,14 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): help_text='Parent devices house child devices in device bays. Leave blank ' 'if this device type is neither a parent nor a child.' ) + front_image = models.ImageField( + upload_to='devicetype-images', + blank=True + ) + rear_image = models.ImageField( + upload_to='devicetype-images', + blank=True + ) comments = models.TextField( blank=True ) @@ -1056,6 +1079,10 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): # Save a copy of u_height for validation in clean() self._original_u_height = self.u_height + # Save references to the original front/rear images + self._original_front_image = self.front_image + self._original_rear_image = self.rear_image + def get_absolute_url(self): return reverse('dcim:devicetype', args=[self.pk]) @@ -1175,6 +1202,26 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): 'u_height': "Child device types must be 0U." }) + def save(self, *args, **kwargs): + ret = super().save(*args, **kwargs) + + # Delete any previously uploaded image files that are no longer in use + if self.front_image != self._original_front_image: + self._original_front_image.delete(save=False) + if self.rear_image != self._original_rear_image: + self._original_rear_image.delete(save=False) + + return ret + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + + # Delete any uploaded image files + if self.front_image: + self.front_image.delete(save=False) + if self.rear_image: + self.rear_image.delete(save=False) + @property def display_name(self): return '{} {}'.format(self.manufacturer.name, self.model) diff --git a/netbox/project-static/css/rack_elevation.css b/netbox/project-static/css/rack_elevation.css index cbb5015a5..69874d13c 100644 --- a/netbox/project-static/css/rack_elevation.css +++ b/netbox/project-static/css/rack_elevation.css @@ -56,7 +56,6 @@ text { .blocked:hover+.add-device { fill: none; } - .unit { margin: 0; padding: 5px 0px; @@ -65,3 +64,6 @@ text { font-size: 10px; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; } +.hidden { + visibility: hidden; +} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 99f76ab35..292a31c89 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -109,6 +109,30 @@ {% endif %} + + Front Image + + {% if devicetype.front_image %} + + {{ devicetype.front_image.name }} + + {% else %} + + {% endif %} + + + + Rear Image + + {% if devicetype.rear_image %} + + {{ devicetype.rear_image.name }} + + {% else %} + + {% endif %} + + Instances {{ devicetype.instances.count }} diff --git a/netbox/templates/dcim/devicetype_edit.html b/netbox/templates/dcim/devicetype_edit.html index 3c22eb9cd..f4f363b14 100644 --- a/netbox/templates/dcim/devicetype_edit.html +++ b/netbox/templates/dcim/devicetype_edit.html @@ -14,6 +14,13 @@ {% render_field form.subdevice_role %} +
+
Rack Images
+
+ {% render_field form.front_image %} + {% render_field form.rear_image %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/dcim/inc/rack_elevation.html b/netbox/templates/dcim/inc/rack_elevation.html index b0fcf4908..feced6a22 100644 --- a/netbox/templates/dcim/inc/rack_elevation.html +++ b/netbox/templates/dcim/inc/rack_elevation.html @@ -1,7 +1,4 @@ {% load helpers %} -
- - - +
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 067f4fbdb..a43b00f54 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -47,6 +47,11 @@
{% custom_links rack %}
+
+ +