diff --git a/apps/family/api/urls.py b/apps/family/api/urls.py
index b231ef99..e94776d7 100644
--- a/apps/family/api/urls.py
+++ b/apps/family/api/urls.py
@@ -1,7 +1,7 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
-
-from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet
+from django.urls import path
+from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet, BatchAchievementsAPIView
def register_family_urls(router, path):
@@ -12,3 +12,7 @@ def register_family_urls(router, path):
router.register(path + '/familymembership', FamilyMembershipViewSet)
router.register(path + '/challenge', ChallengeViewSet)
router.register(path + '/achievement', AchievementViewSet)
+
+urlpatterns = [
+ path('achievements/batch/', BatchAchievementsAPIView.as_view(), name='batch_achievements')
+]
\ No newline at end of file
diff --git a/apps/family/api/views.py b/apps/family/api/views.py
index f2fe0208..d568c1c6 100644
--- a/apps/family/api/views.py
+++ b/apps/family/api/views.py
@@ -4,6 +4,14 @@
from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter
+from rest_framework.views import APIView
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework import status
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_POST
+from django.http import JsonResponse
+import json
from .serializers import FamilySerializer, FamilyMembershipSerializer, ChallengeSerializer, AchievementSerializer
from ..models import Family, FamilyMembership, Challenge, Achievement
@@ -59,3 +67,25 @@ class AchievementViewSet(ReadProtectedModelViewSet):
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', ]
search_fields = ['$name', ]
+
+
+class BatchAchievementsAPIView(APIView):
+ permission_classes = [IsAuthenticated]
+ def post(self, request, format=None):
+ print("POST de la view spéciale")
+ family_ids = request.data.get('families', [])
+ challenge_ids = request.data.get('challenges', [])
+
+ families = Family.objects.filter(id__in=family_ids)
+ challenges = Challenge.objects.filter(id__in=challenge_ids)
+
+ for family in families:
+ for challenge in challenges:
+ a = Achievement(family=family, challenge=challenge)
+ a.save(update_score=False)
+
+ for family in families:
+ family.update_score()
+ Family.update_ranking()
+
+ return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)
\ No newline at end of file
diff --git a/apps/family/models.py b/apps/family/models.py
index 1d2d0d34..1acc9ba8 100644
--- a/apps/family/models.py
+++ b/apps/family/models.py
@@ -165,7 +165,7 @@ class Achievement(models.Model):
return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, )
@transaction.atomic
- def save(self, *args, **kwargs):
+ def save(self, *args, update_score=True, **kwargs):
"""
When saving, also grants points to the family
"""
@@ -175,8 +175,9 @@ class Achievement(models.Model):
super().save(*args, **kwargs)
- self.family.refresh_from_db()
- self.family.update_score()
+ if update_score:
+ self.family.refresh_from_db()
+ self.family.update_score()
# Count only when getting a new achievement
if is_new:
diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js
index dba07f0d..db29923d 100644
--- a/apps/family/static/family/js/achievements.js
+++ b/apps/family/static/family/js/achievements.js
@@ -113,33 +113,47 @@ function reset () {
* Apply all transactions: all notes in `notes` buy each item in `buttons`
*/
function consumeAll () {
- console.log("consumeAll lancée")
if (LOCK) { return }
-
LOCK = true
let error = false
if (notes_display.length === 0) {
- document.getElementById('note').classList.add('is-invalid')
- $('#note_list').html(li('', 'Ajoutez des familles.', 'text-danger'))
+ // ... gestion erreur ...
error = true
}
-
if (buttons.length === 0) {
- $('#consos_list').html(li('', 'Ajoutez des défis.', 'text-danger'))
+ // ... gestion erreur ...
error = true
}
-
if (error) {
LOCK = false
return
}
- notes_display.forEach(function (family) {
- buttons.forEach(function (challenge) {
- grantAchievement(family, challenge)
- })
+ // Récupérer les IDs des familles et des challenges
+ const family_ids = notes_display.map(fam => fam.id)
+ const challenge_ids = buttons.map(chal => chal.id)
+
+ $.ajax({
+ url: '/family/api/family/achievements/batch/',
+ type: 'POST',
+ data: JSON.stringify({
+ families: family_ids,
+ challenges: challenge_ids
+ }),
+ contentType: 'application/json',
+ headers: {
+ 'X-CSRFToken': CSRF_TOKEN
+ },
+ success: function () {
+ reset()
+ addMsg("Défis validés pour les familles !", 'success', 5000)
+ },
+ error: function (e) {
+ reset()
+ addMsg("Erreur lors de la création des achievements.",'danger',5000)
+ }
})
}
diff --git a/apps/family/urls.py b/apps/family/urls.py
index 094ed505..ee491ecc 100644
--- a/apps/family/urls.py
+++ b/apps/family/urls.py
@@ -1,7 +1,7 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
-from django.urls import path
+from django.urls import path, include
from . import views
@@ -18,4 +18,5 @@ urlpatterns = [
path('challenge/detail//', views.ChallengeDetailView.as_view(), name="challenge_detail"),
path('challenge/update//', views.ChallengeUpdateView.as_view(), name="challenge_update"),
path('manage/', views.FamilyManageView.as_view(), name="manage"),
+ path('api/family/', include('family.api.urls')),
]