mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
179 lines
5.0 KiB
Python
179 lines
5.0 KiB
Python
from django.contrib.contenttypes.fields import GenericRelation
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
|
|
from dcim.choices import *
|
|
from dcim.constants import *
|
|
from netbox.config import ConfigItem
|
|
from netbox.models import NetBoxModel
|
|
from utilities.validators import ExclusionValidator
|
|
from .device_components import LinkTermination, PathEndpoint
|
|
|
|
__all__ = (
|
|
'PowerFeed',
|
|
'PowerPanel',
|
|
)
|
|
|
|
|
|
#
|
|
# Power
|
|
#
|
|
|
|
class PowerPanel(NetBoxModel):
|
|
"""
|
|
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(
|
|
max_length=100
|
|
)
|
|
|
|
# Generic relations
|
|
contacts = GenericRelation(
|
|
to='tenancy.ContactAssignment'
|
|
)
|
|
images = GenericRelation(
|
|
to='extras.ImageAttachment'
|
|
)
|
|
|
|
class Meta:
|
|
ordering = ['site', 'name']
|
|
unique_together = ['site', 'name']
|
|
|
|
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(
|
|
f"Location {self.location} ({self.location.site}) is in a different site than {self.site}"
|
|
)
|
|
|
|
|
|
class PowerFeed(NetBoxModel, PathEndpoint, LinkTermination):
|
|
"""
|
|
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(
|
|
max_length=100
|
|
)
|
|
status = models.CharField(
|
|
max_length=50,
|
|
choices=PowerFeedStatusChoices,
|
|
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
|
)
|
|
type = models.CharField(
|
|
max_length=50,
|
|
choices=PowerFeedTypeChoices,
|
|
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
|
)
|
|
supply = models.CharField(
|
|
max_length=50,
|
|
choices=PowerFeedSupplyChoices,
|
|
default=PowerFeedSupplyChoices.SUPPLY_AC
|
|
)
|
|
phase = models.CharField(
|
|
max_length=50,
|
|
choices=PowerFeedPhaseChoices,
|
|
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
|
)
|
|
voltage = models.SmallIntegerField(
|
|
default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'),
|
|
validators=[ExclusionValidator([0])]
|
|
)
|
|
amperage = models.PositiveSmallIntegerField(
|
|
validators=[MinValueValidator(1)],
|
|
default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE')
|
|
)
|
|
max_utilization = models.PositiveSmallIntegerField(
|
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
|
default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
|
|
help_text="Maximum permissible draw (percentage)"
|
|
)
|
|
available_power = models.PositiveIntegerField(
|
|
default=0,
|
|
editable=False
|
|
)
|
|
comments = models.TextField(
|
|
blank=True
|
|
)
|
|
|
|
clone_fields = [
|
|
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
|
'max_utilization', 'available_power',
|
|
]
|
|
|
|
class Meta:
|
|
ordering = ['power_panel', 'name']
|
|
unique_together = ['power_panel', 'name']
|
|
|
|
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 {} ({}) and power panel {} ({}) are in different sites".format(
|
|
self.rack, self.rack.site, self.power_panel, 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)
|