diff --git a/apps/family/api/views.py b/apps/family/api/views.py
index 50ac0496..79a719d1 100644
--- a/apps/family/api/views.py
+++ b/apps/family/api/views.py
@@ -3,7 +3,7 @@
from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
-from rest_framework.filters import SearchFilter
+from api.filters import RegexSafeSearchFilter
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@@ -21,9 +21,9 @@ class FamilyViewSet(ReadProtectedModelViewSet):
"""
queryset = Family.objects.order_by('id')
serializer_class = FamilySerializer
- filter_backends = [DjangoFilterBackend, SearchFilter]
- filterset_fields = ['name', ]
- search_fields = ['$name', ]
+ filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
+ filterset_fields = ['name', 'description', 'score', 'rank', ]
+ search_fields = ['$name', '$description', ]
class FamilyMembershipViewSet(ReadProtectedModelViewSet):
@@ -34,9 +34,11 @@ class FamilyMembershipViewSet(ReadProtectedModelViewSet):
"""
queryset = FamilyMembership.objects.order_by('id')
serializer_class = FamilyMembershipSerializer
- filter_backends = [DjangoFilterBackend, SearchFilter]
- filterset_fields = ['name', ]
- search_fields = ['$name', ]
+ filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
+ filterset_fields = ['user__username', 'user__first_name', 'user__last_name', 'user__email', 'user__note__alias__name',
+ 'user__note__alias__normalized_name', 'family__name', 'family__description', 'year', ]
+ search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email', '$user__note__alias__name',
+ '$user__note__alias__normalized_name', '$family__name', '$family__description', '$year', ]
class ChallengeViewSet(ReadProtectedModelViewSet):
@@ -47,9 +49,9 @@ class ChallengeViewSet(ReadProtectedModelViewSet):
"""
queryset = Challenge.objects.order_by('id')
serializer_class = ChallengeSerializer
- filter_backends = [DjangoFilterBackend, SearchFilter]
- filterset_fields = ['name', ]
- search_fields = ['$name', ]
+ filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
+ filterset_fields = ['name', 'description', 'points', ]
+ search_fields = ['$name', '$description', '$points', ]
class AchievementViewSet(ReadProtectedModelViewSet):
@@ -60,22 +62,19 @@ class AchievementViewSet(ReadProtectedModelViewSet):
"""
queryset = Achievement.objects.order_by('id')
serializer_class = AchievementSerializer
- filter_backends = [DjangoFilterBackend, SearchFilter]
- filterset_fields = ['name', ]
- search_fields = ['$name', ]
+ filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
+ filterset_fields = ['family__name', 'family__description', 'challenge__name', 'challenge__description', 'obtained_at', 'valid', ]
+ search_fields = ['$family__name', '$family__description', '$challenge__name', '$challenge__description', ]
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', [])
-
+ 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)
diff --git a/apps/family/migrations/0004_remove_challenge_obtained.py b/apps/family/migrations/0004_remove_challenge_obtained.py
new file mode 100644
index 00000000..2831bac1
--- /dev/null
+++ b/apps/family/migrations/0004_remove_challenge_obtained.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.2.4 on 2025-07-22 14:33
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('family', '0003_achievement_valid_alter_familymembership_family'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='challenge',
+ name='obtained',
+ ),
+ ]
diff --git a/apps/family/models.py b/apps/family/models.py
index 708c3929..71ccfa08 100644
--- a/apps/family/models.py
+++ b/apps/family/models.py
@@ -4,6 +4,7 @@
from django.db import models, transaction
from django.utils import timezone
from django.contrib.auth.models import User
+from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
@@ -44,6 +45,9 @@ class Family(models.Model):
def __str__(self):
return self.name
+ def get_absolute_url(self):
+ return reverse_lazy('family:family_detail', args=(self.pk,))
+
def update_score(self, *args, **kwargs):
challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True)
points_sum = challenge_set.aggregate(models.Sum("points"))
@@ -119,10 +123,16 @@ class Challenge(models.Model):
verbose_name=_('points'),
)
- obtained = models.PositiveIntegerField(
- verbose_name=_('obtained'),
- default=0,
- )
+ @property
+ def obtained(self):
+ achievements = Achievement.objects.filter(challenge=self, valid=True)
+ return achievements.count()
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse_lazy('family:challenge_detail', args=(self.pk,))
@transaction.atomic
def save(self, *args, **kwargs):
@@ -136,9 +146,6 @@ class Challenge(models.Model):
verbose_name = _('challenge')
verbose_name_plural = _('challenges')
- def __str__(self):
- return self.name
-
class Achievement(models.Model):
challenge = models.ForeignKey(
@@ -176,7 +183,6 @@ 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)
- is_new = self.pk is None
super().save(*args, **kwargs)
@@ -184,13 +190,6 @@ class Achievement(models.Model):
self.family.refresh_from_db()
self.family.update_score()
- # Count only when getting a new achievement
- if is_new:
- self.challenge.refresh_from_db()
- self.challenge.obtained += 1
- self.challenge._force_save = True
- self.challenge.save()
-
@transaction.atomic
def delete(self, *args, **kwargs):
"""
@@ -205,8 +204,3 @@ class Achievement(models.Model):
# Remove points from the family
self.family.refresh_from_db()
self.family.update_score()
-
- self.challenge.refresh_from_db()
- self.challenge.obtained -= 1
- self.challenge._force_save = True
- self.challenge.save()
diff --git a/apps/family/static/family/img/default_picture.png b/apps/family/static/family/img/default_picture.png
new file mode 100644
index 00000000..41a31a1c
Binary files /dev/null and b/apps/family/static/family/img/default_picture.png differ
diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js
index db29923d..ebec8689 100644
--- a/apps/family/static/family/js/achievements.js
+++ b/apps/family/static/family/js/achievements.js
@@ -113,6 +113,7 @@ function reset () {
* Apply all transactions: all notes in `notes` buy each item in `buttons`
*/
function consumeAll () {
+ console.log("test");
if (LOCK) { return }
LOCK = true
@@ -130,11 +131,13 @@ function consumeAll () {
LOCK = false
return
}
-
+ console.log("couocu")
// 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)
+ console.log(family_ids)
+ console.log(challenge_ids)
$.ajax({
url: '/family/api/family/achievements/batch/',
type: 'POST',
@@ -157,34 +160,6 @@ function consumeAll () {
})
}
-/**
- * Create a new achievement through the API.
- * @param family The selected family
- * @param challenge The selected challenge
- */
-function grantAchievement (family, challenge) {
- console.log("grant lancée",family,challenge)
- $.post('/api/family/achievement/',
- {
- csrfmiddlewaretoken: CSRF_TOKEN,
- family: family.id,
- challenge: challenge.id,
- })
- .done(function () {
- reset()
- addMsg("Défi validé pour la famille !", 'success', 5000)
- })
- .fail(function (e) {
- reset()
- if (e.responseJSON) {
- errMsg(e.responseJSON)
- } else if (e.responseText) {
- errMsg(e.responseText)
- } else {
- errMsg("Erreur inconnue lors de la création de l'achievement.")
- }
- })
-}
var searchbar = document.getElementById("search-input")
var search_results = document.getElementById("search-results")
@@ -264,7 +239,6 @@ function li (id, text, extra_css) {
*/
function autoCompleteFamily(field_id, family_list_id, families, families_display, family_prefix = 'family', user_family_field = null, profile_pic_field = null, family_click = null) {
const field = $('#' + field_id)
- console.log("autoCompleteFamily commence")
// Configuration du tooltip
field.tooltip({
html: true,
diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html
index 0ecac60a..22a4ed90 100644
--- a/apps/family/templates/family/manage.html
+++ b/apps/family/templates/family/manage.html
@@ -84,12 +84,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if can_add_family %}
-
+
{% trans "Add a family" %}
{% endif %}
{% if can_add_challenge %}
-
+
{% trans "Add a challenge" %}
{% endif %}
@@ -147,7 +147,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
-{# transaction history #}
+{# achievement history #}
-
+
{% render_table table %}
diff --git a/apps/family/tests/__init__.py b/apps/family/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/family/tests/test_family.py b/apps/family/tests/test_family.py
new file mode 100644
index 00000000..f2b31784
--- /dev/null
+++ b/apps/family/tests/test_family.py
@@ -0,0 +1,318 @@
+# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import os
+
+from api.tests import TestAPI
+from django.contrib.auth.models import User
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test import TestCase
+from rest_framework.test import APITestCase
+from django.urls import reverse
+from django.utils import timezone
+
+from ..api.views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet
+from ..models import Family, FamilyMembership, Challenge, Achievement
+
+
+class TestFamily(TestCase):
+ """
+ Test family
+ """
+
+ def setUp(self):
+ self.user = User.objects.create_superuser(
+ username='admintoto',
+ password='toto1234',
+ email='toto@example.com',
+ )
+ self.client.force_login(self.user)
+
+ sess = self.client.session
+ sess['permission_mask'] = 42
+ sess.save()
+
+ self.family = Family.objects.create(
+ name='Test family',
+ description='',
+ )
+
+ self.challenge = Challenge.objects.create(
+ name='Test challenge',
+ description='',
+ points=100,
+ )
+
+ self.achievement = Achievement.objects.create(
+ family=self.family,
+ challenge=self.challenge,
+ valid=False,
+ )
+
+ def test_family_list(self):
+ """
+ Test display family list
+ """
+ response = self.client.get(reverse("family:family_list"))
+ self.assertEqual(response.status_code, 200)
+
+ def test_family_create(self):
+ """
+ Test create a family
+ """
+ response = self.client.get(reverse("family:family_create"))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("family:family_create"), data={
+ "name": "Family toto",
+ "description": "A test family",
+ })
+ self.assertTrue(Family.objects.filter(name="Family toto").exists())
+ self.assertRedirects(response, reverse("family:manage"), 302, 200)
+
+ def test_family_detail(self):
+ """
+ Test display the detail of a family
+ """
+ response = self.client.get(reverse("family:family_detail", args=(self.family.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ def test_family_update(self):
+ """
+ Test update a family
+ """
+ response = self.client.get(reverse("family:family_update", args=(self.family.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("family:family_update", args=(self.family.pk,)), data=dict(
+ name="Toto family updated",
+ description="A larger description for the test family"
+ ))
+ self.assertRedirects(response, self.family.get_absolute_url(), 302, 200)
+ self.assertTrue(Family.objects.filter(name="Toto family updated").exists())
+
+ def test_family_update_picture(self):
+ """
+ Test update the picture of a family
+ """
+ response = self.client.get(reverse("family:update_pic", args=(self.family.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ old_pic = self.family.display_image
+
+ with open("apps/family/static/family/img/default_picture.png", "rb") as f:
+ image = SimpleUploadedFile("image.png", f.read(), "image/png")
+ response = self.client.post(reverse("family:update_pic", args=(self.family.pk,)), dict(
+ image=image,
+ x=0,
+ y=0,
+ width=200,
+ height=200,
+ ))
+ self.assertRedirects(response, self.family.get_absolute_url(), 302, 200)
+
+ self.family.refresh_from_db()
+ self.assertTrue(os.path.exists(self.family.display_image.path))
+ os.remove(self.family.display_image.path)
+
+ self.family.display_image = old_pic
+ self.family.save()
+
+ def test_family_add_member(self):
+ """
+ Test add memberships to a family
+ """
+ response = self.client.get(reverse("family:family_add_member", args=(self.family.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ user = User.objects.create(username="totototo")
+ user.profile.registration_valid = True
+ user.profile.email_confirmed = True
+ user.profile.save()
+ user.save()
+
+ response = self.client.post(reverse("family:family_add_member", args=(self.family.pk,)), data=dict(
+ user=user.pk,
+ ))
+ self.assertRedirects(response, self.family.get_absolute_url(), 302, 200)
+
+ self.assertTrue(FamilyMembership.objects.filter(user=user, family=self.family, year=timezone.now().year).exists())
+
+ def test_challenge_list(self):
+ """
+ Test display challenge list
+ """
+ response = self.client.get(reverse('family:challenge_list'))
+ self.assertEqual(response.status_code, 200)
+
+ def test_challenge_create(self):
+ """
+ Test create a challenge
+ """
+ response = self.client.get(reverse("family:challenge_create"))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("family:challenge_create"), data={
+ "name": "Challenge for toto",
+ "description": "A test challenge",
+ "points": 50,
+ })
+ self.assertTrue(Challenge.objects.filter(name="Challenge for toto").exists())
+ self.assertRedirects(response, reverse("family:manage"), 302, 200)
+
+ def test_challenge_detail(self):
+ """
+ Test display the detail of a challenge
+ """
+ response = self.client.get(reverse("family:challenge_detail", args=(self.challenge.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ def test_challenge_update(self):
+ """
+ Test update a challenge
+ """
+ response = self.client.get(reverse("family:challenge_update", args=(self.challenge.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("family:challenge_update", args=(self.challenge.pk,)), data=dict(
+ name="Challenge updated",
+ description="Another description",
+ points=10,
+ ))
+ self.assertRedirects(response, self.challenge.get_absolute_url(), 302, 200)
+ self.assertTrue(Challenge.objects.filter(name="Challenge updated").exists())
+
+ def test_render_manage_page(self):
+ """
+ Test render manage page
+ """
+ response = self.client.get(reverse("family:manage"))
+ self.assertEqual(response.status_code, 200)
+
+ def test_validate_achievement(self):
+ """
+ Test validate an achievement
+ """
+ old_family_score = self.family.score
+
+ response = self.client.get(reverse("family:achievement_validate", args=(self.achievement.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("family:achievement_validate", args=(self.achievement.pk,)))
+ self.assertRedirects(response, reverse("family:achievement_list"), 302, 200)
+
+ self.achievement.refresh_from_db()
+ self.assertIs(self.achievement.valid, True)
+
+ self.family.refresh_from_db()
+ self.assertEqual(self.family.score, old_family_score + self.achievement.challenge.points)
+
+ def test_delete_achievement(self):
+ """
+ Test delete an achievement
+ """
+ response = self.client.get(reverse("family:achievement_delete", args=(self.achievement.pk,)))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.delete(reverse("family:achievement_delete", args=(self.achievement.pk,)))
+ self.assertRedirects(response, reverse("family:achievement_list"), 302, 200)
+ self.assertFalse(Achievement.objects.filter(pk=self.achievement.pk).exists())
+
+
+class TestBatchAchievements(APITestCase):
+ def setUp(self):
+ self.user = User.objects.create_superuser(
+ username='admintoto',
+ password='toto1234',
+ email='toto@example.com',
+ )
+ self.client.force_login(self.user)
+
+ sess = self.client.session
+ sess['permission_mask'] = 42
+ sess.save()
+
+ self.families = [
+ Family.objects.create(name=f'Famille {i}', description='') for i in range(2)
+ ]
+ self.challenges = [
+ Challenge.objects.create(name=f'Challenge {i}', description='', points=50) for i in range(3)
+ ]
+
+ self.url = reverse("family:api:batch_achievements")
+
+ def test_batch_achievement_creation(self):
+ family_ids = [f.id for f in self.families]
+ challenge_ids = [c.id for c in self.challenges]
+ response = self.client.post(
+ self.url,
+ data={
+ 'families': family_ids,
+ 'challenges': challenge_ids
+ },
+ format='json'
+ )
+
+ self.assertEqual(response.status_code, 201)
+ self.assertEqual(response.data['status'], 'ok')
+
+ expected_count = len(family_ids) * len(challenge_ids)
+ self.assertEqual(Achievement.objects.count(), expected_count)
+
+ # Check that correct couples family/challenge exist
+ for f in self.families:
+ for c in self.challenges:
+ self.assertTrue(
+ Achievement.objects.filter(family=f, challenge=c).exists()
+ )
+
+
+class TestFamilyAPI(TestAPI):
+ def setUp(self):
+ super().setUp()
+
+ self.family = Family.objects.create(
+ name='Test family',
+ description='',
+ )
+
+ self.familymembership = FamilyMembership.objects.create(
+ user=self.user,
+ family=self.family,
+ )
+
+ self.challenge = Challenge.objects.create(
+ name='Test challenge',
+ description='',
+ points=100,
+ )
+
+ self.achievement = Achievement.objects.create(
+ family=self.family,
+ challenge=self.challenge,
+ valid=False,
+ )
+
+ def test_family_api(self):
+ """
+ Load Family API page and test all filters and permissions
+ """
+ self.check_viewset(FamilyViewSet, '/api/family/family/')
+
+ def test_familymembership_api(self):
+ """
+ Load FamilyMembership API page and test all filters and permissions
+ """
+ self.check_viewset(FamilyMembershipViewSet, '/api/family/familymembership/')
+
+ def test_challenge_api(self):
+ """
+ Load Challenge API page and test all filters and permissions
+ """
+ self.check_viewset(ChallengeViewSet, '/api/family/challenge/')
+
+ def test_achievement_api(self):
+ """
+ Load Achievement API page and test all filters and permissions
+ """
+ self.check_viewset(AchievementViewSet, '/api/family/achievement/')
diff --git a/apps/family/urls.py b/apps/family/urls.py
index 7d9e9c5b..edb0d18a 100644
--- a/apps/family/urls.py
+++ b/apps/family/urls.py
@@ -8,18 +8,18 @@ from . import views
app_name = 'family'
urlpatterns = [
path('list/', views.FamilyListView.as_view(), name="family_list"),
- path('add-family/', views.FamilyCreateView.as_view(), name="add_family"),
+ path('create/', views.FamilyCreateView.as_view(), name="family_create"),
path('
/detail/', views.FamilyDetailView.as_view(), name="family_detail"),
path('/update/', views.FamilyUpdateView.as_view(), name="family_update"),
path('/update_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"),
path('/add_member/', views.FamilyAddMemberView.as_view(), name="family_add_member"),
path('challenge/list/', views.ChallengeListView.as_view(), name="challenge_list"),
- path('add-challenge/', views.ChallengeCreateView.as_view(), name="add_challenge"),
+ path('challenge/create/', views.ChallengeCreateView.as_view(), name="challenge_create"),
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('achievement/list/', views.AchievementsView.as_view(), name="achievement_list"),
+ path('achievement/list/', views.AchievementListView.as_view(), name="achievement_list"),
path('achievement//validate/', views.AchievementValidateView.as_view(), name="achievement_validate"),
path('achievement//delete/', views.AchievementDeleteView.as_view(), name="achievement_delete"),
- path('api/family/', include('family.api.urls')),
+ path('api/family/', include(('family.api.urls', 'family_api'), namespace='api')),
]
diff --git a/apps/family/views.py b/apps/family/views.py
index a6e886a8..aee6f276 100644
--- a/apps/family/views.py
+++ b/apps/family/views.py
@@ -8,14 +8,14 @@ from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.views.generic import DetailView, UpdateView, ListView
-from django.views.generic.edit import DeleteView
+from django.views.generic.edit import DeleteView, FormMixin
from django.views.generic.base import TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2 import SingleTableView, MultiTableMixin
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django.urls import reverse_lazy
-from member.views import PictureUpdateView
+from member.forms import ImageForm
from .models import Family, Challenge, FamilyMembership, User, Achievement
from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable
@@ -112,17 +112,28 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})
-class FamilyPictureUpdateView(PictureUpdateView):
+class FamilyPictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView):
"""
Update profile picture of the family
"""
model = Family
extra_context = {"title": _("Update family picture")}
template_name = 'family/picture_update.html'
+ form_class = ImageForm
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['form'] = self.form_class(self.request.POST, self.request.FILES)
+ return context
def get_success_url(self):
"""Redirect to family page after upload"""
- return reverse_lazy('family:family_detail', kwargs={'pk': self.object.id})
+ return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})
+
+ def post(self, request, *args, **kwargs):
+ form = self.get_form()
+ self.object = self.get_object()
+ return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
@transaction.atomic
def form_valid(self, form):
@@ -141,6 +152,11 @@ class FamilyPictureUpdateView(PictureUpdateView):
else:
image.name = "{}_pic.png".format(self.object.pk)
+ # Save
+ self.object.display_image = image
+ self.object.save()
+ return super().form_valid(form)
+
class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
"""
@@ -282,8 +298,8 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
PermissionBackend.filter_queryset(self.request, Challenge, "view")
).order_by('name')
- context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.add_family")
- context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.add_challenge")
+ context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.family_create")
+ context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.challenge_create")
return context
@@ -294,7 +310,7 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
return table
-class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
+class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
"""
List all achievements
"""
@@ -333,12 +349,11 @@ class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, Template
template_name = 'family/achievement_confirm_validate.html'
def post(self, request, pk):
- # On récupère l'objet à valider
achievement = Achievement.objects.get(pk=pk)
- # On modifie le champ valid
+
achievement.valid = True
achievement.save()
- # On redirige vers la page de détail ou la liste
+
return redirect(reverse_lazy('family:achievement_list'))