# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime from django.conf import settings from django.db import models from django.db.models import Count, Q from django.core.validators import MinValueValidator from django.contrib.contenttypes.models import ContentType class OfferManager(models.Manager): def manageable_by(self, user): """" Renvoie la liste des offres dont l'utilisateur est autorisé à voir les membres et les abonnements dans l'interface d'administration. """ from coin.members.models import RowLevelPermission # toutes les permissions appliquées à cet utilisateur # (liste de chaines de caractères) perms = user.get_all_permissions() allowedcodenames = [ s.split('offers.',1)[1] for s in perms if s.startswith('offers.')] # parmi toutes les RowLevelPermission, celles qui sont relatives à des OfferSubscription et qui sont dans allowedcodenames rowperms = RowLevelPermission.objects.filter(content_type=ContentType.objects.get_for_model(OfferSubscription), codename__in=allowedcodenames) # toutes les Offers pour lesquelles il existe une RowLevelpermission correspondante dans rowperms return super(OfferManager, self).filter(rowlevelpermission__in=rowperms).distinct() class Offer(models.Model): """Description of an offer available to subscribers. Implementation notes: configuration_type store the model name of the configuration backend (ex VPNConfiguration). The choices list is dynamically generated at start in the __init__ """ name = models.CharField(max_length=255, blank=False, null=False, verbose_name="nom de l'offre") reference = models.CharField(max_length=255, blank=True, verbose_name="référence de l'offre", help_text="Identifiant a utiliser par exemple comme identifiant de virement") configuration_type = models.CharField(max_length=50, blank=True, verbose_name='type de configuration', help_text="Type de configuration à utiliser avec cette offre") billing_period = models.IntegerField(blank=False, null=False, default=1, verbose_name='période de facturation', help_text='en mois', validators=[MinValueValidator(1)]) period_fees = models.DecimalField(max_digits=5, decimal_places=2, blank=False, null=False, verbose_name='montant par période de ' 'facturation', help_text='en €') initial_fees = models.DecimalField(max_digits=5, decimal_places=2, blank=False, null=False, verbose_name='frais de mise en service', help_text='en €') non_billable = models.BooleanField(default=False, verbose_name='n\'est pas facturable', help_text='L\'offre ne sera pas facturée par la commande charge_members') objects = OfferManager() def get_configuration_type_display(self): """ Renvoi le nom affichable du type de configuration """ from coin.configuration.models import Configuration for item in Configuration.get_configurations_choices_list(): if item and self.configuration_type in item: return item[1] return self.configuration_type get_configuration_type_display.short_description = 'type de configuration' def display_price(self): """Displays the price of an offer in a human-readable manner (for instance "30€ / month") """ if int(self.period_fees) == self.period_fees: fee = int(self.period_fees) else: fee = self.period_fees if self.billing_period == 1: period = "" else: period = self.billing_period return "{period_fee}€ / {billing_period} mois".format( period_fee=fee, billing_period=period) def __unicode__(self): return '{name} - {price}'.format(name=self.name, price=self.display_price()) class Meta: verbose_name = 'offre' class OfferSubscriptionQuerySet(models.QuerySet): def running(self, at_date=None): """ Only the running contracts at a given date. Running mean already started and not stopped yet """ if at_date is None: at_date = datetime.date.today() return self.filter(Q(subscription_date__lte=at_date) & (Q(resign_date__gt=at_date) | Q(resign_date__isnull=True))) def offer_summary(self): """ Agregates as a count of subscriptions per offer """ return self.values('offer__name', 'offer__reference').annotate( num_subscriptions=Count('offer')) class OfferSubscription(models.Model): """Only contains administrative details about a subscription, not technical. Nothing here should end up into the LDAP backend. Implementation notes: the Configuration model (which actually implementing the backend (technical configuration for the technology)) relate to this class with a OneToOneField """ subscription_date = models.DateField( null=False, blank=False, default=datetime.date.today, verbose_name="date de souscription à l'offre") # TODO: for data retention, prevent deletion of a subscription object # while the resign date is recent enough (e.g. one year in France). resign_date = models.DateField( null=True, blank=True, verbose_name='date de résiliation') # TODO: move this to offers? commitment = models.IntegerField(blank=False, null=False, verbose_name="période d'engagement", help_text='en mois', validators=[MinValueValidator(0)], default=0) comments = models.TextField(blank=True, verbose_name='commentaires', help_text="Commentaires libres (informations" " spécifiques concernant l'abonnement)") member = models.ForeignKey('members.Member', verbose_name='membre') offer = models.ForeignKey('Offer', verbose_name='offre') objects = OfferSubscriptionQuerySet().as_manager() def get_subscription_reference(self): return settings.SUBSCRIPTION_REFERENCE.format(subscription=self) get_subscription_reference.short_description = 'Référence' def __unicode__(self): return '%s - %s - %s' % (self.member, self.offer.name, self.subscription_date) class Meta: verbose_name = 'abonnement' def count_active_subscriptions(): today = datetime.date.today() query = Q(subscription_date__lte=today) & (Q(resign_date__isnull=True) | Q(resign_date__gte=today)) return OfferSubscription.objects.filter(query).count()