models.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. from django.conf import settings
  5. from django.db import models
  6. from django.db.models import Count, Q
  7. from django.core.validators import MinValueValidator
  8. from django.contrib.contenttypes.models import ContentType
  9. class OfferManager(models.Manager):
  10. def manageable_by(self, user):
  11. """" Renvoie la liste des offres dont l'utilisateur est autorisé à
  12. voir les membres et les abonnements dans l'interface d'administration.
  13. """
  14. from coin.members.models import RowLevelPermission
  15. # toutes les permissions appliquées à cet utilisateur
  16. # (liste de chaines de caractères)
  17. perms = user.get_all_permissions()
  18. allowedcodenames = [ s.split('offers.',1)[1] for s in perms if s.startswith('offers.')]
  19. # parmi toutes les RowLevelPermission, celles qui sont relatives à des OfferSubscription et qui sont dans allowedcodenames
  20. rowperms = RowLevelPermission.objects.filter(content_type=ContentType.objects.get_for_model(OfferSubscription), codename__in=allowedcodenames)
  21. # toutes les Offers pour lesquelles il existe une RowLevelpermission correspondante dans rowperms
  22. return super(OfferManager, self).filter(rowlevelpermission__in=rowperms).distinct()
  23. class Offer(models.Model):
  24. """Description of an offer available to subscribers.
  25. Implementation notes:
  26. configuration_type store the model name of the configuration backend
  27. (ex VPNConfiguration).
  28. The choices list is dynamically generated at start in the __init__
  29. """
  30. name = models.CharField(max_length=255, blank=False, null=False,
  31. verbose_name="nom de l'offre")
  32. reference = models.CharField(max_length=255, blank=True,
  33. verbose_name="référence de l'offre",
  34. help_text="Identifiant a utiliser par exemple comme identifiant de virement")
  35. configuration_type = models.CharField(max_length=50,
  36. blank=True,
  37. verbose_name='type de configuration',
  38. help_text="Type de configuration à utiliser avec cette offre")
  39. billing_period = models.IntegerField(blank=False, null=False, default=1,
  40. verbose_name='période de facturation',
  41. help_text='en mois',
  42. validators=[MinValueValidator(1)])
  43. period_fees = models.DecimalField(max_digits=5, decimal_places=2,
  44. blank=False, null=False,
  45. verbose_name='montant par période de '
  46. 'facturation',
  47. help_text='en €')
  48. initial_fees = models.DecimalField(max_digits=5, decimal_places=2,
  49. blank=False, null=False,
  50. verbose_name='frais de mise en service',
  51. help_text='en €')
  52. non_billable = models.BooleanField(default=False,
  53. verbose_name='n\'est pas facturable',
  54. help_text='L\'offre ne sera pas facturée par la commande charge_members')
  55. objects = OfferManager()
  56. def get_configuration_type_display(self):
  57. """
  58. Renvoi le nom affichable du type de configuration
  59. """
  60. from coin.configuration.models import Configuration
  61. for item in Configuration.get_configurations_choices_list():
  62. if item and self.configuration_type in item:
  63. return item[1]
  64. return self.configuration_type
  65. get_configuration_type_display.short_description = 'type de configuration'
  66. def display_price(self):
  67. """Displays the price of an offer in a human-readable manner
  68. (for instance "30€ / month")
  69. """
  70. if int(self.period_fees) == self.period_fees:
  71. fee = int(self.period_fees)
  72. else:
  73. fee = self.period_fees
  74. if self.billing_period == 1:
  75. period = ""
  76. else:
  77. period = self.billing_period
  78. return "{period_fee}€ / {billing_period} mois".format(
  79. period_fee=fee,
  80. billing_period=period)
  81. def __unicode__(self):
  82. return '{name} - {price}'.format(name=self.name,
  83. price=self.display_price())
  84. class Meta:
  85. verbose_name = 'offre'
  86. class OfferSubscriptionQuerySet(models.QuerySet):
  87. def running(self, at_date=None):
  88. """ Only the running contracts at a given date.
  89. Running mean already started and not stopped yet
  90. """
  91. if at_date is None:
  92. at_date = datetime.date.today()
  93. return self.filter(Q(subscription_date__lte=at_date) &
  94. (Q(resign_date__gt=at_date) |
  95. Q(resign_date__isnull=True)))
  96. def offer_summary(self):
  97. """ Agregates as a count of subscriptions per offer
  98. """
  99. return self.values('offer__name', 'offer__reference').annotate(
  100. num_subscriptions=Count('offer'))
  101. class OfferSubscription(models.Model):
  102. """Only contains administrative details about a subscription, not
  103. technical. Nothing here should end up into the LDAP backend.
  104. Implementation notes: the Configuration model (which actually implementing the backend
  105. (technical configuration for the technology)) relate to this class
  106. with a OneToOneField
  107. """
  108. subscription_date = models.DateField(
  109. null=False,
  110. blank=False,
  111. default=datetime.date.today,
  112. verbose_name="date de souscription à l'offre")
  113. # TODO: for data retention, prevent deletion of a subscription object
  114. # while the resign date is recent enough (e.g. one year in France).
  115. resign_date = models.DateField(
  116. null=True,
  117. blank=True,
  118. verbose_name='date de résiliation')
  119. # TODO: move this to offers?
  120. commitment = models.IntegerField(blank=False, null=False,
  121. verbose_name="période d'engagement",
  122. help_text='en mois',
  123. validators=[MinValueValidator(0)],
  124. default=0)
  125. comments = models.TextField(blank=True, verbose_name='commentaires',
  126. help_text="Commentaires libres (informations"
  127. " spécifiques concernant l'abonnement)")
  128. member = models.ForeignKey('members.Member', verbose_name='membre')
  129. offer = models.ForeignKey('Offer', verbose_name='offre')
  130. objects = OfferSubscriptionQuerySet().as_manager()
  131. def get_subscription_reference(self):
  132. return settings.SUBSCRIPTION_REFERENCE.format(subscription=self)
  133. get_subscription_reference.short_description = 'Référence'
  134. def __unicode__(self):
  135. return '%s - %s - %s' % (self.member, self.offer.name,
  136. self.subscription_date)
  137. class Meta:
  138. verbose_name = 'abonnement'
  139. def count_active_subscriptions():
  140. today = datetime.date.today()
  141. query = Q(subscription_date__lte=today) & (Q(resign_date__isnull=True) | Q(resign_date__gte=today))
  142. return OfferSubscription.objects.filter(query).count()