diff --git a/netbox/circuits/choices.py b/netbox/circuits/choices.py index e3177adb4..007b45298 100644 --- a/netbox/circuits/choices.py +++ b/netbox/circuits/choices.py @@ -16,23 +16,14 @@ class CircuitStatusChoices(ChoiceSet): STATUS_DECOMMISSIONED = 'decommissioned' CHOICES = [ - (STATUS_PLANNED, 'Planned'), - (STATUS_PROVISIONING, 'Provisioning'), - (STATUS_ACTIVE, 'Active'), - (STATUS_OFFLINE, 'Offline'), - (STATUS_DEPROVISIONING, 'Deprovisioning'), - (STATUS_DECOMMISSIONED, 'Decommissioned'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_PROVISIONING, 'Provisioning', 'primary'), + (STATUS_ACTIVE, 'Active', 'success'), + (STATUS_OFFLINE, 'Offline', 'danger'), + (STATUS_DEPROVISIONING, 'Deprovisioning', 'warning'), + (STATUS_DECOMMISSIONED, 'Decommissioned', 'secondary'), ] - CSS_CLASSES = { - STATUS_DEPROVISIONING: 'warning', - STATUS_ACTIVE: 'success', - STATUS_PLANNED: 'info', - STATUS_PROVISIONING: 'primary', - STATUS_OFFLINE: 'danger', - STATUS_DECOMMISSIONED: 'secondary', - } - # # CircuitTerminations diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 602c0f403..013aef557 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -135,7 +135,7 @@ class Circuit(PrimaryModel): return reverse('circuits:circuit', args=[self.pk]) def get_status_class(self): - return CircuitStatusChoices.CSS_CLASSES.get(self.status) + return CircuitStatusChoices.colors.get(self.status, 'secondary') @extras_features('webhooks') diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 208a06c5d..bcc926580 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -15,21 +15,13 @@ class SiteStatusChoices(ChoiceSet): STATUS_RETIRED = 'retired' CHOICES = [ - (STATUS_PLANNED, 'Planned'), - (STATUS_STAGING, 'Staging'), - (STATUS_ACTIVE, 'Active'), - (STATUS_DECOMMISSIONING, 'Decommissioning'), - (STATUS_RETIRED, 'Retired'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_STAGING, 'Staging', 'primary'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'), + (STATUS_RETIRED, 'Retired', 'danger'), ] - CSS_CLASSES = { - STATUS_PLANNED: 'info', - STATUS_STAGING: 'primary', - STATUS_ACTIVE: 'success', - STATUS_DECOMMISSIONING: 'warning', - STATUS_RETIRED: 'danger', - } - # # Racks @@ -77,21 +69,13 @@ class RackStatusChoices(ChoiceSet): STATUS_DEPRECATED = 'deprecated' CHOICES = [ - (STATUS_RESERVED, 'Reserved'), - (STATUS_AVAILABLE, 'Available'), - (STATUS_PLANNED, 'Planned'), - (STATUS_ACTIVE, 'Active'), - (STATUS_DEPRECATED, 'Deprecated'), + (STATUS_RESERVED, 'Reserved', 'warning'), + (STATUS_AVAILABLE, 'Available', 'success'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_DEPRECATED, 'Deprecated', 'danger'), ] - CSS_CLASSES = { - STATUS_RESERVED: 'warning', - STATUS_AVAILABLE: 'success', - STATUS_PLANNED: 'info', - STATUS_ACTIVE: 'primary', - STATUS_DEPRECATED: 'danger', - } - class RackDimensionUnitChoices(ChoiceSet): @@ -157,25 +141,15 @@ class DeviceStatusChoices(ChoiceSet): STATUS_DECOMMISSIONING = 'decommissioning' CHOICES = [ - (STATUS_OFFLINE, 'Offline'), - (STATUS_ACTIVE, 'Active'), - (STATUS_PLANNED, 'Planned'), - (STATUS_STAGED, 'Staged'), - (STATUS_FAILED, 'Failed'), - (STATUS_INVENTORY, 'Inventory'), - (STATUS_DECOMMISSIONING, 'Decommissioning'), + (STATUS_OFFLINE, 'Offline', 'warning'), + (STATUS_ACTIVE, 'Active', 'success'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_STAGED, 'Staged', 'primary'), + (STATUS_FAILED, 'Failed', 'danger'), + (STATUS_INVENTORY, 'Inventory', 'secondary'), + (STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'), ] - CSS_CLASSES = { - STATUS_OFFLINE: 'warning', - STATUS_ACTIVE: 'success', - STATUS_PLANNED: 'info', - STATUS_STAGED: 'primary', - STATUS_FAILED: 'danger', - STATUS_INVENTORY: 'secondary', - STATUS_DECOMMISSIONING: 'warning', - } - class DeviceAirflowChoices(ChoiceSet): @@ -1147,17 +1121,11 @@ class LinkStatusChoices(ChoiceSet): STATUS_DECOMMISSIONING = 'decommissioning' CHOICES = ( - (STATUS_CONNECTED, 'Connected'), - (STATUS_PLANNED, 'Planned'), - (STATUS_DECOMMISSIONING, 'Decommissioning'), + (STATUS_CONNECTED, 'Connected', 'success'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'), ) - CSS_CLASSES = { - STATUS_CONNECTED: 'success', - STATUS_PLANNED: 'info', - STATUS_DECOMMISSIONING: 'warning', - } - class CableLengthUnitChoices(ChoiceSet): @@ -1194,19 +1162,12 @@ class PowerFeedStatusChoices(ChoiceSet): STATUS_FAILED = 'failed' CHOICES = [ - (STATUS_OFFLINE, 'Offline'), - (STATUS_ACTIVE, 'Active'), - (STATUS_PLANNED, 'Planned'), - (STATUS_FAILED, 'Failed'), + (STATUS_OFFLINE, 'Offline', 'warning'), + (STATUS_ACTIVE, 'Active', 'success'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_FAILED, 'Failed', 'danger'), ] - CSS_CLASSES = { - STATUS_OFFLINE: 'warning', - STATUS_ACTIVE: 'success', - STATUS_PLANNED: 'info', - STATUS_FAILED: 'danger', - } - class PowerFeedTypeChoices(ChoiceSet): @@ -1214,15 +1175,10 @@ class PowerFeedTypeChoices(ChoiceSet): TYPE_REDUNDANT = 'redundant' CHOICES = ( - (TYPE_PRIMARY, 'Primary'), - (TYPE_REDUNDANT, 'Redundant'), + (TYPE_PRIMARY, 'Primary', 'success'), + (TYPE_REDUNDANT, 'Redundant', 'info'), ) - CSS_CLASSES = { - TYPE_PRIMARY: 'success', - TYPE_REDUNDANT: 'info', - } - class PowerFeedSupplyChoices(ChoiceSet): diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 333972b21..12fe91036 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -289,7 +289,7 @@ class Cable(PrimaryModel): self._pk = self.pk def get_status_class(self): - return LinkStatusChoices.CSS_CLASSES.get(self.status) + return LinkStatusChoices.colors.get(self.status) def get_compatible_types(self): """ diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index a2ae20319..24eeb7ac3 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -862,7 +862,7 @@ class Device(PrimaryModel, ConfigContextModel): return Device.objects.filter(parent_bay__device=self.pk) def get_status_class(self): - return DeviceStatusChoices.CSS_CLASSES.get(self.status) + return DeviceStatusChoices.colors.get(self.status, 'secondary') # diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index b5d8d4c83..e3146c167 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -174,7 +174,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, LinkTermination): return self.power_panel def get_type_class(self): - return PowerFeedTypeChoices.CSS_CLASSES.get(self.type) + return PowerFeedTypeChoices.colors.get(self.type) def get_status_class(self): - return PowerFeedStatusChoices.CSS_CLASSES.get(self.status) + return PowerFeedStatusChoices.colors.get(self.status, 'secondary') diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 082ecfe57..c324d4cba 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -251,7 +251,7 @@ class Rack(PrimaryModel): return reversed(range(1, self.u_height + 1)) def get_status_class(self): - return RackStatusChoices.CSS_CLASSES.get(self.status) + return RackStatusChoices.colors.get(self.status, 'secondary') def get_rack_units(self, user=None, face=DeviceFaceChoices.FACE_FRONT, exclude=None, expand_devices=True): """ diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 0be7e4617..a71206224 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -315,7 +315,7 @@ class Site(PrimaryModel): return reverse('dcim:site', args=[self.pk]) def get_status_class(self): - return SiteStatusChoices.CSS_CLASSES.get(self.status) + return SiteStatusChoices.colors.get(self.status, 'secondary') # diff --git a/netbox/extras/choices.py b/netbox/extras/choices.py index 7503b4110..ff117c4e5 100644 --- a/netbox/extras/choices.py +++ b/netbox/extras/choices.py @@ -91,17 +91,11 @@ class ObjectChangeActionChoices(ChoiceSet): ACTION_DELETE = 'delete' CHOICES = ( - (ACTION_CREATE, 'Created'), - (ACTION_UPDATE, 'Updated'), - (ACTION_DELETE, 'Deleted'), + (ACTION_CREATE, 'Created', 'success'), + (ACTION_UPDATE, 'Updated', 'primary'), + (ACTION_DELETE, 'Deleted', 'danger'), ) - CSS_CLASSES = { - ACTION_CREATE: 'success', - ACTION_UPDATE: 'primary', - ACTION_DELETE: 'danger', - } - # # Jounral entries @@ -115,19 +109,12 @@ class JournalEntryKindChoices(ChoiceSet): KIND_DANGER = 'danger' CHOICES = ( - (KIND_INFO, 'Info'), - (KIND_SUCCESS, 'Success'), - (KIND_WARNING, 'Warning'), - (KIND_DANGER, 'Danger'), + (KIND_INFO, 'Info', 'info'), + (KIND_SUCCESS, 'Success', 'success'), + (KIND_WARNING, 'Warning', 'warning'), + (KIND_DANGER, 'Danger', 'danger'), ) - CSS_CLASSES = { - KIND_INFO: 'info', - KIND_SUCCESS: 'success', - KIND_WARNING: 'warning', - KIND_DANGER: 'danger', - } - # # Log Levels for Reports and Scripts @@ -142,21 +129,13 @@ class LogLevelChoices(ChoiceSet): LOG_FAILURE = 'failure' CHOICES = ( - (LOG_DEFAULT, 'Default'), - (LOG_SUCCESS, 'Success'), - (LOG_INFO, 'Info'), - (LOG_WARNING, 'Warning'), - (LOG_FAILURE, 'Failure'), + (LOG_DEFAULT, 'Default', 'secondary'), + (LOG_SUCCESS, 'Success', 'success'), + (LOG_INFO, 'Info', 'info'), + (LOG_WARNING, 'Warning', 'warning'), + (LOG_FAILURE, 'Failure', 'danger'), ) - CSS_CLASSES = { - LOG_DEFAULT: 'secondary', - LOG_SUCCESS: 'success', - LOG_INFO: 'info', - LOG_WARNING: 'warning', - LOG_FAILURE: 'danger', - } - # # Job results diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py index 15bd3cbd8..8dfeb2f18 100644 --- a/netbox/extras/models/change_logging.py +++ b/netbox/extras/models/change_logging.py @@ -105,4 +105,4 @@ class ObjectChange(BigIDModel): return reverse('extras:objectchange', args=[self.pk]) def get_action_class(self): - return ObjectChangeActionChoices.CSS_CLASSES.get(self.action) + return ObjectChangeActionChoices.colors.get(self.action) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 47da21e19..c20117b91 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -440,7 +440,7 @@ class JournalEntry(ChangeLoggedModel): return reverse('extras:journalentry', args=[self.pk]) def get_kind_class(self): - return JournalEntryKindChoices.CSS_CLASSES.get(self.kind) + return JournalEntryKindChoices.colors.get(self.kind) class JobResult(BigIDModel): diff --git a/netbox/extras/templatetags/log_levels.py b/netbox/extras/templatetags/log_levels.py index 050a6996d..0779a87eb 100644 --- a/netbox/extras/templatetags/log_levels.py +++ b/netbox/extras/templatetags/log_levels.py @@ -13,5 +13,5 @@ def log_level(level): """ return { 'name': LogLevelChoices.as_dict()[level], - 'class': LogLevelChoices.CSS_CLASSES.get(level) + 'class': LogLevelChoices.colors.get(level) } diff --git a/netbox/ipam/choices.py b/netbox/ipam/choices.py index c414fc115..693ee6689 100644 --- a/netbox/ipam/choices.py +++ b/netbox/ipam/choices.py @@ -25,19 +25,12 @@ class PrefixStatusChoices(ChoiceSet): STATUS_DEPRECATED = 'deprecated' CHOICES = [ - (STATUS_CONTAINER, 'Container'), - (STATUS_ACTIVE, 'Active'), - (STATUS_RESERVED, 'Reserved'), - (STATUS_DEPRECATED, 'Deprecated'), + (STATUS_CONTAINER, 'Container', 'secondary'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_RESERVED, 'Reserved', 'info'), + (STATUS_DEPRECATED, 'Deprecated', 'danger'), ] - CSS_CLASSES = { - STATUS_CONTAINER: 'secondary', - STATUS_ACTIVE: 'primary', - STATUS_RESERVED: 'info', - STATUS_DEPRECATED: 'danger', - } - # # IP Ranges @@ -51,17 +44,11 @@ class IPRangeStatusChoices(ChoiceSet): STATUS_DEPRECATED = 'deprecated' CHOICES = [ - (STATUS_ACTIVE, 'Active'), - (STATUS_RESERVED, 'Reserved'), - (STATUS_DEPRECATED, 'Deprecated'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_RESERVED, 'Reserved', 'info'), + (STATUS_DEPRECATED, 'Deprecated', 'danger'), ] - CSS_CLASSES = { - STATUS_ACTIVE: 'primary', - STATUS_RESERVED: 'info', - STATUS_DEPRECATED: 'danger', - } - # # IP Addresses @@ -77,21 +64,13 @@ class IPAddressStatusChoices(ChoiceSet): STATUS_SLAAC = 'slaac' CHOICES = [ - (STATUS_ACTIVE, 'Active'), - (STATUS_RESERVED, 'Reserved'), - (STATUS_DEPRECATED, 'Deprecated'), - (STATUS_DHCP, 'DHCP'), - (STATUS_SLAAC, 'SLAAC'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_RESERVED, 'Reserved', 'info'), + (STATUS_DEPRECATED, 'Deprecated', 'danger'), + (STATUS_DHCP, 'DHCP', 'success'), + (STATUS_SLAAC, 'SLAAC', 'success'), ] - CSS_CLASSES = { - STATUS_ACTIVE: 'primary', - STATUS_RESERVED: 'info', - STATUS_DEPRECATED: 'danger', - STATUS_DHCP: 'success', - STATUS_SLAAC: 'success', - } - class IPAddressRoleChoices(ChoiceSet): @@ -105,27 +84,16 @@ class IPAddressRoleChoices(ChoiceSet): ROLE_CARP = 'carp' CHOICES = ( - (ROLE_LOOPBACK, 'Loopback'), - (ROLE_SECONDARY, 'Secondary'), - (ROLE_ANYCAST, 'Anycast'), + (ROLE_LOOPBACK, 'Loopback', 'secondary'), + (ROLE_SECONDARY, 'Secondary', 'primary'), + (ROLE_ANYCAST, 'Anycast', 'warning'), (ROLE_VIP, 'VIP'), - (ROLE_VRRP, 'VRRP'), - (ROLE_HSRP, 'HSRP'), - (ROLE_GLBP, 'GLBP'), - (ROLE_CARP, 'CARP'), + (ROLE_VRRP, 'VRRP', 'success'), + (ROLE_HSRP, 'HSRP', 'success'), + (ROLE_GLBP, 'GLBP', 'success'), + (ROLE_CARP, 'CARP'), 'success', ) - CSS_CLASSES = { - ROLE_LOOPBACK: 'secondary', - ROLE_SECONDARY: 'primary', - ROLE_ANYCAST: 'warning', - ROLE_VIP: 'success', - ROLE_VRRP: 'success', - ROLE_HSRP: 'success', - ROLE_GLBP: 'success', - ROLE_CARP: 'success', - } - # # FHRP @@ -171,17 +139,11 @@ class VLANStatusChoices(ChoiceSet): STATUS_DEPRECATED = 'deprecated' CHOICES = [ - (STATUS_ACTIVE, 'Active'), - (STATUS_RESERVED, 'Reserved'), - (STATUS_DEPRECATED, 'Deprecated'), + (STATUS_ACTIVE, 'Active', 'primary'), + (STATUS_RESERVED, 'Reserved', 'info'), + (STATUS_DEPRECATED, 'Deprecated', 'danger'), ] - CSS_CLASSES = { - STATUS_ACTIVE: 'primary', - STATUS_RESERVED: 'info', - STATUS_DEPRECATED: 'danger', - } - # # Services diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index aeb71e70f..0b03cbe79 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -403,7 +403,7 @@ class Prefix(PrimaryModel): prefix_length = property(fset=_set_prefix_length) def get_status_class(self): - return PrefixStatusChoices.CSS_CLASSES.get(self.status) + return PrefixStatusChoices.colors.get(self.status, 'secondary') def get_parents(self, include_self=False): """ @@ -692,7 +692,7 @@ class IPRange(PrimaryModel): prefix_length = property(fset=_set_prefix_length) def get_status_class(self): - return IPRangeStatusChoices.CSS_CLASSES.get(self.status) + return IPRangeStatusChoices.colors.get(self.status, 'secondary') def get_child_ips(self): """ @@ -909,7 +909,7 @@ class IPAddress(PrimaryModel): mask_length = property(fset=_set_mask_length) def get_status_class(self): - return IPAddressStatusChoices.CSS_CLASSES.get(self.status) + return IPAddressStatusChoices.colors.get(self.status, 'secondary') def get_role_class(self): - return IPAddressRoleChoices.CSS_CLASSES.get(self.role) + return IPAddressRoleChoices.colors.get(self.role) diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 1c1691a62..3a1725770 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -173,7 +173,7 @@ class VLAN(PrimaryModel): }) def get_status_class(self): - return VLANStatusChoices.CSS_CLASSES.get(self.status) + return VLANStatusChoices.colors.get(self.status, 'secondary') def get_interfaces(self): # Return all device interfaces assigned to this VLAN diff --git a/netbox/utilities/choices.py b/netbox/utilities/choices.py index 46d74490a..18fd2f5a6 100644 --- a/netbox/utilities/choices.py +++ b/netbox/utilities/choices.py @@ -8,20 +8,37 @@ class ChoiceSetMeta(type): def __new__(mcs, name, bases, attrs): # Extend static choices with any configured choices - if 'key' in attrs: + key = attrs.get('key') + if key: try: - attrs['CHOICES'].extend(settings.FIELD_CHOICES[attrs['key']]) + attrs['CHOICES'].extend(settings.FIELD_CHOICES[key]) except KeyError: pass + # Define choice tuples + # TODO: Support optgroup nesting + attrs['_choices'] = [ + (c[0], c[1]) for c in attrs['CHOICES'] + ] + + # Define color maps + # TODO: Support optgroup nesting + colors = {} + for c in attrs['CHOICES']: + try: + colors[c[0]] = c[2] + except IndexError: + pass + attrs['colors'] = colors + return super().__new__(mcs, name, bases, attrs) def __call__(cls, *args, **kwargs): # Django will check if a 'choices' value is callable, and if so assume that it returns an iterable - return getattr(cls, 'CHOICES', ()) + return getattr(cls, '_choices', ()) def __iter__(cls): - choices = getattr(cls, 'CHOICES', ()) + choices = getattr(cls, '_choices', ()) return iter(choices) diff --git a/netbox/virtualization/choices.py b/netbox/virtualization/choices.py index c121d052e..1aaaf6bf9 100644 --- a/netbox/virtualization/choices.py +++ b/netbox/virtualization/choices.py @@ -16,19 +16,10 @@ class VirtualMachineStatusChoices(ChoiceSet): STATUS_DECOMMISSIONING = 'decommissioning' CHOICES = [ - (STATUS_OFFLINE, 'Offline'), - (STATUS_ACTIVE, 'Active'), - (STATUS_PLANNED, 'Planned'), - (STATUS_STAGED, 'Staged'), - (STATUS_FAILED, 'Failed'), - (STATUS_DECOMMISSIONING, 'Decommissioning'), + (STATUS_OFFLINE, 'Offline', 'warning'), + (STATUS_ACTIVE, 'Active', 'success'), + (STATUS_PLANNED, 'Planned', 'info'), + (STATUS_STAGED, 'Staged', 'primary'), + (STATUS_FAILED, 'Failed', 'danger'), + (STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'), ] - - CSS_CLASSES = { - STATUS_OFFLINE: 'warning', - STATUS_ACTIVE: 'success', - STATUS_PLANNED: 'info', - STATUS_STAGED: 'primary', - STATUS_FAILED: 'danger', - STATUS_DECOMMISSIONING: 'warning', - } diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 5a1bcd42f..b19715127 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -329,7 +329,7 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): }) def get_status_class(self): - return VirtualMachineStatusChoices.CSS_CLASSES.get(self.status) + return VirtualMachineStatusChoices.colors.get(self.status, 'secondary') @property def primary_ip(self): diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 151828c88..2fcfc97aa 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -182,7 +182,7 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): return reverse('wireless:wirelesslink', args=[self.pk]) def get_status_class(self): - return LinkStatusChoices.CSS_CLASSES.get(self.status) + return LinkStatusChoices.colors.get(self.status) def clean(self):