Parcourir la source

Fixes #672: Expanded color selection for rack and device roles

Jeremy Stretch il y a 8 ans
Parent
commit
eb4cd0e723

+ 9 - 9
netbox/dcim/fixtures/initial_data.json

@@ -5,7 +5,7 @@
     "fields": {
         "name": "Console Server",
         "slug": "console-server",
-        "color": "teal"
+        "color": "009688"
     }
 },
 {
@@ -14,7 +14,7 @@
     "fields": {
         "name": "Core Switch",
         "slug": "core-switch",
-        "color": "blue"
+        "color": "2196f3"
     }
 },
 {
@@ -23,7 +23,7 @@
     "fields": {
         "name": "Distribution Switch",
         "slug": "distribution-switch",
-        "color": "blue"
+        "color": "2196f3"
     }
 },
 {
@@ -32,7 +32,7 @@
     "fields": {
         "name": "Access Switch",
         "slug": "access-switch",
-        "color": "blue"
+        "color": "2196f3"
     }
 },
 {
@@ -41,7 +41,7 @@
     "fields": {
         "name": "Management Switch",
         "slug": "management-switch",
-        "color": "orange"
+        "color": "ff9800"
     }
 },
 {
@@ -50,7 +50,7 @@
     "fields": {
         "name": "Firewall",
         "slug": "firewall",
-        "color": "red"
+        "color": "f44336"
     }
 },
 {
@@ -59,7 +59,7 @@
     "fields": {
         "name": "Router",
         "slug": "router",
-        "color": "purple"
+        "color": "9c27b0"
     }
 },
 {
@@ -68,7 +68,7 @@
     "fields": {
         "name": "Server",
         "slug": "server",
-        "color": "medium_gray"
+        "color": "9e9e9e"
     }
 },
 {
@@ -77,7 +77,7 @@
     "fields": {
         "name": "PDU",
         "slug": "pdu",
-        "color": "dark_gray"
+        "color": "607d8b"
     }
 },
 {

+ 57 - 0
netbox/dcim/migrations/0022_color_names_to_rgb.py

@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-12-06 16:35
+from __future__ import unicode_literals
+
+from django.db import migrations
+import utilities.fields
+
+
+COLOR_CONVERSION = {
+    'teal': '009688',
+    'green': '4caf50',
+    'blue': '2196f3',
+    'purple': '9c27b0',
+    'yellow': 'ffeb3b',
+    'orange': 'ff9800',
+    'red': 'f44336',
+    'light_gray': 'c0c0c0',
+    'medium_gray': '9e9e9e',
+    'dark_gray': '607d8b',
+}
+
+
+def color_names_to_rgb(apps, schema_editor):
+    RackRole = apps.get_model('dcim', 'RackRole')
+    DeviceRole = apps.get_model('dcim', 'DeviceRole')
+    for color_name, color_rgb in COLOR_CONVERSION.items():
+        RackRole.objects.filter(color=color_name).update(color=color_rgb)
+        DeviceRole.objects.filter(color=color_name).update(color=color_rgb)
+
+
+def color_rgb_to_name(apps, schema_editor):
+    RackRole = apps.get_model('dcim', 'RackRole')
+    DeviceRole = apps.get_model('dcim', 'DeviceRole')
+    for color_name, color_rgb in COLOR_CONVERSION.items():
+        RackRole.objects.filter(color=color_rgb).update(color=color_name)
+        DeviceRole.objects.filter(color=color_rgb).update(color=color_name)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0021_add_ff_flexstack'),
+    ]
+
+    operations = [
+        migrations.RunPython(color_names_to_rgb, color_rgb_to_name),
+        migrations.AlterField(
+            model_name='devicerole',
+            name='color',
+            field=utilities.fields.ColorField(max_length=6),
+        ),
+        migrations.AlterField(
+            model_name='rackrole',
+            name='color',
+            field=utilities.fields.ColorField(max_length=6),
+        ),
+    ]

+ 4 - 27
netbox/dcim/models.py

@@ -3,7 +3,7 @@ from collections import OrderedDict
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.fields import GenericRelation
-from django.core.exceptions import MultipleObjectsReturned, ValidationError
+from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
@@ -12,7 +12,7 @@ from django.db.models import Count, Q, ObjectDoesNotExist
 from extras.models import CustomFieldModel, CustomField, CustomFieldValue
 from extras.rpc import RPC_CLIENTS
 from tenancy.models import Tenant
-from utilities.fields import NullableCharField
+from utilities.fields import ColorField, NullableCharField
 from utilities.managers import NaturalOrderByManager
 from utilities.models import CreatedUpdatedModel
 
@@ -54,29 +54,6 @@ SUBDEVICE_ROLE_CHOICES = (
     (SUBDEVICE_ROLE_CHILD, 'Child'),
 )
 
-COLOR_TEAL = 'teal'
-COLOR_GREEN = 'green'
-COLOR_BLUE = 'blue'
-COLOR_PURPLE = 'purple'
-COLOR_YELLOW = 'yellow'
-COLOR_ORANGE = 'orange'
-COLOR_RED = 'red'
-COLOR_GRAY1 = 'light_gray'
-COLOR_GRAY2 = 'medium_gray'
-COLOR_GRAY3 = 'dark_gray'
-ROLE_COLOR_CHOICES = [
-    [COLOR_TEAL, 'Teal'],
-    [COLOR_GREEN, 'Green'],
-    [COLOR_BLUE, 'Blue'],
-    [COLOR_PURPLE, 'Purple'],
-    [COLOR_YELLOW, 'Yellow'],
-    [COLOR_ORANGE, 'Orange'],
-    [COLOR_RED, 'Red'],
-    [COLOR_GRAY1, 'Light Gray'],
-    [COLOR_GRAY2, 'Medium Gray'],
-    [COLOR_GRAY3, 'Dark Gray'],
-]
-
 # Virtual
 IFACE_FF_VIRTUAL = 0
 # Ethernet
@@ -345,7 +322,7 @@ class RackRole(models.Model):
     """
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
-    color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
+    color = ColorField()
 
     class Meta:
         ordering = ['name']
@@ -761,7 +738,7 @@ class DeviceRole(models.Model):
     """
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
-    color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
+    color = ColorField()
 
     class Meta:
         ordering = ['name']

+ 3 - 3
netbox/dcim/tables.py

@@ -11,7 +11,7 @@ from .models import (
 
 
 COLOR_LABEL = """
-<label class="label {{ record.color }}">{{ record }}</label>
+<label class="label" style="background-color: #{{ record.color }}">{{ record }}</label>
 """
 
 DEVICE_LINK = """
@@ -34,7 +34,7 @@ RACKROLE_ACTIONS = """
 
 RACK_ROLE = """
 {% if record.role %}
-    <label class="label {{ record.role.color }}">{{ value }}</label>
+    <label class="label" style="background-color: #{{ record.role.color }}">{{ value }}</label>
 {% else %}
     &mdash;
 {% endif %}
@@ -59,7 +59,7 @@ PLATFORM_ACTIONS = """
 """
 
 DEVICE_ROLE = """
-<label class="label {{ record.device_role.color }}">{{ value }}</label>
+<label class="label" style="background-color: #{{ record.device_role.color }}">{{ value }}</label>
 """
 
 STATUS_ICON = """

+ 0 - 12
netbox/project-static/css/base.css

@@ -296,18 +296,6 @@ li.occupied + li.available {
     border-top: 1px solid #474747;
 }
 
-/* Colors (from http://flatuicolors.com) */
-.teal { background-color: #1abc9c; }
-.green { background-color: #2ecc71; }
-.blue { background-color: #3498db; }
-.purple { background-color: #9b59b6; }
-.yellow { background-color: #f1c40f; }
-.orange { background-color: #e67e22; }
-.red { background-color: #e74c3c; }
-.light_gray { background-color: #dce2e3; }
-.medium_gray { background-color: #95a5a6; }
-.dark_gray { background-color: #34495e; }
-
 /* Misc */
 .banner-bottom {
     margin-bottom: 50px;

+ 19 - 0
netbox/utilities/fields.py

@@ -1,5 +1,11 @@
+from django.core.validators import RegexValidator
 from django.db import models
 
+from .forms import ColorSelect
+
+
+validate_color = RegexValidator('^[0-9a-f]{6}$', 'Enter a valid hexadecimal RGB color code.', 'invalid')
+
 
 class NullableCharField(models.CharField):
     description = "Stores empty values as NULL rather than ''"
@@ -11,3 +17,16 @@ class NullableCharField(models.CharField):
 
     def get_prep_value(self, value):
         return value or None
+
+
+class ColorField(models.CharField):
+    default_validators = [validate_color]
+    description = "A hexadecimal RGB color code"
+
+    def __init__(self, *args, **kwargs):
+        kwargs['max_length'] = 6
+        super(ColorField, self).__init__(*args, **kwargs)
+
+    def formfield(self, **kwargs):
+        kwargs['widget'] = ColorSelect
+        return super(ColorField, self).formfield(**kwargs)

+ 47 - 0
netbox/utilities/forms.py

@@ -11,6 +11,32 @@ from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 
 
+COLOR_CHOICES = (
+    ('aa1409', 'Dark red'),
+    ('f44336', 'Red'),
+    ('e91e63', 'Pink'),
+    ('ff66ff', 'Fuschia'),
+    ('9c27b0', 'Purple'),
+    ('673ab7', 'Dark purple'),
+    ('3f51b5', 'Indigo'),
+    ('2196f3', 'Blue'),
+    ('03a9f4', 'Light blue'),
+    ('00bcd4', 'Cyan'),
+    ('009688', 'Teal'),
+    ('2f6a31', 'Dark green'),
+    ('4caf50', 'Green'),
+    ('8bc34a', 'Light green'),
+    ('cddc39', 'Lime'),
+    ('ffeb3b', 'Yellow'),
+    ('ffc107', 'Amber'),
+    ('ff9800', 'Orange'),
+    ('ff5722', 'Dark orange'),
+    ('795548', 'Brown'),
+    ('c0c0c0', 'Light grey'),
+    ('9e9e9e', 'Grey'),
+    ('607d8b', 'Dark grey'),
+    ('111111', 'Black'),
+)
 NUMERIC_EXPANSION_PATTERN = '\[(\d+-\d+)\]'
 IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]'
 IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]'
@@ -71,6 +97,27 @@ class SmallTextarea(forms.Textarea):
     pass
 
 
+class ColorSelect(forms.Select):
+
+    def __init__(self, *args, **kwargs):
+        kwargs['choices'] = COLOR_CHOICES
+        super(ColorSelect, self).__init__(*args, **kwargs)
+
+    def render_option(self, selected_choices, option_value, option_label):
+        if option_value is None:
+            option_value = ''
+        option_value = force_text(option_value)
+        if option_value in selected_choices:
+            selected_html = mark_safe(' selected')
+            if not self.allow_multiple_selected:
+                # Only allow for a single selection.
+                selected_choices.remove(option_value)
+        else:
+            selected_html = ''
+        return format_html('<option value="{}"{} style="background-color: #{}">{}</option>',
+                           option_value, selected_html, option_value, force_text(option_label))
+
+
 class SelectWithDisabled(forms.Select):
     """
     Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include