mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
Fixes #672: Expanded color selection for rack and device roles
This commit is contained in:
parent
300ee820fa
commit
eb4cd0e723
@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Console Server",
|
"name": "Console Server",
|
||||||
"slug": "console-server",
|
"slug": "console-server",
|
||||||
"color": "teal"
|
"color": "009688"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Core Switch",
|
"name": "Core Switch",
|
||||||
"slug": "core-switch",
|
"slug": "core-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Distribution Switch",
|
"name": "Distribution Switch",
|
||||||
"slug": "distribution-switch",
|
"slug": "distribution-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Access Switch",
|
"name": "Access Switch",
|
||||||
"slug": "access-switch",
|
"slug": "access-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Management Switch",
|
"name": "Management Switch",
|
||||||
"slug": "management-switch",
|
"slug": "management-switch",
|
||||||
"color": "orange"
|
"color": "ff9800"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Firewall",
|
"name": "Firewall",
|
||||||
"slug": "firewall",
|
"slug": "firewall",
|
||||||
"color": "red"
|
"color": "f44336"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Router",
|
"name": "Router",
|
||||||
"slug": "router",
|
"slug": "router",
|
||||||
"color": "purple"
|
"color": "9c27b0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Server",
|
"name": "Server",
|
||||||
"slug": "server",
|
"slug": "server",
|
||||||
"color": "medium_gray"
|
"color": "9e9e9e"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "PDU",
|
"name": "PDU",
|
||||||
"slug": "pdu",
|
"slug": "pdu",
|
||||||
"color": "dark_gray"
|
"color": "607d8b"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
57
netbox/dcim/migrations/0022_color_names_to_rgb.py
Normal file
57
netbox/dcim/migrations/0022_color_names_to_rgb.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -3,7 +3,7 @@ from collections import OrderedDict
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
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.urlresolvers import reverse
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
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.models import CustomFieldModel, CustomField, CustomFieldValue
|
||||||
from extras.rpc import RPC_CLIENTS
|
from extras.rpc import RPC_CLIENTS
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.fields import NullableCharField
|
from utilities.fields import ColorField, NullableCharField
|
||||||
from utilities.managers import NaturalOrderByManager
|
from utilities.managers import NaturalOrderByManager
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
|
||||||
@ -54,29 +54,6 @@ SUBDEVICE_ROLE_CHOICES = (
|
|||||||
(SUBDEVICE_ROLE_CHILD, 'Child'),
|
(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
|
# Virtual
|
||||||
IFACE_FF_VIRTUAL = 0
|
IFACE_FF_VIRTUAL = 0
|
||||||
# Ethernet
|
# Ethernet
|
||||||
@ -345,7 +322,7 @@ class RackRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
color = ColorField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -761,7 +738,7 @@ class DeviceRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
color = ColorField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -11,7 +11,7 @@ from .models import (
|
|||||||
|
|
||||||
|
|
||||||
COLOR_LABEL = """
|
COLOR_LABEL = """
|
||||||
<label class="label {{ record.color }}">{{ record }}</label>
|
<label class="label" style="background-color: #{{ record.color }}">{{ record }}</label>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_LINK = """
|
DEVICE_LINK = """
|
||||||
@ -34,7 +34,7 @@ RACKROLE_ACTIONS = """
|
|||||||
|
|
||||||
RACK_ROLE = """
|
RACK_ROLE = """
|
||||||
{% if record.role %}
|
{% if record.role %}
|
||||||
<label class="label {{ record.role.color }}">{{ value }}</label>
|
<label class="label" style="background-color: #{{ record.role.color }}">{{ value }}</label>
|
||||||
{% else %}
|
{% else %}
|
||||||
—
|
—
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -59,7 +59,7 @@ PLATFORM_ACTIONS = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_ROLE = """
|
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 = """
|
STATUS_ICON = """
|
||||||
|
@ -296,18 +296,6 @@ li.occupied + li.available {
|
|||||||
border-top: 1px solid #474747;
|
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 */
|
/* Misc */
|
||||||
.banner-bottom {
|
.banner-bottom {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
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):
|
class NullableCharField(models.CharField):
|
||||||
description = "Stores empty values as NULL rather than ''"
|
description = "Stores empty values as NULL rather than ''"
|
||||||
@ -11,3 +17,16 @@ class NullableCharField(models.CharField):
|
|||||||
|
|
||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
return value or None
|
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)
|
||||||
|
@ -11,6 +11,32 @@ from django.utils.html import format_html
|
|||||||
from django.utils.safestring import mark_safe
|
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+)\]'
|
NUMERIC_EXPANSION_PATTERN = '\[(\d+-\d+)\]'
|
||||||
IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]'
|
IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]'
|
||||||
IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]'
|
IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]'
|
||||||
@ -71,6 +97,27 @@ class SmallTextarea(forms.Textarea):
|
|||||||
pass
|
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):
|
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
|
Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include
|
||||||
|
Loading…
Reference in New Issue
Block a user