mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-22 08:53:28 +02:00
Added valid field and logic for Achievement
This commit is contained in:
@ -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'),
|
||||
),
|
||||
]
|
@ -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')
|
||||
|
@ -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',)
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
@ -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 %}
|
@ -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')),
|
||||
]
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user