| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- from django.core.exceptions import ValidationError
- from django.core.validators import MaxValueValidator, MinValueValidator
- from django.db import models
- from django.urls import reverse
- from taggit.managers import TaggableManager
- from dcim.choices import *
- from dcim.constants import *
- from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
- from extras.utils import extras_features
- from utilities.querysets import RestrictedQuerySet
- from utilities.validators import ExclusionValidator
- from .device_components import CableTermination
- __all__ = (
- 'PowerFeed',
- 'PowerPanel',
- )
- #
- # Power
- #
- @extras_features('custom_links', 'export_templates', 'webhooks')
- class PowerPanel(ChangeLoggedModel):
- """
- A distribution point for electrical power; e.g. a data center RPP.
- """
- site = models.ForeignKey(
- to='Site',
- on_delete=models.PROTECT
- )
- rack_group = models.ForeignKey(
- to='RackGroup',
- on_delete=models.PROTECT,
- blank=True,
- null=True
- )
- name = models.CharField(
- max_length=50
- )
- tags = TaggableManager(through=TaggedItem)
- objects = RestrictedQuerySet.as_manager()
- csv_headers = ['site', 'rack_group', 'name']
- 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 to_csv(self):
- return (
- self.site.name,
- self.rack_group.name if self.rack_group else None,
- self.name,
- )
- def clean(self):
- # RackGroup must belong to assigned Site
- if self.rack_group and self.rack_group.site != self.site:
- raise ValidationError("Rack group {} ({}) is in a different site than {}".format(
- self.rack_group, self.rack_group.site, self.site
- ))
- @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
- class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
- """
- 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
- )
- connected_endpoint = models.OneToOneField(
- to='dcim.PowerPort',
- on_delete=models.SET_NULL,
- related_name='+',
- blank=True,
- null=True
- )
- connection_status = models.BooleanField(
- choices=CONNECTION_STATUS_CHOICES,
- blank=True,
- null=True
- )
- name = models.CharField(
- max_length=50
- )
- 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=POWERFEED_VOLTAGE_DEFAULT,
- validators=[ExclusionValidator([0])]
- )
- amperage = models.PositiveSmallIntegerField(
- validators=[MinValueValidator(1)],
- default=POWERFEED_AMPERAGE_DEFAULT
- )
- max_utilization = models.PositiveSmallIntegerField(
- validators=[MinValueValidator(1), MaxValueValidator(100)],
- default=POWERFEED_MAX_UTILIZATION_DEFAULT,
- help_text="Maximum permissible draw (percentage)"
- )
- available_power = models.PositiveIntegerField(
- default=0,
- editable=False
- )
- comments = models.TextField(
- blank=True
- )
- tags = TaggableManager(through=TaggedItem)
- objects = RestrictedQuerySet.as_manager()
- csv_headers = [
- 'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
- 'amperage', 'max_utilization', 'comments',
- ]
- clone_fields = [
- 'power_panel', 'rack', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization',
- 'available_power',
- ]
- STATUS_CLASS_MAP = {
- PowerFeedStatusChoices.STATUS_OFFLINE: 'warning',
- PowerFeedStatusChoices.STATUS_ACTIVE: 'success',
- PowerFeedStatusChoices.STATUS_PLANNED: 'info',
- PowerFeedStatusChoices.STATUS_FAILED: 'danger',
- }
- TYPE_CLASS_MAP = {
- PowerFeedTypeChoices.TYPE_PRIMARY: 'success',
- PowerFeedTypeChoices.TYPE_REDUNDANT: 'info',
- }
- 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 to_csv(self):
- return (
- self.power_panel.site.name,
- self.power_panel.name,
- self.rack.group.name if self.rack and self.rack.group else None,
- self.rack.name if self.rack else None,
- self.name,
- self.get_status_display(),
- self.get_type_display(),
- self.get_supply_display(),
- self.get_phase_display(),
- self.voltage,
- self.amperage,
- self.max_utilization,
- self.comments,
- )
- def clean(self):
- # 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(self):
- return self.power_panel
- def get_type_class(self):
- return self.TYPE_CLASS_MAP.get(self.type)
- def get_status_class(self):
- return self.STATUS_CLASS_MAP.get(self.status)
|