diff --git a/apps/registration/forms.py b/apps/registration/forms.py
index ffe2ac0..731cbd7 100644
--- a/apps/registration/forms.py
+++ b/apps/registration/forms.py
@@ -1,7 +1,6 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
-from address.forms import AddressField
-from address.widgets import AddressWidget
+
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
@@ -69,7 +68,8 @@ class StudentRegistrationForm(forms.ModelForm):
class Meta:
model = StudentRegistration
fields = ('team', 'student_class', 'birth_date', 'address', 'phone_number',
- 'school', 'give_contact_to_animath', 'email_confirmed',)
+ 'school', 'responsible_name', 'responsible_phone', 'responsible_email',
+ 'give_contact_to_animath', 'email_confirmed',)
class PhotoAuthorizationForm(forms.ModelForm):
@@ -94,6 +94,50 @@ class PhotoAuthorizationForm(forms.ModelForm):
fields = ('photo_authorization',)
+class HealthSheetForm(forms.ModelForm):
+ """
+ Form to send a health sheet.
+ """
+ def clean_health_sheet(self):
+ if "health_sheet" in self.files:
+ file = self.files["health_sheet"]
+ if file.size > 2e6:
+ raise ValidationError(_("The uploaded file size must be under 2 Mo."))
+ if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
+ raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
+ return self.cleaned_data["health_sheet"]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["health_sheet"].widget = FileInput()
+
+ class Meta:
+ model = StudentRegistration
+ fields = ('health_sheet',)
+
+
+class ParentalAuthorizationForm(forms.ModelForm):
+ """
+ Form to send a parental authorization.
+ """
+ def clean_parental_authorization(self):
+ if "parental_authorization" in self.files:
+ file = self.files["parental_authorization"]
+ if file.size > 2e6:
+ raise ValidationError(_("The uploaded file size must be under 2 Mo."))
+ if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
+ raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
+ return self.cleaned_data["parental_authorization"]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["parental_authorization"].widget = FileInput()
+
+ class Meta:
+ model = StudentRegistration
+ fields = ('parental_authorization',)
+
+
class CoachRegistrationForm(forms.ModelForm):
"""
A coach can tell its professional activity.
diff --git a/apps/registration/models.py b/apps/registration/models.py
index 61d5b0c..ff1cdc4 100644
--- a/apps/registration/models.py
+++ b/apps/registration/models.py
@@ -1,6 +1,8 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
+from datetime import date
+
from address.models import AddressField
from django.contrib.sites.models import Site
from django.db import models
@@ -73,7 +75,7 @@ class Registration(PolymorphicModel):
@property
def participates(self):
- return isinstance(self, StudentRegistration) or isinstance(self, CoachRegistration)
+ return isinstance(self, ParticipantRegistration)
@property
def is_admin(self):
@@ -119,7 +121,7 @@ class ParticipantRegistration(Registration):
birth_date = models.DateField(
verbose_name=_("birth date"),
- default=timezone.now,
+ default=date.today,
)
address = AddressField(
@@ -147,6 +149,10 @@ class ParticipantRegistration(Registration):
default="",
)
+ @property
+ def under_18(self):
+ return (timezone.now().date() - self.birth_date).days < 18 * 365.24
+
@property
def type(self): # pragma: no cover
raise NotImplementedError
diff --git a/apps/registration/templates/registration/upload_health_sheet.html b/apps/registration/templates/registration/upload_health_sheet.html
new file mode 100644
index 0000000..0c4415b
--- /dev/null
+++ b/apps/registration/templates/registration/upload_health_sheet.html
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+
+{% load i18n static crispy_forms_filters %}
+
+{% block content %}
+ {% trans "Back to the user detail" %}
+
{% csrf_token %}
{{ form|crispy }}
diff --git a/apps/registration/templates/registration/user_detail.html b/apps/registration/templates/registration/user_detail.html
index 710cf48..d3ec639 100644
--- a/apps/registration/templates/registration/user_detail.html
+++ b/apps/registration/templates/registration/user_detail.html
@@ -21,7 +21,7 @@
{{ user_object.email }}
{% if not user_object.registration.email_confirmed %} ({% trans "Not confirmed" %}, {% trans "resend the validation link" %}){% endif %}
- {% if user_object.registration.participates or True %}
+ {% if user_object.registration.participates %}
{% trans "Team:" %}
{% trans "any" as any %}
@@ -29,14 +29,15 @@
{{ user_object.registration.team|default:any }}
- {% endif %}
- {% if user_object.registration.studentregistration %}
-
{% trans "Student class:" %}
-
{{ user_object.registration.get_student_class_display }}
+
{% trans "Birth date:" %}
+
{{ user_object.registration.birth_date }}
-
{% trans "School:" %}
-
{{ user_object.registration.school }}
+
{% trans "Address:" %}
+
{{ user_object.registration.address }}
+
+
{% trans "Phone number:" %}
+
{{ user_object.registration.phone_number }}
{% trans "Photo authorization:" %}
@@ -47,6 +48,47 @@
{% endif %}
+
+
{% trans "Health sheet:" %}
+
+ {% if user_object.registration.health_sheet %}
+ {% trans "Download" %}
+ {% endif %}
+ {% if user_object.pk == user.pk %}
+
+ {% endif %}
+
+ {% endif %}
+
+ {% if user_object.registration.studentregistration %}
+ {% if user_object.registration.under_18 %}
+
{% trans "Parental authorization:" %}
+
+ {% if user_object.registration.parental_authorization %}
+ {% trans "Download" %}
+ {% endif %}
+ {% if user_object.pk == user.pk %}
+
+ {% endif %}
+
+ {% endif %}
+
+
{% trans "Student class:" %}
+
{{ user_object.registration.get_student_class_display }}
+
+
{% trans "School:" %}
+
{{ user_object.registration.school }}
+
+
{% trans "Responsible name:" %}
+
{{ user_object.registration.responsible_name }}
+
+
{% trans "Responsible phone number:" %}
+
{{ user_object.registration.responsible_phone }}
+
+
{% trans "Responsible email address:" %}
+ {% with user_object.registration.responsible_email as email %}
+
{{ email }}
+ {% endwith %}
{% elif user_object.registration.coachregistration %}
{% trans "Profesional activity:" %}
{{ user_object.registration.professional_activity }}
@@ -78,6 +120,16 @@
{% trans "Upload" as modal_button %}
{% url "registration:upload_user_photo_authorization" pk=user_object.registration.pk as modal_action %}
{% include "base_modal.html" with modal_id="uploadPhotoAuthorization" modal_enctype="multipart/form-data" %}
+
+ {% trans "Upload health sheet" as modal_title %}
+ {% trans "Upload" as modal_button %}
+ {% url "registration:upload_user_health_sheet" pk=user_object.registration.pk as modal_action %}
+ {% include "base_modal.html" with modal_id="uploadHealthSheet" modal_enctype="multipart/form-data" %}
+
+ {% trans "Upload parental authorization" as modal_title %}
+ {% trans "Upload" as modal_button %}
+ {% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk as modal_action %}
+ {% include "base_modal.html" with modal_id="uploadParentalAuthorization" modal_enctype="multipart/form-data" %}
{% endblock %}
{% block extrajavascript %}
@@ -93,6 +145,16 @@
if (!modalBody.html().trim())
modalBody.load("{% url "registration:upload_user_photo_authorization" pk=user_object.registration.pk %} #form-content");
});
+ $('button[data-target="#uploadHealthSheetModal"]').click(function() {
+ let modalBody = $("#uploadHealthSheetModal div.modal-body");
+ if (!modalBody.html().trim())
+ modalBody.load("{% url "registration:upload_user_health_sheet" pk=user_object.registration.pk %} #form-content");
+ });
+ $('button[data-target="#uploadParentalAuthorizationModal"]').click(function() {
+ let modalBody = $("#uploadParentalAuthorizationModal div.modal-body");
+ if (!modalBody.html().trim())
+ modalBody.load("{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk %} #form-content");
+ });
});
{% endblock %}
diff --git a/apps/registration/tests.py b/apps/registration/tests.py
index 9317ed2..b620b51 100644
--- a/apps/registration/tests.py
+++ b/apps/registration/tests.py
@@ -268,37 +268,38 @@ class TestRegistration(TestCase):
"""
Try to upload a photo authorization.
"""
- response = self.client.get(reverse("registration:upload_user_photo_authorization",
- args=(self.student.registration.pk,)))
- self.assertEqual(response.status_code, 200)
+ for auth_type in ["photo_authorization", "health_sheet", "parental_authorization"]:
+ response = self.client.get(reverse("registration:upload_user_photo_authorization",
+ args=(self.student.registration.pk,)))
+ self.assertEqual(response.status_code, 200)
- # README is not a valid PDF file
- response = self.client.post(reverse("registration:upload_user_photo_authorization",
- args=(self.student.registration.pk,)), data=dict(
- photo_authorization=open("README.md", "rb"),
- ))
- self.assertEqual(response.status_code, 200)
+ # README is not a valid PDF file
+ response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
+ args=(self.student.registration.pk,)), data={
+ auth_type: open("README.md", "rb"),
+ })
+ self.assertEqual(response.status_code, 200)
- # Don't send too large files
- response = self.client.post(reverse("registration:upload_user_photo_authorization",
- args=(self.student.registration.pk,)), data=dict(
- photo_authorization=SimpleUploadedFile("file.pdf", content=int(0).to_bytes(2000001, "big"),
- content_type="application/pdf"),
- ))
- self.assertEqual(response.status_code, 200)
+ # Don't send too large files
+ response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
+ args=(self.student.registration.pk,)), data={
+ auth_type: SimpleUploadedFile("file.pdf", content=int(0).to_bytes(2000001, "big"),
+ content_type="application/pdf"),
+ })
+ self.assertEqual(response.status_code, 200)
- response = self.client.post(reverse("registration:upload_user_photo_authorization",
- args=(self.student.registration.pk,)), data=dict(
- photo_authorization=open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
- ))
- self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
+ response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
+ args=(self.student.registration.pk,)), data={
+ auth_type: open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
+ })
+ self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
- self.student.registration.refresh_from_db()
- self.assertTrue(self.student.registration.photo_authorization)
+ self.student.registration.refresh_from_db()
+ self.assertTrue(getattr(self.student.registration, auth_type))
- response = self.client.get(reverse("photo_authorization",
- args=(self.student.registration.photo_authorization.name.split('/')[-1],)))
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse(
+ auth_type, args=(self.student.registration.photo_authorization.name.split('/')[-1],)))
+ self.assertEqual(response.status_code, 200)
from participation.models import Team
team = Team.objects.create(name="Test", trigram="TES")
diff --git a/apps/registration/urls.py b/apps/registration/urls.py
index 7d6bf01..de0fc63 100644
--- a/apps/registration/urls.py
+++ b/apps/registration/urls.py
@@ -4,7 +4,8 @@
from django.urls import path
from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \
- UserListView, UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \
+ UserListView, UserResendValidationEmailView, UserUpdateView, UserUploadHealthSheetView, \
+ UserUploadParentalAuthorizationView, UserUploadPhotoAuthorizationView, UserValidateView, \
UserValidationEmailSentView
app_name = "registration"
@@ -20,6 +21,10 @@ urlpatterns = [
path("user/
/update/", UserUpdateView.as_view(), name="update_user"),
path("user//upload-photo-authorization/", UserUploadPhotoAuthorizationView.as_view(),
name="upload_user_photo_authorization"),
+ path("user//upload-health_sheet/", UserUploadHealthSheetView.as_view(),
+ name="upload_user_health_sheet"),
+ path("user//upload-parental-authorization/", UserUploadParentalAuthorizationView.as_view(),
+ name="upload_user_parental_authorization"),
path("user//impersonate/", UserImpersonateView.as_view(), name="user_impersonate"),
path("user/list/", UserListView.as_view(), name="user_list"),
path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"),
diff --git a/apps/registration/views.py b/apps/registration/views.py
index 723ccd0..f36f477 100644
--- a/apps/registration/views.py
+++ b/apps/registration/views.py
@@ -17,9 +17,10 @@ from django.views.generic import CreateView, DetailView, RedirectView, TemplateV
from django_tables2 import SingleTableView
from magic import Magic
from tfjm.tokens import email_validation_token
-from tfjm.views import AdminMixin
+from tfjm.views import AdminMixin, UserMixin
-from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm
+from .forms import CoachRegistrationForm, HealthSheetForm, ParentalAuthorizationForm, PhotoAuthorizationForm,\
+ SignupForm, StudentRegistrationForm, UserForm
from .models import Registration, StudentRegistration
from .tables import RegistrationTable
@@ -150,7 +151,7 @@ class MyAccountDetailView(LoginRequiredMixin, RedirectView):
return reverse_lazy("registration:user_detail", args=(self.request.user.pk,))
-class UserDetailView(LoginRequiredMixin, DetailView):
+class UserDetailView(UserMixin, DetailView):
"""
Display the detail about a user.
"""
@@ -159,15 +160,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
context_object_name = "user_object"
template_name = "registration/user_detail.html"
- def dispatch(self, request, *args, **kwargs):
- user = request.user
- if not user.is_authenticated:
- return self.handle_no_permission()
- # Only an admin or the concerned user can see the information
- if not user.registration.is_admin and user.pk != kwargs["pk"]:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = _("Detail of user {user}").format(user=str(self.object.registration))
@@ -183,7 +175,7 @@ class UserListView(AdminMixin, SingleTableView):
template_name = "registration/user_list.html"
-class UserUpdateView(LoginRequiredMixin, UpdateView):
+class UserUpdateView(UserMixin, UpdateView):
"""
Update the detail about a user and its registration.
"""
@@ -191,12 +183,6 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
form_class = UserForm
template_name = "registration/update_user.html"
- def dispatch(self, request, *args, **kwargs):
- user = request.user
- if not user.registration.is_admin and user.pk != kwargs["pk"]:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.get_object()
@@ -229,7 +215,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return reverse_lazy("registration:user_detail", args=(self.object.pk,))
-class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
+class UserUploadPhotoAuthorizationView(UserMixin, UpdateView):
"""
A participant can send its photo authorization.
"""
@@ -238,12 +224,6 @@ class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
template_name = "registration/upload_photo_authorization.html"
extra_context = dict(title=_("Upload photo authorization"))
- def dispatch(self, request, *args, **kwargs):
- user = request.user
- if not user.registration.is_admin and user.registration.pk != kwargs["pk"]:
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
@transaction.atomic
def form_valid(self, form):
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
@@ -255,6 +235,46 @@ class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
+class UserUploadHealthSheetView(UserMixin, UpdateView):
+ """
+ A participant can send its health sheet.
+ """
+ model = StudentRegistration
+ form_class = HealthSheetForm
+ template_name = "registration/upload_health_sheet.html"
+ extra_context = dict(title=_("Upload health sheet"))
+
+ @transaction.atomic
+ def form_valid(self, form):
+ old_instance = StudentRegistration.objects.get(pk=self.object.pk)
+ if old_instance.health_sheet:
+ old_instance.health_sheet.delete()
+ return super().form_valid(form)
+
+ def get_success_url(self):
+ return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
+
+
+class UserUploadParentalAuthorizationView(UserMixin, UpdateView):
+ """
+ A participant can send its parental authorization.
+ """
+ model = StudentRegistration
+ form_class = ParentalAuthorizationForm
+ template_name = "registration/upload_parental_authorization.html"
+ extra_context = dict(title=_("Upload parental authorization"))
+
+ @transaction.atomic
+ def form_valid(self, form):
+ old_instance = StudentRegistration.objects.get(pk=self.object.pk)
+ if old_instance.parental_authorization:
+ old_instance.parental_authorization.delete()
+ return super().form_valid(form)
+
+ def get_success_url(self):
+ return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
+
+
class PhotoAuthorizationView(LoginRequiredMixin, View):
"""
Display the sent photo authorization.
diff --git a/tfjm/views.py b/tfjm/views.py
index 76a43df..81bdae2 100644
--- a/tfjm/views.py
+++ b/tfjm/views.py
@@ -8,7 +8,15 @@ from haystack.generic_views import SearchView
class AdminMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
- if not request.user.registration.is_admin:
+ if request.user.is_authenticated and not request.user.registration.is_admin:
+ raise PermissionDenied
+ return super().dispatch(request, *args, **kwargs)
+
+
+class UserMixin(LoginRequiredMixin):
+ def dispatch(self, request, *args, **kwargs):
+ user = request.user
+ if user.is_authenticated and not user.registration.is_admin and user.registration.pk != kwargs["pk"]:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)