1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-23 17:26:46 +02:00
This commit is contained in:
Ehouarn
2025-07-22 18:31:55 +02:00
parent c66cc14576
commit adc925e4b1
10 changed files with 403 additions and 86 deletions

View File

@ -3,7 +3,7 @@
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend 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.views import APIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
@ -21,9 +21,9 @@ class FamilyViewSet(ReadProtectedModelViewSet):
""" """
queryset = Family.objects.order_by('id') queryset = Family.objects.order_by('id')
serializer_class = FamilySerializer serializer_class = FamilySerializer
filter_backends = [DjangoFilterBackend, SearchFilter] filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['name', ] filterset_fields = ['name', 'description', 'score', 'rank', ]
search_fields = ['$name', ] search_fields = ['$name', '$description', ]
class FamilyMembershipViewSet(ReadProtectedModelViewSet): class FamilyMembershipViewSet(ReadProtectedModelViewSet):
@ -34,9 +34,11 @@ class FamilyMembershipViewSet(ReadProtectedModelViewSet):
""" """
queryset = FamilyMembership.objects.order_by('id') queryset = FamilyMembership.objects.order_by('id')
serializer_class = FamilyMembershipSerializer serializer_class = FamilyMembershipSerializer
filter_backends = [DjangoFilterBackend, SearchFilter] filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['name', ] filterset_fields = ['user__username', 'user__first_name', 'user__last_name', 'user__email', 'user__note__alias__name',
search_fields = ['$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): class ChallengeViewSet(ReadProtectedModelViewSet):
@ -47,9 +49,9 @@ class ChallengeViewSet(ReadProtectedModelViewSet):
""" """
queryset = Challenge.objects.order_by('id') queryset = Challenge.objects.order_by('id')
serializer_class = ChallengeSerializer serializer_class = ChallengeSerializer
filter_backends = [DjangoFilterBackend, SearchFilter] filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['name', ] filterset_fields = ['name', 'description', 'points', ]
search_fields = ['$name', ] search_fields = ['$name', '$description', '$points', ]
class AchievementViewSet(ReadProtectedModelViewSet): class AchievementViewSet(ReadProtectedModelViewSet):
@ -60,22 +62,19 @@ class AchievementViewSet(ReadProtectedModelViewSet):
""" """
queryset = Achievement.objects.order_by('id') queryset = Achievement.objects.order_by('id')
serializer_class = AchievementSerializer serializer_class = AchievementSerializer
filter_backends = [DjangoFilterBackend, SearchFilter] filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['name', ] filterset_fields = ['family__name', 'family__description', 'challenge__name', 'challenge__description', 'obtained_at', 'valid', ]
search_fields = ['$name', ] search_fields = ['$family__name', '$family__description', '$challenge__name', '$challenge__description', ]
class BatchAchievementsAPIView(APIView): class BatchAchievementsAPIView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def post(self, request, format=None): def post(self, request, format=None):
print("POST de la view spéciale") family_ids = request.data.get('families')
family_ids = request.data.get('families', []) challenge_ids = request.data.get('challenges')
challenge_ids = request.data.get('challenges', [])
families = Family.objects.filter(id__in=family_ids) families = Family.objects.filter(id__in=family_ids)
challenges = Challenge.objects.filter(id__in=challenge_ids) challenges = Challenge.objects.filter(id__in=challenge_ids)
for family in families: for family in families:
for challenge in challenges: for challenge in challenges:
a = Achievement(family=family, challenge=challenge) a = Achievement(family=family, challenge=challenge)

View File

@ -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',
),
]

View File

@ -4,6 +4,7 @@
from django.db import models, transaction from django.db import models, transaction
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -44,6 +45,9 @@ class Family(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self):
return reverse_lazy('family:family_detail', args=(self.pk,))
def update_score(self, *args, **kwargs): def update_score(self, *args, **kwargs):
challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True) challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True)
points_sum = challenge_set.aggregate(models.Sum("points")) points_sum = challenge_set.aggregate(models.Sum("points"))
@ -119,10 +123,16 @@ class Challenge(models.Model):
verbose_name=_('points'), verbose_name=_('points'),
) )
obtained = models.PositiveIntegerField( @property
verbose_name=_('obtained'), def obtained(self):
default=0, 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 @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -136,9 +146,6 @@ class Challenge(models.Model):
verbose_name = _('challenge') verbose_name = _('challenge')
verbose_name_plural = _('challenges') verbose_name_plural = _('challenges')
def __str__(self):
return self.name
class Achievement(models.Model): class Achievement(models.Model):
challenge = models.ForeignKey( challenge = models.ForeignKey(
@ -176,7 +183,6 @@ class Achievement(models.Model):
""" """
self.family = Family.objects.select_for_update().get(pk=self.family_id) self.family = Family.objects.select_for_update().get(pk=self.family_id)
self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id) self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id)
is_new = self.pk is None
super().save(*args, **kwargs) super().save(*args, **kwargs)
@ -184,13 +190,6 @@ class Achievement(models.Model):
self.family.refresh_from_db() self.family.refresh_from_db()
self.family.update_score() 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 @transaction.atomic
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" """
@ -205,8 +204,3 @@ class Achievement(models.Model):
# Remove points from the family # Remove points from the family
self.family.refresh_from_db() self.family.refresh_from_db()
self.family.update_score() self.family.update_score()
self.challenge.refresh_from_db()
self.challenge.obtained -= 1
self.challenge._force_save = True
self.challenge.save()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -113,6 +113,7 @@ function reset () {
* Apply all transactions: all notes in `notes` buy each item in `buttons` * Apply all transactions: all notes in `notes` buy each item in `buttons`
*/ */
function consumeAll () { function consumeAll () {
console.log("test");
if (LOCK) { return } if (LOCK) { return }
LOCK = true LOCK = true
@ -130,11 +131,13 @@ function consumeAll () {
LOCK = false LOCK = false
return return
} }
console.log("couocu")
// Récupérer les IDs des familles et des challenges // Récupérer les IDs des familles et des challenges
const family_ids = notes_display.map(fam => fam.id) const family_ids = notes_display.map(fam => fam.id)
const challenge_ids = buttons.map(chal => chal.id) const challenge_ids = buttons.map(chal => chal.id)
console.log(family_ids)
console.log(challenge_ids)
$.ajax({ $.ajax({
url: '/family/api/family/achievements/batch/', url: '/family/api/family/achievements/batch/',
type: 'POST', 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 searchbar = document.getElementById("search-input")
var search_results = document.getElementById("search-results") 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) { 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) const field = $('#' + field_id)
console.log("autoCompleteFamily commence")
// Configuration du tooltip // Configuration du tooltip
field.tooltip({ field.tooltip({
html: true, html: true,

View File

@ -84,12 +84,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3> </h3>
<div class="card-body text-center"> <div class="card-body text-center">
{% if can_add_family %} {% if can_add_family %}
<a class="btn btn-sm btn-primary mx-2" href="{% url "family:add_family" %}"> <a class="btn btn-sm btn-primary mx-2" href="{% url "family:family_create" %}">
{% trans "Add a family" %} {% trans "Add a family" %}
</a> </a>
{% endif %} {% endif %}
{% if can_add_challenge %} {% if can_add_challenge %}
<a class="btn btn-sm btn-primary mx-2" href="{% url "family:add_challenge" %}"> <a class="btn btn-sm btn-primary mx-2" href="{% url "family:challenge_create" %}">
{% trans "Add a challenge" %} {% trans "Add a challenge" %}
</a> </a>
{% endif %} {% endif %}
@ -147,7 +147,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
{# transaction history #} {# achievement history #}
<div class="card"> <div class="card">
<div class="card-header position-relative" id="historyListHeading"> <div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold" <a class="stretched-link font-weight-bold"
@ -155,7 +155,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans "Recent achievements history" %} {% trans "Recent achievements history" %}
</a> </a>
</div> </div>
<div id="history_list"> <div id="history">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>

View File

View File

@ -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/')

View File

@ -8,18 +8,18 @@ from . import views
app_name = 'family' app_name = 'family'
urlpatterns = [ urlpatterns = [
path('list/', views.FamilyListView.as_view(), name="family_list"), 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('<int:pk>/detail/', views.FamilyDetailView.as_view(), name="family_detail"), path('<int:pk>/detail/', views.FamilyDetailView.as_view(), name="family_detail"),
path('<int:pk>/update/', views.FamilyUpdateView.as_view(), name="family_update"), path('<int:pk>/update/', views.FamilyUpdateView.as_view(), name="family_update"),
path('<int:pk>/update_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"), path('<int:pk>/update_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"),
path('<int:family_pk>/add_member/', views.FamilyAddMemberView.as_view(), name="family_add_member"), path('<int:family_pk>/add_member/', views.FamilyAddMemberView.as_view(), name="family_add_member"),
path('challenge/list/', views.ChallengeListView.as_view(), name="challenge_list"), 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/<int:pk>/detail/', views.ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/<int:pk>/detail/', views.ChallengeDetailView.as_view(), name="challenge_detail"),
path('challenge/<int:pk>/update/', views.ChallengeUpdateView.as_view(), name="challenge_update"), path('challenge/<int:pk>/update/', views.ChallengeUpdateView.as_view(), name="challenge_update"),
path('manage/', views.FamilyManageView.as_view(), name="manage"), 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/<int:pk>/validate/', views.AchievementValidateView.as_view(), name="achievement_validate"), path('achievement/<int:pk>/validate/', views.AchievementValidateView.as_view(), name="achievement_validate"),
path('achievement/<int:pk>/delete/', views.AchievementDeleteView.as_view(), name="achievement_delete"), path('achievement/<int:pk>/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')),
] ]

View File

@ -8,14 +8,14 @@ from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction from django.db import transaction
from django.views.generic import DetailView, UpdateView, ListView 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.views.generic.base import TemplateView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_tables2 import SingleTableView, MultiTableMixin from django_tables2 import SingleTableView, MultiTableMixin
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django.urls import reverse_lazy 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 .models import Family, Challenge, FamilyMembership, User, Achievement
from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable 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}) 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 Update profile picture of the family
""" """
model = Family model = Family
extra_context = {"title": _("Update family picture")} extra_context = {"title": _("Update family picture")}
template_name = 'family/picture_update.html' 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): def get_success_url(self):
"""Redirect to family page after upload""" """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 @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
@ -141,6 +152,11 @@ class FamilyPictureUpdateView(PictureUpdateView):
else: else:
image.name = "{}_pic.png".format(self.object.pk) 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): class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
""" """
@ -282,8 +298,8 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
PermissionBackend.filter_queryset(self.request, Challenge, "view") PermissionBackend.filter_queryset(self.request, Challenge, "view")
).order_by('name') ).order_by('name')
context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.add_family") context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.family_create")
context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.add_challenge") context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.challenge_create")
return context return context
@ -294,7 +310,7 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
return table return table
class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
""" """
List all achievements List all achievements
""" """
@ -333,12 +349,11 @@ class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, Template
template_name = 'family/achievement_confirm_validate.html' template_name = 'family/achievement_confirm_validate.html'
def post(self, request, pk): def post(self, request, pk):
# On récupère l'objet à valider
achievement = Achievement.objects.get(pk=pk) achievement = Achievement.objects.get(pk=pk)
# On modifie le champ valid
achievement.valid = True achievement.valid = True
achievement.save() achievement.save()
# On redirige vers la page de détail ou la liste
return redirect(reverse_lazy('family:achievement_list')) return redirect(reverse_lazy('family:achievement_list'))