123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import datetime
- from django.conf import settings
- from django.core.exceptions import ValidationError
- from django.core.urlresolvers import reverse
- from django.db import models
- class Document(models.Model):
- """ A document is a scenario or a record from facts, on 1 month.
- """
- TYPE_FACT = 'fact'
- TYPE_PLAN = 'plan'
- name = models.CharField(max_length=130)
- comment = models.TextField(blank=True)
- date = models.DateField(default=datetime.datetime.now)
- type = models.CharField(max_length=10, choices=(
- (TYPE_FACT, 'relevé'),
- (TYPE_PLAN, 'scénario/estimation'),
- ))
- def __str__(self):
- return '{} {:%b %Y}'.format(self.name, self.date)
- def get_absolute_url(self):
- return reverse('detail-document', kwargs={'pk': self.pk})
- class AbstractItem(models.Model):
- name = models.CharField(max_length=130)
- description = models.TextField(blank=True)
- document = models.ForeignKey(Document)
- def __str__(self):
- return self.name
- class Meta:
- abstract = True
- class AbstractResource(AbstractItem):
- UNIT_AMP = 'a'
- UNIT_MBPS = 'mbps'
- UNIT_U = 'u'
- UNIT_IPV4 = 'ipv4'
- UNIT_ETHERNET_PORT = 'eth'
- capacity_unit = models.CharField(
- max_length=10,
- choices=(
- (UNIT_AMP, 'A'),
- (UNIT_MBPS, 'Mbps'),
- (UNIT_U, 'U'),
- (UNIT_IPV4, 'IPv4'),
- (UNIT_ETHERNET_PORT, 'ports'),
- ),
- blank=True,
- )
- total_capacity = models.FloatField(default=1)
- class Meta:
- abstract = True
- def get_use_class(self):
- raise NotImplemented
- def used(self, except_by=None):
- """ Return the used fraction of an item
- :type: Service
- :param except_by: exclude this service from the math
- :rtype: float
- """
- sharing_costs = self.get_use_class().objects.filter(resource=self)
- if except_by:
- sharing_costs = sharing_costs.exclude(service=except_by)
- existing_uses_sum = sum(
- sharing_costs.values_list('share', flat=True))
- return existing_uses_sum
- def used_fraction(self, *args, **kwargs):
- return self.used(*args, **kwargs)/self.total_capacity
- def unused(self):
- return self.total_capacity-self.used()
- def __str__(self):
- if self.capacity_unit == '':
- return self.name
- else:
- return '{} {:.0f} {}'.format(
- self.name, self.total_capacity,
- self.get_capacity_unit_display())
- class Cost(AbstractResource):
- """ A monthtly cost we have to pay
- """
- price = models.FloatField(help_text="Coût mensuel")
- def get_use_class(self):
- return CostUse
- class Meta:
- verbose_name = 'Coût'
- class Good(AbstractResource):
- """ A good, which replacement is provisioned
- """
- price = models.FloatField()
- provisioning_duration = models.DurationField(
- choices=settings.PROVISIONING_DURATIONS)
- def get_use_class(self):
- return GoodUse
- def monthly_provision(self):
- return self.price/self.provisioning_duration.days*(365.25/12)
- class Meta:
- verbose_name = 'Bien'
- class AbstractUse(models.Model):
- share = models.FloatField()
- service = models.ForeignKey('Service')
- class Meta:
- abstract = True
- def clean(self):
- if hasattr(self, 'resource'):
- usage = self.resource.used(except_by=self.service) + self.share
- if usage > self.resource.total_capacity:
- raise ValidationError(
- "Cannot use more than 100% of {})".format(self.resource))
- def real_share(self):
- """The share, + wasted space share
- Taking into account that the unused space is
- wasted and has to be divided among actual users
- """
- return (
- self.share +
- (self.share/self.resource.used())*self.resource.unused()
- )
- def unit_share(self):
- if self.service.subscriptions_count == 0:
- return 0
- else:
- return self.share/self.service.subscriptions_count
- return
- def unit_real_share(self):
- if self.service.subscriptions_count == 0:
- return 0
- else:
- return self.real_share()/self.service.subscriptions_count
- def value_share(self):
- return (
- self.resource.price
- * self.real_share()
- / self.resource.total_capacity
- )
- def unit_value_share(self):
- if self.service.subscriptions_count == 0:
- return 0
- else:
- return self.value_share()/self.service.subscriptions_count
- class CostUse(AbstractUse):
- resource = models.ForeignKey(Cost)
- def cost_share(self):
- return (
- self.real_share() / self.resource.total_capacity
- * self.resource.price
- )
- def unit_cost_share(self):
- subscriptions_count = self.service.subscriptions_count
- if subscriptions_count == 0:
- return 0
- else:
- return self.cost_share()/self.service.subscriptions_count
- class GoodUse(AbstractUse):
- resource = models.ForeignKey(Good)
- def monthly_provision_share(self):
- return (
- self.real_share()
- * self.resource.monthly_provision()
- / self.resource.total_capacity)
- def unit_monthly_provision_share(self):
- subscriptions_count = self.service.subscriptions_count
- monthly_share = self.monthly_provision_share()
- if subscriptions_count == 0:
- return 0
- else:
- return monthly_share/subscriptions_count
- class Service(AbstractItem):
- """ A service we sell
- (considered monthly)
- """
- costs = models.ManyToManyField(
- Cost,
- through=CostUse,
- related_name='using_services')
- goods = models.ManyToManyField(
- Good,
- through=GoodUse,
- related_name='using_services')
- # services = models.ManyToMany('Service') #TODO
- subscriptions_count = models.PositiveIntegerField(default=0)
- def get_absolute_url(self):
- return reverse('detail-service', kwargs={'pk': self.pk})
|