1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-22 16:56:48 +02:00

Added valid field and logic for Achievement

This commit is contained in:
Ehouarn
2025-07-22 01:30:47 +02:00
parent db4d0dd83a
commit c66cc14576
9 changed files with 152 additions and 31 deletions

View File

@ -0,0 +1,24 @@
# Generated by Django 5.2.4 on 2025-07-21 21:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('family', '0002_family_display_image'),
]
operations = [
migrations.AddField(
model_name='achievement',
name='valid',
field=models.BooleanField(default=False, verbose_name='valid'),
),
migrations.AlterField(
model_name='familymembership',
name='family',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='family.family', verbose_name='family'),
),
]

View File

@ -45,9 +45,9 @@ class Family(models.Model):
return self.name
def update_score(self, *args, **kwargs):
challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self)
challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True)
points_sum = challenge_set.aggregate(models.Sum("points"))
self.score = points_sum["points__sum"]
self.score = points_sum["points__sum"] if points_sum["points__sum"] else 0
self.save()
self.update_ranking()
@ -86,7 +86,7 @@ class FamilyMembership(models.Model):
family = models.ForeignKey(
Family,
on_delete=models.PROTECT,
related_name=_('members'),
related_name=_('memberships'),
verbose_name=_('family'),
)
@ -157,6 +157,11 @@ class Achievement(models.Model):
default=timezone.now,
)
valid = models.BooleanField(
verbose_name=_('valid'),
default=False,
)
class Meta:
verbose_name = _('achievement')
verbose_name_plural = _('achievements')

View File

@ -65,6 +65,23 @@ class AchievementTable(tables.Table):
"""
List recent achievements.
"""
validate = tables.LinkColumn(
'family:achievement_validate',
args=[A('id')],
verbose_name=_("Validate"),
text=_("Validate"),
orderable=False,
attrs={
'th': {
'id': 'validate-achievement-header'
},
'a': {
'class': 'btn btn-success',
'data-type': 'validate-achievement'
}
},
)
delete = tables.LinkColumn(
'family:achievement_delete',
args=[A('id')],
@ -73,11 +90,11 @@ class AchievementTable(tables.Table):
orderable=False,
attrs={
'th': {
'id': 'delete-membership-header'
'id': 'delete-achievement-header'
},
'a': {
'class': 'btn btn-danger',
'data-type': 'delete-membership'
'data-type': 'delete-achievement'
}
},
)
@ -87,10 +104,11 @@ class AchievementTable(tables.Table):
'class': 'table table-condensed table-striped table-hover'
}
model = Achievement
fields = ('family', 'challenge', 'challenge__points', 'obtained_at', )
fields = ('family', 'challenge', 'challenge__points', 'obtained_at', 'valid')
template_name = 'django_tables2/bootstrap4.html'
order_by = ('-obtained_at',)
class FamilyAchievementTable(tables.Table):
"""
Table des défis réalisés par une famille spécifique.
@ -102,4 +120,4 @@ class FamilyAchievementTable(tables.Table):
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
order_by = ('-obtained_at',)
order_by = ('-obtained_at',)

View File

@ -18,9 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post">
{% csrf_token %}
<a class="btn btn-primary" href="{% url 'family:achievement_list' %}">{% trans "Return to achievements list" %}</a>
{% if not object.locked %}
<button class="btn btn-danger" type="submit">{% trans "Delete" %}</button>
{% endif %}
<button class="btn btn-danger" type="submit">{% trans "Delete" %}</button>
</form>
</div>
</div>

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block content %}
<div class="card bg-light">
<div class="card-header text-center">
<h4>{% trans "Validate achievement" %}</h4>
</div>
<div class="card-body">
<div class="alert alert-warning">
{% blocktrans %}Are you sure you want to validate this achievement? This action can't be undone.{% endblocktrans %}
</div>
</div>
<div class="card-footer text-center">
<form method="post">
{% csrf_token %}
<a class="btn btn-primary" href="{% url 'family:achievement_list' %}">{% trans "Return to achievements list" %}</a>
<form method="post" action="{% url 'family:achievement_validate' pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-success">{% trans "Validate" %}</button>
</form>
</form>
</div>
</div>
{% endblock %}

View File

@ -10,13 +10,24 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card mb-4" id="history">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "Recent achievements history" %}
{% trans "Invalid achievements history" %}
</p>
<a class="btn btn-sm btn-primary mx-2" href="{% url "family:manage" %}">
{% trans "Return to management page" %}
</a>
</div>
{% render_table table %}
{% render_table invalid %}
</div>
<div class="card mb-4" id="history">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "Valid achievements history" %}
</p>
<a class="btn btn-sm btn-primary mx-2" href="{% url "family:manage" %}">
{% trans "Return to management page" %}
</a>
</div>
{% render_table valid %}
</div>
{% endblock %}

View File

@ -9,16 +9,17 @@ app_name = 'family'
urlpatterns = [
path('list/', views.FamilyListView.as_view(), name="family_list"),
path('add-family/', views.FamilyCreateView.as_view(), name="add_family"),
path('detail/<int:pk>/', views.FamilyDetailView.as_view(), name="family_detail"),
path('update/<int:pk>/', views.FamilyUpdateView.as_view(), name="family_update"),
path('update_pic/<int:pk>/', views.FamilyPictureUpdateView.as_view(), name="update_pic"),
path('add_member/<int:family_pk>/', views.FamilyAddMemberView.as_view(), name="family_add_member"),
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_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"),
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('add-challenge/', views.ChallengeCreateView.as_view(), name="add_challenge"),
path('challenge/detail/<int:pk>/', views.ChallengeDetailView.as_view(), name="challenge_detail"),
path('challenge/update/<int:pk>/', views.ChallengeUpdateView.as_view(), name="challenge_update"),
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('manage/', views.FamilyManageView.as_view(), name="manage"),
path('achievements/', views.AchievementsView.as_view(), name="achievement_list"),
path('achievement/delete/<int:pk>/', views.AchievementDeleteView.as_view(), name="achievement_delete"),
path('achievement/list/', views.AchievementsView.as_view(), name="achievement_list"),
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('api/family/', include('family.api.urls')),
]

View File

@ -4,12 +4,14 @@
from datetime import date
from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.views.generic import DetailView, UpdateView
from django.views.generic import DetailView, UpdateView, ListView
from django.views.generic.edit import DeleteView
from django.views.generic.base import TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2 import SingleTableView
from django_tables2 import SingleTableView, MultiTableMixin
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django.urls import reverse_lazy
@ -287,23 +289,57 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
def get_table(self, **kwargs):
table = super().get_table(**kwargs)
table.exclude = ('delete',)
table.exclude = ('delete', 'validate',)
table.orderable = False
return table
class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
"""
List all achievements
"""
model = Achievement
table_class = AchievementTable
tables = [AchievementTable, AchievementTable, ]
extra_context = {'title': _('Achievement list')}
def get_table(self, **kwargs):
table = super().get_table(**kwargs)
table.orderable = True
return table
def get_tables(self, **kwargs):
tables = super().get_tables(**kwargs)
tables[0].prefix = 'invalid-'
tables[1].prefix = 'valid-'
tables[1].exclude = ('validate', 'delete',)
return tables
def get_tables_data(self):
table_valid = self.get_queryset().filter(valid=True)
table_invalid = self.get_queryset().filter(valid=False)
return [table_invalid, table_valid, ]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tables = context['tables']
context['invalid'] = tables[0]
context['valid'] = tables[1]
return context
class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, TemplateView):
"""
Validate an achievement obtained by a family
"""
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'))
class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):

View File

@ -207,8 +207,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
modified_note.is_active = True
context["can_unlock_note"] = not user.note.is_active and PermissionBackend\
.check_perm(self.request, "note.change_noteuser_is_active", modified_note)
families = Family.objects.filter(members__user=user).distinct()
families = Family.objects.filter(memberships__user=user).distinct()
context["families"] = families
return context