jeremystretch 3 лет назад
Родитель
Сommit
64080e808e
3 измененных файлов с 283 добавлено и 270 удалено
  1. 2 0
      netbox/dcim/svg/__init__.py
  2. 2 270
      netbox/dcim/svg/cables.py
  3. 279 0
      netbox/dcim/svg/racks.py

+ 2 - 0
netbox/dcim/svg/__init__.py

@@ -0,0 +1,2 @@
+from .cables import *
+from .racks import *

+ 2 - 270
netbox/dcim/svg.py → netbox/dcim/svg/cables.py

@@ -1,285 +1,17 @@
-import decimal
 import svgwrite
+
+from django.conf import settings
 from svgwrite.container import Group, Hyperlink
-from svgwrite.image import Image
-from svgwrite.gradients import LinearGradient
 from svgwrite.shapes import Line, Rect
 from svgwrite.text import Text
 
-from django.conf import settings
-from django.urls import reverse
-from django.utils.http import urlencode
-
-from netbox.config import get_config
 from utilities.utils import foreground_color
-from .choices import DeviceFaceChoices
-from .constants import RACK_ELEVATION_BORDER_WIDTH
-
 
 __all__ = (
     'CableTraceSVG',
-    'RackElevationSVG',
 )
 
 
-def get_device_name(device):
-    if device.virtual_chassis:
-        name = f'{device.virtual_chassis.name}:{device.vc_position}'
-    elif device.name:
-        name = device.name
-    else:
-        name = str(device.device_type)
-    if device.devicebay_count:
-        name += ' ({}/{})'.format(device.get_children().count(), device.devicebay_count)
-
-    return name
-
-
-def get_device_description(device):
-    return '{} ({}) — {} {} ({}U) {} {}'.format(
-        device.name,
-        device.device_role,
-        device.device_type.manufacturer.name,
-        device.device_type.model,
-        device.device_type.u_height,
-        device.asset_tag or '',
-        device.serial or ''
-    )
-
-
-class RackElevationSVG:
-    """
-    Use this class to render a rack elevation as an SVG image.
-
-    :param rack: A NetBox Rack instance
-    :param user: User instance. If specified, only devices viewable by this user will be fully displayed.
-    :param include_images: If true, the SVG document will embed front/rear device face images, where available
-    :param base_url: Base URL for links within the SVG document. If none, links will be relative.
-    """
-    def __init__(self, rack, unit_height=None, unit_width=None, legend_width=None, user=None, include_images=True,
-                 base_url=None):
-        self.rack = rack
-        self.include_images = include_images
-        self.base_url = base_url.rstrip('/') if base_url is not None else ''
-
-        # Set drawing dimensions
-        config = get_config()
-        self.unit_width = unit_width or config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
-        self.unit_height = unit_height or config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT
-        self.legend_width = legend_width or config.RACK_ELEVATION_LEGEND_WIDTH_DEFAULT
-
-        # Determine the subset of devices within this rack that are viewable by the user, if any
-        permitted_devices = self.rack.devices
-        if user is not None:
-            permitted_devices = permitted_devices.restrict(user, 'view')
-        self.permitted_device_ids = permitted_devices.values_list('pk', flat=True)
-
-    @staticmethod
-    def _add_gradient(drawing, id_, color):
-        gradient = LinearGradient(
-            start=(0, 0),
-            end=(0, 25),
-            spreadMethod='repeat',
-            id_=id_,
-            gradientTransform='rotate(45, 0, 0)',
-            gradientUnits='userSpaceOnUse'
-        )
-        gradient.add_stop_color(offset='0%', color='#f7f7f7')
-        gradient.add_stop_color(offset='50%', color='#f7f7f7')
-        gradient.add_stop_color(offset='50%', color=color)
-        gradient.add_stop_color(offset='100%', color=color)
-
-        drawing.defs.add(gradient)
-
-    def _setup_drawing(self):
-        width = self.unit_width + self.legend_width + RACK_ELEVATION_BORDER_WIDTH * 2
-        height = self.unit_height * self.rack.u_height + RACK_ELEVATION_BORDER_WIDTH * 2
-        drawing = svgwrite.Drawing(size=(width, height))
-
-        # Add the stylesheet
-        with open(f'{settings.STATIC_ROOT}/rack_elevation.css') as css_file:
-            drawing.defs.add(drawing.style(css_file.read()))
-
-        # Add gradients
-        RackElevationSVG._add_gradient(drawing, 'occupied', '#d7d7d7')
-        RackElevationSVG._add_gradient(drawing, 'blocked', '#ffc0c0')
-
-        return drawing
-
-    def _get_device_coords(self, position, height):
-        """
-        Return the X, Y coordinates of the top left corner for a device in the specified rack unit.
-        """
-        x = self.legend_width + RACK_ELEVATION_BORDER_WIDTH
-        y = RACK_ELEVATION_BORDER_WIDTH
-        if self.rack.desc_units:
-            y += int((position - 1) * self.unit_height)
-        else:
-            y += int((self.rack.u_height - position + 1) * self.unit_height) - int(height * self.unit_height)
-
-        return x, y
-
-    def _draw_device(self, device, coords, size, color=None, image=None):
-        name = get_device_name(device)
-        description = get_device_description(device)
-        text_coords = (
-            coords[0] + size[0] / 2,
-            coords[1] + size[1] / 2
-        )
-        text_color = f'#{foreground_color(color)}' if color else '#000000'
-
-        # Create hyperlink element
-        link = Hyperlink(
-            href='{}{}'.format(
-                self.base_url,
-                reverse('dcim:device', kwargs={'pk': device.pk})
-            ),
-            target='_blank',
-        )
-        link.set_desc(description)
-        if color:
-            link.add(Rect(coords, size, style=f'fill: #{color}', class_='slot'))
-        else:
-            link.add(Rect(coords, size, class_='slot blocked'))
-        link.add(Text(name, insert=text_coords, fill=text_color))
-
-        # Embed device type image if provided
-        if self.include_images and image:
-            image = Image(
-                href='{}{}'.format(self.base_url, image.url),
-                insert=coords,
-                size=size,
-                class_='device-image'
-            )
-            image.fit(scale='slice')
-            link.add(image)
-            link.add(Text(name, insert=text_coords, stroke='black',
-                     stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
-            link.add(Text(name, insert=text_coords, fill='white', class_='device-image-label'))
-
-        self.drawing.add(link)
-
-    def draw_device_front(self, device, coords, size):
-        """
-        Draw the front (mounted) face of a device.
-        """
-        color = device.device_role.color
-        image = device.device_type.front_image
-        self._draw_device(device, coords, size, color=color, image=image)
-
-    def draw_device_rear(self, device, coords, size):
-        """
-        Draw the rear (opposite) face of a device.
-        """
-        image = device.device_type.rear_image
-        self._draw_device(device, coords, size, image=image)
-
-    def draw_border(self):
-        """
-        Draw a border around the collection of rack units.
-        """
-        border_width = RACK_ELEVATION_BORDER_WIDTH
-        border_offset = RACK_ELEVATION_BORDER_WIDTH / 2
-        frame = Rect(
-            insert=(self.legend_width + border_offset, border_offset),
-            size=(self.unit_width + border_width, self.rack.u_height * self.unit_height + border_width),
-            class_='rack'
-        )
-        self.drawing.add(frame)
-
-    def draw_legend(self):
-        """
-        Draw the rack unit labels along the lefthand side of the elevation.
-        """
-        for ru in range(0, self.rack.u_height):
-            start_y = ru * self.unit_height + RACK_ELEVATION_BORDER_WIDTH
-            position_coordinates = (self.legend_width / 2, start_y + self.unit_height / 2 + RACK_ELEVATION_BORDER_WIDTH)
-            unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru
-            self.drawing.add(
-                Text(str(unit), position_coordinates, class_='unit')
-            )
-
-    def draw_background(self, face):
-        """
-        Draw the rack unit placeholders which form the "background" of the rack elevation.
-        """
-        x_offset = RACK_ELEVATION_BORDER_WIDTH + self.legend_width
-        url_string = '{}?{}&position={{}}'.format(
-            reverse('dcim:device_add'),
-            urlencode({
-                'site': self.rack.site.pk,
-                'location': self.rack.location.pk if self.rack.location else '',
-                'rack': self.rack.pk,
-                'face': face,
-            })
-        )
-
-        for ru in range(0, self.rack.u_height):
-            y_offset = RACK_ELEVATION_BORDER_WIDTH + ru * self.unit_height
-            text_coords = (
-                x_offset + self.unit_width / 2,
-                y_offset + self.unit_height / 2
-            )
-
-            link = Hyperlink(href=url_string.format(ru), target='_blank')
-            link.add(Rect((x_offset, y_offset), (self.unit_width, self.unit_height), class_='slot'))
-            link.add(Text('add device', insert=text_coords, class_='add-device'))
-
-            self.drawing.add(link)
-
-    def draw_face(self, face, opposite=False):
-        """
-        Draw any occupied rack units for the specified rack face.
-        """
-        for unit in self.rack.get_rack_units(face=face, expand_devices=False):
-
-            # Loop through all units in the elevation
-            device = unit['device']
-            height = unit.get('height', decimal.Decimal(1.0))
-
-            device_coords = self._get_device_coords(unit['id'], height)
-            device_size = (
-                self.unit_width,
-                int(self.unit_height * height)
-            )
-
-            # Draw the device
-            if device and device.pk in self.permitted_device_ids:
-                if device.face == face and not opposite:
-                    self.draw_device_front(device, device_coords, device_size)
-                else:
-                    self.draw_device_rear(device, device_coords, device_size)
-
-            elif device:
-                # Devices which the user does not have permission to view are rendered only as unavailable space
-                self.drawing.add(Rect(device_coords, device_size, class_='blocked'))
-
-    def render(self, face):
-        """
-        Return an SVG document representing a rack elevation.
-        """
-
-        # Initialize the drawing
-        self.drawing = self._setup_drawing()
-
-        # Draw the empty rack & legend
-        self.draw_legend()
-        self.draw_background(face)
-
-        # Draw the opposite rack face first, then the near face
-        if face == DeviceFaceChoices.FACE_REAR:
-            opposite_face = DeviceFaceChoices.FACE_FRONT
-        else:
-            opposite_face = DeviceFaceChoices.FACE_REAR
-        # self.draw_face(opposite_face, opposite=True)
-        self.draw_face(face)
-
-        # Draw the rack border last
-        self.draw_border()
-
-        return self.drawing
-
-
 OFFSET = 0.5
 PADDING = 10
 LINE_HEIGHT = 20

+ 279 - 0
netbox/dcim/svg/racks.py

@@ -0,0 +1,279 @@
+import decimal
+import svgwrite
+from svgwrite.container import Hyperlink
+from svgwrite.image import Image
+from svgwrite.gradients import LinearGradient
+from svgwrite.shapes import Rect
+from svgwrite.text import Text
+
+from django.conf import settings
+from django.urls import reverse
+from django.utils.http import urlencode
+
+from netbox.config import get_config
+from utilities.utils import foreground_color
+from dcim.choices import DeviceFaceChoices
+from dcim.constants import RACK_ELEVATION_BORDER_WIDTH
+
+
+__all__ = (
+    'RackElevationSVG',
+)
+
+
+def get_device_name(device):
+    if device.virtual_chassis:
+        name = f'{device.virtual_chassis.name}:{device.vc_position}'
+    elif device.name:
+        name = device.name
+    else:
+        name = str(device.device_type)
+    if device.devicebay_count:
+        name += ' ({}/{})'.format(device.get_children().count(), device.devicebay_count)
+
+    return name
+
+
+def get_device_description(device):
+    return '{} ({}) — {} {} ({}U) {} {}'.format(
+        device.name,
+        device.device_role,
+        device.device_type.manufacturer.name,
+        device.device_type.model,
+        device.device_type.u_height,
+        device.asset_tag or '',
+        device.serial or ''
+    )
+
+
+class RackElevationSVG:
+    """
+    Use this class to render a rack elevation as an SVG image.
+
+    :param rack: A NetBox Rack instance
+    :param user: User instance. If specified, only devices viewable by this user will be fully displayed.
+    :param include_images: If true, the SVG document will embed front/rear device face images, where available
+    :param base_url: Base URL for links within the SVG document. If none, links will be relative.
+    """
+    def __init__(self, rack, unit_height=None, unit_width=None, legend_width=None, user=None, include_images=True,
+                 base_url=None):
+        self.rack = rack
+        self.include_images = include_images
+        self.base_url = base_url.rstrip('/') if base_url is not None else ''
+
+        # Set drawing dimensions
+        config = get_config()
+        self.unit_width = unit_width or config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
+        self.unit_height = unit_height or config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT
+        self.legend_width = legend_width or config.RACK_ELEVATION_LEGEND_WIDTH_DEFAULT
+
+        # Determine the subset of devices within this rack that are viewable by the user, if any
+        permitted_devices = self.rack.devices
+        if user is not None:
+            permitted_devices = permitted_devices.restrict(user, 'view')
+        self.permitted_device_ids = permitted_devices.values_list('pk', flat=True)
+
+    @staticmethod
+    def _add_gradient(drawing, id_, color):
+        gradient = LinearGradient(
+            start=(0, 0),
+            end=(0, 25),
+            spreadMethod='repeat',
+            id_=id_,
+            gradientTransform='rotate(45, 0, 0)',
+            gradientUnits='userSpaceOnUse'
+        )
+        gradient.add_stop_color(offset='0%', color='#f7f7f7')
+        gradient.add_stop_color(offset='50%', color='#f7f7f7')
+        gradient.add_stop_color(offset='50%', color=color)
+        gradient.add_stop_color(offset='100%', color=color)
+
+        drawing.defs.add(gradient)
+
+    def _setup_drawing(self):
+        width = self.unit_width + self.legend_width + RACK_ELEVATION_BORDER_WIDTH * 2
+        height = self.unit_height * self.rack.u_height + RACK_ELEVATION_BORDER_WIDTH * 2
+        drawing = svgwrite.Drawing(size=(width, height))
+
+        # Add the stylesheet
+        with open(f'{settings.STATIC_ROOT}/rack_elevation.css') as css_file:
+            drawing.defs.add(drawing.style(css_file.read()))
+
+        # Add gradients
+        RackElevationSVG._add_gradient(drawing, 'occupied', '#d7d7d7')
+        RackElevationSVG._add_gradient(drawing, 'blocked', '#ffc0c0')
+
+        return drawing
+
+    def _get_device_coords(self, position, height):
+        """
+        Return the X, Y coordinates of the top left corner for a device in the specified rack unit.
+        """
+        x = self.legend_width + RACK_ELEVATION_BORDER_WIDTH
+        y = RACK_ELEVATION_BORDER_WIDTH
+        if self.rack.desc_units:
+            y += int((position - 1) * self.unit_height)
+        else:
+            y += int((self.rack.u_height - position + 1) * self.unit_height) - int(height * self.unit_height)
+
+        return x, y
+
+    def _draw_device(self, device, coords, size, color=None, image=None):
+        name = get_device_name(device)
+        description = get_device_description(device)
+        text_coords = (
+            coords[0] + size[0] / 2,
+            coords[1] + size[1] / 2
+        )
+        text_color = f'#{foreground_color(color)}' if color else '#000000'
+
+        # Create hyperlink element
+        link = Hyperlink(
+            href='{}{}'.format(
+                self.base_url,
+                reverse('dcim:device', kwargs={'pk': device.pk})
+            ),
+            target='_blank',
+        )
+        link.set_desc(description)
+        if color:
+            link.add(Rect(coords, size, style=f'fill: #{color}', class_='slot'))
+        else:
+            link.add(Rect(coords, size, class_='slot blocked'))
+        link.add(Text(name, insert=text_coords, fill=text_color))
+
+        # Embed device type image if provided
+        if self.include_images and image:
+            image = Image(
+                href='{}{}'.format(self.base_url, image.url),
+                insert=coords,
+                size=size,
+                class_='device-image'
+            )
+            image.fit(scale='slice')
+            link.add(image)
+            link.add(Text(name, insert=text_coords, stroke='black',
+                     stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
+            link.add(Text(name, insert=text_coords, fill='white', class_='device-image-label'))
+
+        self.drawing.add(link)
+
+    def draw_device_front(self, device, coords, size):
+        """
+        Draw the front (mounted) face of a device.
+        """
+        color = device.device_role.color
+        image = device.device_type.front_image
+        self._draw_device(device, coords, size, color=color, image=image)
+
+    def draw_device_rear(self, device, coords, size):
+        """
+        Draw the rear (opposite) face of a device.
+        """
+        image = device.device_type.rear_image
+        self._draw_device(device, coords, size, image=image)
+
+    def draw_border(self):
+        """
+        Draw a border around the collection of rack units.
+        """
+        border_width = RACK_ELEVATION_BORDER_WIDTH
+        border_offset = RACK_ELEVATION_BORDER_WIDTH / 2
+        frame = Rect(
+            insert=(self.legend_width + border_offset, border_offset),
+            size=(self.unit_width + border_width, self.rack.u_height * self.unit_height + border_width),
+            class_='rack'
+        )
+        self.drawing.add(frame)
+
+    def draw_legend(self):
+        """
+        Draw the rack unit labels along the lefthand side of the elevation.
+        """
+        for ru in range(0, self.rack.u_height):
+            start_y = ru * self.unit_height + RACK_ELEVATION_BORDER_WIDTH
+            position_coordinates = (self.legend_width / 2, start_y + self.unit_height / 2 + RACK_ELEVATION_BORDER_WIDTH)
+            unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru
+            self.drawing.add(
+                Text(str(unit), position_coordinates, class_='unit')
+            )
+
+    def draw_background(self, face):
+        """
+        Draw the rack unit placeholders which form the "background" of the rack elevation.
+        """
+        x_offset = RACK_ELEVATION_BORDER_WIDTH + self.legend_width
+        url_string = '{}?{}&position={{}}'.format(
+            reverse('dcim:device_add'),
+            urlencode({
+                'site': self.rack.site.pk,
+                'location': self.rack.location.pk if self.rack.location else '',
+                'rack': self.rack.pk,
+                'face': face,
+            })
+        )
+
+        for ru in range(0, self.rack.u_height):
+            y_offset = RACK_ELEVATION_BORDER_WIDTH + ru * self.unit_height
+            text_coords = (
+                x_offset + self.unit_width / 2,
+                y_offset + self.unit_height / 2
+            )
+
+            link = Hyperlink(href=url_string.format(ru), target='_blank')
+            link.add(Rect((x_offset, y_offset), (self.unit_width, self.unit_height), class_='slot'))
+            link.add(Text('add device', insert=text_coords, class_='add-device'))
+
+            self.drawing.add(link)
+
+    def draw_face(self, face, opposite=False):
+        """
+        Draw any occupied rack units for the specified rack face.
+        """
+        for unit in self.rack.get_rack_units(face=face, expand_devices=False):
+
+            # Loop through all units in the elevation
+            device = unit['device']
+            height = unit.get('height', decimal.Decimal(1.0))
+
+            device_coords = self._get_device_coords(unit['id'], height)
+            device_size = (
+                self.unit_width,
+                int(self.unit_height * height)
+            )
+
+            # Draw the device
+            if device and device.pk in self.permitted_device_ids:
+                if device.face == face and not opposite:
+                    self.draw_device_front(device, device_coords, device_size)
+                else:
+                    self.draw_device_rear(device, device_coords, device_size)
+
+            elif device:
+                # Devices which the user does not have permission to view are rendered only as unavailable space
+                self.drawing.add(Rect(device_coords, device_size, class_='blocked'))
+
+    def render(self, face):
+        """
+        Return an SVG document representing a rack elevation.
+        """
+
+        # Initialize the drawing
+        self.drawing = self._setup_drawing()
+
+        # Draw the empty rack & legend
+        self.draw_legend()
+        self.draw_background(face)
+
+        # Draw the opposite rack face first, then the near face
+        if face == DeviceFaceChoices.FACE_REAR:
+            opposite_face = DeviceFaceChoices.FACE_FRONT
+        else:
+            opposite_face = DeviceFaceChoices.FACE_REAR
+        # self.draw_face(opposite_face, opposite=True)
+        self.draw_face(face)
+
+        # Draw the rack border last
+        self.draw_border()
+
+        return self.drawing