mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
212 lines
6.2 KiB
Python
212 lines
6.2 KiB
Python
from django.core.exceptions import ValidationError
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from dcim.choices import *
|
|
from netbox.config import ConfigItem
|
|
from netbox.models import PrimaryModel
|
|
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
|
|
from utilities.validators import ExclusionValidator
|
|
from .device_components import CabledObjectModel, PathEndpoint
|
|
|
|
__all__ = (
|
|
'PowerFeed',
|
|
'PowerPanel',
|
|
)
|
|
|
|
|
|
#
|
|
# Power
|
|
#
|
|
|
|
class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
|
|
"""
|
|
A distribution point for electrical power; e.g. a data center RPP.
|
|
"""
|
|
site = models.ForeignKey(
|
|
to='Site',
|
|
on_delete=models.PROTECT
|
|
)
|
|
location = models.ForeignKey(
|
|
to='dcim.Location',
|
|
on_delete=models.PROTECT,
|
|
blank=True,
|
|
null=True
|
|
)
|
|
name = models.CharField(
|
|
verbose_name=_('name'),
|
|
max_length=100
|
|
)
|
|
|
|
prerequisite_models = (
|
|
'dcim.Site',
|
|
)
|
|
|
|
class Meta:
|
|
ordering = ['site', 'name']
|
|
constraints = (
|
|
models.UniqueConstraint(
|
|
fields=('site', 'name'),
|
|
name='%(app_label)s_%(class)s_unique_site_name'
|
|
),
|
|
)
|
|
verbose_name = _('power panel')
|
|
verbose_name_plural = _('power panels')
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('dcim:powerpanel', args=[self.pk])
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
|
|
# Location must belong to assigned Site
|
|
if self.location and self.location.site != self.site:
|
|
raise ValidationError(
|
|
_("Location {location} ({location_site}) is in a different site than {site}").format(
|
|
location=self.location, location_site=self.location.site, site=self.site)
|
|
)
|
|
|
|
|
|
class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
|
|
"""
|
|
An electrical circuit delivered from a PowerPanel.
|
|
"""
|
|
power_panel = models.ForeignKey(
|
|
to='PowerPanel',
|
|
on_delete=models.PROTECT,
|
|
related_name='powerfeeds'
|
|
)
|
|
rack = models.ForeignKey(
|
|
to='Rack',
|
|
on_delete=models.PROTECT,
|
|
blank=True,
|
|
null=True
|
|
)
|
|
name = models.CharField(
|
|
verbose_name=_('name'),
|
|
max_length=100
|
|
)
|
|
status = models.CharField(
|
|
verbose_name=_('status'),
|
|
max_length=50,
|
|
choices=PowerFeedStatusChoices,
|
|
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
|
)
|
|
type = models.CharField(
|
|
verbose_name=_('type'),
|
|
max_length=50,
|
|
choices=PowerFeedTypeChoices,
|
|
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
|
)
|
|
supply = models.CharField(
|
|
verbose_name=_('supply'),
|
|
max_length=50,
|
|
choices=PowerFeedSupplyChoices,
|
|
default=PowerFeedSupplyChoices.SUPPLY_AC
|
|
)
|
|
phase = models.CharField(
|
|
verbose_name=_('phase'),
|
|
max_length=50,
|
|
choices=PowerFeedPhaseChoices,
|
|
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
|
)
|
|
voltage = models.SmallIntegerField(
|
|
verbose_name=_('voltage'),
|
|
default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'),
|
|
validators=[ExclusionValidator([0])]
|
|
)
|
|
amperage = models.PositiveSmallIntegerField(
|
|
verbose_name=_('amperage'),
|
|
validators=[MinValueValidator(1)],
|
|
default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE')
|
|
)
|
|
max_utilization = models.PositiveSmallIntegerField(
|
|
verbose_name=_('max utilization'),
|
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
|
default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
|
|
help_text=_("Maximum permissible draw (percentage)")
|
|
)
|
|
available_power = models.PositiveIntegerField(
|
|
verbose_name=_('available power'),
|
|
default=0,
|
|
editable=False
|
|
)
|
|
tenant = models.ForeignKey(
|
|
to='tenancy.Tenant',
|
|
on_delete=models.PROTECT,
|
|
related_name='power_feeds',
|
|
blank=True,
|
|
null=True
|
|
)
|
|
|
|
clone_fields = (
|
|
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
|
'max_utilization', 'tenant',
|
|
)
|
|
prerequisite_models = (
|
|
'dcim.PowerPanel',
|
|
)
|
|
|
|
class Meta:
|
|
ordering = ['power_panel', 'name']
|
|
constraints = (
|
|
models.UniqueConstraint(
|
|
fields=('power_panel', 'name'),
|
|
name='%(app_label)s_%(class)s_unique_power_panel_name'
|
|
),
|
|
)
|
|
verbose_name = _('power feed')
|
|
verbose_name_plural = _('power feeds')
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('dcim:powerfeed', args=[self.pk])
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
|
|
# Rack must belong to same Site as PowerPanel
|
|
if self.rack and self.rack.site != self.power_panel.site:
|
|
raise ValidationError(_(
|
|
"Rack {rack} ({rack_site}) and power panel {powerpanel} ({powerpanel_site}) are in different sites."
|
|
).format(
|
|
rack=self.rack,
|
|
rack_site=self.rack.site,
|
|
powerpanel=self.power_panel,
|
|
powerpanel_site=self.power_panel.site
|
|
))
|
|
|
|
# AC voltage cannot be negative
|
|
if self.voltage < 0 and self.supply == PowerFeedSupplyChoices.SUPPLY_AC:
|
|
raise ValidationError({
|
|
"voltage": _("Voltage cannot be negative for AC supply")
|
|
})
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
# Cache the available_power property on the instance
|
|
kva = abs(self.voltage) * self.amperage * (self.max_utilization / 100)
|
|
if self.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
|
|
self.available_power = round(kva * 1.732)
|
|
else:
|
|
self.available_power = round(kva)
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
@property
|
|
def parent_object(self):
|
|
return self.power_panel
|
|
|
|
def get_type_color(self):
|
|
return PowerFeedTypeChoices.colors.get(self.type)
|
|
|
|
def get_status_color(self):
|
|
return PowerFeedStatusChoices.colors.get(self.status)
|