1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-19 23:51:25 +02:00

Family views

This commit is contained in:
Ehouarn
2025-07-17 17:07:47 +02:00
parent 3ebadf34bc
commit 65dd42fc97
7 changed files with 227 additions and 8 deletions

View File

@ -3,7 +3,6 @@
from django import forms from django import forms
from django.forms.widgets import NumberInput from django.forms.widgets import NumberInput
from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete from note_kfet.inputs import Autocomplete
from .models import Challenge, FamilyMembership, User from .models import Challenge, FamilyMembership, User
@ -19,3 +18,21 @@ class ChallengeUpdateForm(forms.ModelForm):
widgets = { widgets = {
"points": NumberInput() "points": NumberInput()
} }
class FamilyMembershipForm(forms.ModelForm):
class Meta:
model = FamilyMembership
fields = ('user', )
widgets = {
"user":
Autocomplete(
User,
attrs={
'api_url': '/api/user/',
'name_field': 'username',
'placeholder': 'Nom ...',
},
)
}

View File

@ -4,7 +4,7 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2 import A from django_tables2 import A
from .models import Family, Challenge from .models import Family, Challenge, FamilyMembership
class FamilyTable(tables.Table): class FamilyTable(tables.Table):
@ -43,3 +43,17 @@ class ChallengeTable(tables.Table):
model = Challenge model = Challenge
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
fields = ('name', 'description', 'points',) fields = ('name', 'description', 'points',)
class FamilyMembershipTable(tables.Table):
"""
List all family memberships.
"""
class Meta:
attrs = {
'class': 'table table-condensed table-striped',
'style': 'table-layout: fixed;'
}
template_name = 'django_tables2/bootstrap4.html'
fields = ('user',)
model = FamilyMembership

View File

@ -0,0 +1,60 @@
{% extends "family/base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags i18n pretty_money %}
{% block profile_content %}
<div class="card bg-light">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function autocompleted(user) {
$("#id_last_name").val(user.last_name);
$("#id_first_name").val(user.first_name);
$.getJSON("/api/members/profile/" + user.id + "/", function (profile) {
let fee = profile.paid ? "{{ club.membership_fee_paid }}" : "{{ club.membership_fee_unpaid }}";
$("#id_credit_amount").val((Number(fee) / 100).toFixed(2));
});
}
soge_field = $("#id_soge");
function fillFields() {
let checked = soge_field.is(':checked');
if (!checked) {
$("input").attr('disabled', false);
$("#id_user").attr('disabled', true);
$("select").attr('disabled', false);
return;
}
let credit_type = $("#id_credit_type");
credit_type.attr('disabled', true);
credit_type.val(4);
let credit_amount = $("#id_credit_amount");
credit_amount.attr('disabled', true);
credit_amount.val('{{ total_fee }}');
let bank = $("#id_bank");
bank.attr('disabled', true);
bank.val('Société générale');
}
soge_field.change(fillFields);
</script>
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n perms %}
{# Use a fluid-width container #}
{% block containertype %}container-fluid{% endblock %}
{% block content %}
<div class="row mt-4">
<div class="col-xl-4">
{% block profile_info %}
<div class="card bg-light" id="card-infos">
<h4 class="card-header text-center">
{% if user_object %}
{% trans "Account #" %}{{ user_object.pk }}
{% elif club %}
Club {{ club.name }}
{% endif %}
</h4>
<div class="text-center">
{% if user_object %}
<a href="{% url 'member:user_update_pic' user_object.pk %}">
<img src="{{ user_object.note.display_image.url }}" class="img-thumbnail mt-2">
</a>
{% elif club %}
<a href="{% url 'member:club_update_pic' club.pk %}">
<img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2">
</a>
{% endif %}
</div>
<div class="card-body" id="profile_infos">
{% if user_object %}
{% include "member/includes/profile_info.html" %}
{% elif club %}
{% include "member/includes/club_info.html" %}
{% endif %}
</div>
<div class="card-footer">
{% if can_add_members %}
<a class="btn btn-sm btn-success" href="{% url 'family:family_add_member' family_pk=family.pk %}"
data-turbolinks="false"> {% trans "Add member" %}</a>
{% endif %}
{% if ".change_"|has_perm:family %}
<a class="btn btn-sm btn-secondary" href="{% url 'family:family_update' pk=family.pk %}"
data-turbolinks="false">
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
</a>
{% endif %}
{% url 'member:club_detail' club.pk as club_detail_url %}
{% if request.path_info != club_detail_url %}
<a class="btn btn-sm btn-primary" href="{{ club_detail_url }}">{% trans 'View Profile' %}</a>
{% endif %}
{% if can_lock_note %}
<button class="btn btn-sm btn-danger" data-toggle="modal" data-target="#lock-note-modal">
<i class="fa fa-ban"></i> {% trans 'Lock note' %}
</button>
{% elif can_unlock_note %}
<button class="btn btn-sm btn-success" data-toggle="modal" data-target="#unlock-note-modal">
<i class="fa fa-check-circle"></i> {% trans 'Unlock note' %}
</button>
{% endif %}
</div>
</div>
{% endblock %}
</div>
<div class="col-xl-8">
{% block profile_content %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "family/base.html" %}
{% comment %} {% comment %}
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %} {% endcomment %}

View File

@ -3,12 +3,14 @@
from django.urls import path from django.urls import path
from .views import FamilyListView, FamilyDetailView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView
app_name = 'family' app_name = 'family'
urlpatterns = [ urlpatterns = [
path('list/', FamilyListView.as_view(), name="family_list"), path('list/', FamilyListView.as_view(), name="family_list"),
path('detail/<int:pk>/', FamilyDetailView.as_view(), name="family_detail"), path('detail/<int:pk>/', FamilyDetailView.as_view(), name="family_detail"),
path('update/<int:pk>/', FamilyUpdateView.as_view(), name="family_update"),
path('<int:family_pk>/add_member', FamilyAddMemberView.as_view(), name="family_add_member"),
path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"),
path('challenge/detail/<int:pk>/', ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/detail/<int:pk>/', ChallengeDetailView.as_view(), name="challenge_detail"),
path('challenge/update/<int:pk>/', ChallengeUpdateView.as_view(), name="challenge_update"), path('challenge/update/<int:pk>/', ChallengeUpdateView.as_view(), name="challenge_update"),

View File

@ -13,7 +13,7 @@ from django.urls import reverse_lazy
from .models import Family, Challenge, FamilyMembership, User from .models import Family, Challenge, FamilyMembership, User
from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable
from .forms import ChallengeUpdateForm from .forms import ChallengeUpdateForm, FamilyMembershipForm
class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView):
@ -49,6 +49,35 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context_object_name = "family" context_object_name = "family"
extra_context = {"title": _('Family detail')} extra_context = {"title": _('Family detail')}
def get_context_data(self, **kwargs):
"""
Add members list
"""
context = super().get_context_data(**kwargs)
family = self.object
# member list
family_member = FamilyMembership.objects.filter(
family=family,
year=date.today().year,
).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\
.order_by("user__username").distinct("user__username")
membership_table = FamilyMembershipTable(data=family_member)
context['member_list'] = membership_table
# Check if the user has the right to create a membership, to display the button.
empty_membership = FamilyMembership(
family=family,
user=User.objects.first(),
year=date.today().year,
)
context["can_add_members"] = PermissionBackend()\
.has_perm(self.request.user, "family.add_membership", empty_membership)
return context
class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
@ -59,6 +88,30 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
extra_context = {"title": _('Update family')} extra_context = {"title": _('Update family')}
class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Add a membership to a family
"""
model = FamilyMembership
form_class = FamilyMembershipForm
template_name = 'family/add_member.html'
extra_context = {"title": _("Add a new member to the family")}
def get_sample_object(self):
if "family_pk" in self.kwargs:
family = Family.objects.get(pk=self.kwargs["family_pk"])
else:
family = FamilyMembership.objects.get(pk=self.kwargs["pk"]).family
return FamilyMembership(
user=self.request.user,
family=family,
year=date.today().year,
)
def get_success_url(self):
return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id})
class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
""" """
Create challenge Create challenge
@ -72,7 +125,7 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
description="Sample challenge", description="Sample challenge",
points=0, points=0,
) )
def get_success_url(self): def get_success_url(self):
return reverse_lazy('family:challenge_list') return reverse_lazy('family:challenge_list')
@ -103,7 +156,7 @@ class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context["fields"] = [( context["fields"] = [(
Challenge._meta.get_field(field).verbose_name.capitalize(), Challenge._meta.get_field(field).verbose_name.capitalize(),
value) for field, value in fields.items()] value) for field, value in fields.items()]
context["obtained"] = getattr(self.object, "obtained") context["obtained"] = self.object.obtained
context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge") context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge")
return context return context
@ -121,4 +174,4 @@ class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk})