From eb4cd0e72306f340e79a4763100706a4d51d4aa9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Dec 2016 12:28:29 -0500 Subject: [PATCH] Fixes #672: Expanded color selection for rack and device roles --- netbox/dcim/fixtures/initial_data.json | 18 +++--- .../migrations/0022_color_names_to_rgb.py | 57 +++++++++++++++++++ netbox/dcim/models.py | 31 ++-------- netbox/dcim/tables.py | 6 +- netbox/project-static/css/base.css | 12 ---- netbox/utilities/fields.py | 19 +++++++ netbox/utilities/forms.py | 47 +++++++++++++++ 7 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 netbox/dcim/migrations/0022_color_names_to_rgb.py diff --git a/netbox/dcim/fixtures/initial_data.json b/netbox/dcim/fixtures/initial_data.json index a26cbfcc5..e765de227 100644 --- a/netbox/dcim/fixtures/initial_data.json +++ b/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" } }, { diff --git a/netbox/dcim/migrations/0022_color_names_to_rgb.py b/netbox/dcim/migrations/0022_color_names_to_rgb.py new file mode 100644 index 000000000..97e5de9ca --- /dev/null +++ b/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), + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 01b373376..e8967b0c0 100644 --- a/netbox/dcim/models.py +++ b/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'] diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 6c138b446..c81c24f82 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -11,7 +11,7 @@ from .models import ( COLOR_LABEL = """ - + """ DEVICE_LINK = """ @@ -34,7 +34,7 @@ RACKROLE_ACTIONS = """ RACK_ROLE = """ {% if record.role %} - + {% else %} — {% endif %} @@ -59,7 +59,7 @@ PLATFORM_ACTIONS = """ """ DEVICE_ROLE = """ - + """ STATUS_ICON = """ diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 2be592053..ff9eb98c1 100644 --- a/netbox/project-static/css/base.css +++ b/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; diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py index 017ceb275..14d1c7d8f 100644 --- a/netbox/utilities/fields.py +++ b/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) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 7104e34c0..979706ace 100644 --- a/netbox/utilities/forms.py +++ b/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, 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