From c7bd733911569e6989553bc4f67921f72c224837 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 6 Jul 2025 18:11:09 +0200 Subject: [PATCH] Models fixed --- apps/family/migrations/0001_initial.py | 30 +++---- apps/family/models.py | 105 ++++++++++++------------- 2 files changed, 59 insertions(+), 76 deletions(-) diff --git a/apps/family/migrations/0001_initial.py b/apps/family/migrations/0001_initial.py index 86c7c135..6a9c2357 100644 --- a/apps/family/migrations/0001_initial.py +++ b/apps/family/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.21 on 2025-07-04 19:05 +# Generated by Django 4.2.21 on 2025-07-06 16:07 from django.conf import settings from django.db import migrations, models @@ -16,14 +16,17 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='ChallengeCategory', + name='Challenge', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, unique=True, verbose_name='name')), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('description', models.CharField(max_length=255, verbose_name='description')), + ('points', models.PositiveIntegerField(verbose_name='points')), + ('obtained', models.PositiveIntegerField(default=0, verbose_name='obtained')), ], options={ - 'verbose_name': 'challenge category', - 'verbose_name_plural': 'challenge categories', + 'verbose_name': 'challenge', + 'verbose_name_plural': 'challenges', }, ), migrations.CreateModel( @@ -32,7 +35,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255, unique=True, verbose_name='name')), ('description', models.CharField(max_length=255, verbose_name='description')), - ('score', models.PositiveIntegerField(verbose_name='score')), + ('score', models.PositiveIntegerField(default=0, verbose_name='score')), ('rank', models.PositiveIntegerField(verbose_name='rank')), ], options={ @@ -40,21 +43,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Families', }, ), - migrations.CreateModel( - name='Challenge', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('description', models.CharField(max_length=255, verbose_name='description')), - ('points', models.PositiveIntegerField(verbose_name='points')), - ('obtained', models.PositiveIntegerField(verbose_name='obtained')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challengecategory', verbose_name='category')), - ], - options={ - 'verbose_name': 'challenge', - 'verbose_name_plural': 'challenges', - }, - ), migrations.CreateModel( name='Achievement', fields=[ diff --git a/apps/family/models.py b/apps/family/models.py index 2b10999b..46e8af47 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -11,16 +11,17 @@ class Family(models.Model): name = models.CharField( max_length=255, verbose_name=_('name'), - unique=True + unique=True, ) description = models.CharField( max_length=255, - verbose_name=_('description') + verbose_name=_('description'), ) score = models.PositiveIntegerField( - verbose_name=_('score') + verbose_name=_('score'), + default=0, ) rank = models.PositiveIntegerField( @@ -34,6 +35,36 @@ class Family(models.Model): def __str__(self): return self.name + def update_score(self, *args, **kwargs): + challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self) + points_sum = challenge_set.aggregate(models.Sum("points")) + self.score = points_sum["points__sum"] + self.save() + self.update_ranking() + + @staticmethod + def update_ranking(*args, **kwargs): + """ + Update ranking when adding or removing points + """ + family_set = Family.objects.select_for_update().all().order_by("-score") + for i in range(family_set.count()): + if i == 0 or family_set[i].score != family_set[i - 1].score: + new_rank = i + 1 + family = family_set[i] + family.rank = new_rank + family._force_save = True + family.save() + + def save(self, *args, **kwargs): + if self.rank is None: + last_family = Family.objects.order_by("rank").last() + if last_family is None or last_family.score > self.score: + self.rank = Family.objects.count() + 1 + else: + self.rank = last_family.rank + super().save(*args, **kwargs) + class FamilyMembership(models.Model): user = models.OneToOneField( @@ -64,21 +95,6 @@ class FamilyMembership(models.Model): return _('Family membership of {user} to {family}').format(user=self.user.username, family=self.family.name, ) -class ChallengeCategory(models.Model): - name = models.CharField( - max_length=255, - verbose_name=_('name'), - unique=True, - ) - - class Meta: - verbose_name = _('challenge category') - verbose_name_plural = _('challenge categories') - - def __str__(self): - return self.name - - class Challenge(models.Model): name = models.CharField( max_length=255, @@ -94,15 +110,18 @@ class Challenge(models.Model): verbose_name=_('points'), ) - category = models.ForeignKey( - ChallengeCategory, - verbose_name=_('category'), - on_delete=models.PROTECT + obtained = models.PositiveIntegerField( + verbose_name=_('obtained'), + default=0, ) - obtained = models.PositiveIntegerField( - verbose_name=_('obtained') - ) + @transaction.atomic + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + # Update families who already obtained this challenge + achievements = Achievement.objects.filter(challenge=self) + for achievement in achievements: + achievement.save() class Meta: verbose_name = _('challenge') @@ -136,20 +155,6 @@ class Achievement(models.Model): def __str__(self): return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, ) - @classmethod - def update_ranking(cls, *args, **kwargs): - """ - Update ranking when adding or removing points - """ - family_set = cls.objects.select_for_update().all().order_by("-score") - for i in range(family_set.count()): - if i == 0 or family_set[i].score != family_set[i - 1].score: - new_rank = i + 1 - family = family_set[i] - family.rank = new_rank - family._force_save = True - family.save() - @transaction.atomic def save(self, *args, **kwargs): """ @@ -157,25 +162,20 @@ class Achievement(models.Model): """ self.family = Family.objects.select_for_update().get(pk=self.family_id) self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id) - challenge_points = self.challenge.points is_new = self.pk is None - super.save(*args, **kwargs) + super().save(*args, **kwargs) - # Only grant points when getting a new achievement + self.family.refresh_from_db() + self.family.update_score() + + # Count only when getting a new achievement if is_new: - self.family.refresh_from_db() - self.family.score += challenge_points - self.family._force_save = True - self.family.save() - self.challenge.refresh_from_db() self.challenge.obtained += 1 self.challenge._force_save = True self.challenge.save() - self.__class__.update_ranking() - @transaction.atomic def delete(self, *args, **kwargs): """ @@ -183,20 +183,15 @@ class Achievement(models.Model): """ # Get the family and challenge before deletion self.family = Family.objects.select_for_update().get(pk=self.family_id) - challenge_points = self.challenge.points # Delete the achievement super().delete(*args, **kwargs) # Remove points from the family self.family.refresh_from_db() - self.family.score -= challenge_points - self.family._force_save = True - self.family.save() + self.family.update_score() self.challenge.refresh_from_db() self.challenge.obtained -= 1 self.challenge._force_save = True self.challenge.save() - - self.__class__.update_ranking()