import datetime from collections import defaultdict from django.utils import timezone as tz from django.db import models # Create your models here. class Etiquette(models.Model): nom = models.CharField(max_length=128, unique=True) def __str__(self): return "%s" % self.nom def get_placeholder_label(): print("get placeholder") return Etiquette.objects.get_or_create(nom="Mettez-moi à jour")[0] class MonthManager(models.Manager): def get_queryset_for_month(self, month, year): return super().get_queryset().filter(date__month=month, date__year=year) def get_queryset(self, month=None, year=None): """ Add custom 'month' and 'year' keyword arguments, used as filters. Without argument, simply proxy to parent method. This method checks if month is newly created (first access). If so, it spawns recursive records for this month. """ # Generic call if not month and not year: return super().get_queryset() # Custom call elif month and year: recursifs = EnregistrementRecursif.objects.all() if (not self.get_queryset_for_month(month, year).exists() and recursifs.exists()): print("LOG: Month is created so we populated it...") for r in recursifs: r.spawn(month, year) return self.get_queryset_for_month(month, year) # Invalid call else: raise TypeError( "You must call with either none or \ both 'month' and 'year' keyword arguments") def _populate_data(self, qs, data): def somme_par_etiquette(qs): sommes = defaultdict(lambda:0) for el in qs: sommes[el.etiquette.nom] += el.montant return dict(sommes) data['balance'] = sum(map(lambda t: t[0], qs.values_list('montant'))) data['par_etiquette'] = somme_par_etiquette(qs) return data def calculate_data(self, qs): #TODO: cached results return self._populate_data(qs, {}) def get_month_with_data(self, month, year): """ Retrieve the queryset for given month and year, plus some data calculated on the set """ qs = self.get_queryset(month=month, year=year) data = self.calculate_data(qs) return qs, data class Enregistrement(models.Model): objects = MonthManager() date = models.DateField() montant = models.FloatField() etiquette = models.ForeignKey(Etiquette, on_delete=models.SET(get_placeholder_label)) description = models.CharField(max_length=512) def __str__(self): return "" % self.etiquette class Meta: ordering = ('date',) class EnregistrementRecursif(models.Model): jour = models.IntegerField() montant = models.FloatField() etiquette = models.ForeignKey(Etiquette, on_delete=models.SET(get_placeholder_label)) description = models.CharField(max_length=512) created_date = models.DateField() class Meta: ordering = ('jour',) def is_older(self, month, year): return (self.created_date.year < year or (self.created_date.month < month and self.created_date.year == year)) def spawn(self, month, year, commit=True): """ Spawn a new Enregistrement for given month and year. """ # Do not spawn for dates older than this instance creation date if not self.is_older(month, year): return None # Create new Enregistrement from this instance values date = datetime.date(year, month, self.jour) new_object, created = Enregistrement.objects.get_or_create(date=date, montant=self.montant, etiquette=self.etiquette, description=self.description) if created and commit: new_object.save() return new_object def save(self, **kwargs): """ Update 'created_date' on each save, this avoids conflict with older spawned instances. """ self.created_date = tz.now().date() return super().save(**kwargs) def __str__(self): return "<[%i] %s (%s) : %s {%s}>" % (self.jour, self.description, self.etiquette, self.montant, self.created_date)