diff --git a/apps/registration/forms.py b/apps/registration/forms.py index 0bcca9b..ae519f0 100644 --- a/apps/registration/forms.py +++ b/apps/registration/forms.py @@ -26,6 +26,18 @@ class SignupForm(UserCreationForm): fields = ('first_name', 'last_name', 'email', 'password1', 'password2', 'role',) +class UserForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["first_name"].required = True + self.fields["last_name"].required = True + self.fields["email"].required = True + + class Meta: + model = User + fields = ('first_name', 'last_name', 'email',) + + class StudentRegistrationForm(forms.ModelForm): class Meta: model = StudentRegistration diff --git a/apps/registration/models.py b/apps/registration/models.py index 4ab8d2b..e2dcc05 100644 --- a/apps/registration/models.py +++ b/apps/registration/models.py @@ -50,6 +50,10 @@ class Registration(PolymorphicModel): def type(self): raise NotImplementedError + @property + def form_class(self): + raise NotImplementedError + @property def participates(self): return isinstance(self, StudentRegistration) or isinstance(self, CoachRegistration) @@ -90,6 +94,11 @@ class StudentRegistration(Registration): def type(self): return _("student") + @property + def form_class(self): + from registration.forms import StudentRegistrationForm + return StudentRegistrationForm + class Meta: verbose_name = _("student registration") verbose_name_plural = _("student registrations") @@ -113,6 +122,11 @@ class CoachRegistration(Registration): def type(self): return _("coach") + @property + def form_class(self): + from registration.forms import CoachRegistrationForm + return CoachRegistrationForm + class Meta: verbose_name = _("coach registration") verbose_name_plural = _("coach registrations") @@ -127,6 +141,11 @@ class AdminRegistration(Registration): def type(self): return _("admin") + @property + def form_class(self): + from registration.forms import AdminRegistrationForm + return AdminRegistrationForm + class Meta: verbose_name = _("admin registration") verbose_name_plural = _("admin registrations") diff --git a/apps/registration/templates/registration/update_user.html b/apps/registration/templates/registration/update_user.html new file mode 100644 index 0000000..7e282d5 --- /dev/null +++ b/apps/registration/templates/registration/update_user.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% load crispy_forms_filters i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{ form|crispy }} + {{ registration_form|crispy }} +
+ +
+{% endblock content %} + diff --git a/apps/registration/templates/registration/update_user_modal.html b/apps/registration/templates/registration/update_user_modal.html new file mode 100644 index 0000000..34e6cb9 --- /dev/null +++ b/apps/registration/templates/registration/update_user_modal.html @@ -0,0 +1,21 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/apps/registration/templates/registration/user_detail.html b/apps/registration/templates/registration/user_detail.html new file mode 100644 index 0000000..06800af --- /dev/null +++ b/apps/registration/templates/registration/user_detail.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% load i18n %} + +{% block content %} +{% trans "any" as any %} + +
+
+

{{ user.first_name }} {{ user.last_name }}

+
+
+
+
{% trans "Last name:" %}
+
{{ user.last_name }}
+ +
{% trans "First name:" %}
+
{{ user.first_name }}
+ +
{% trans "Email:" %}
+
{{ user.email }}
+ + {% if user.registration.studentregistration %} +
{% trans "Student class:" %}
+
{{ user.registration.get_student_class_display }}
+ +
{% trans "School:" %}
+
{{ user.registration.school }}
+ {% elif user.registration.coachregistration %} +
{% trans "Profesional activity:" %}
+
{{ user.registration.professional_activity }}
+ {% elif user.registration.adminregistration %} +
{% trans "Role:" %}
+
{{ user.registration.role }}
+ {% endif %} + +
{% trans "Grant Animath to contact me in the future about other actions:" %}
+
{{ user.registration.give_contact_to_animath|yesno }}
+
+
+ +
+ + {% include "registration/update_user_modal.html" %} +{% endblock %} + +{% block extrajavascript %} + +{% endblock %} diff --git a/apps/registration/tests.py b/apps/registration/tests.py index 9c815f9..a81945d 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -23,6 +23,11 @@ class TestRegistration(TestCase): ) self.client.force_login(self.user) + self.student = User.objects.create(email="student@example.com") + StudentRegistration.objects.create(user=self.student, student_class=11, school="Earth") + self.coach = User.objects.create(email="coach@example.com") + CoachRegistration.objects.create(user=self.coach, professional_activity="Teacher") + def test_registration(self): response = self.client.get(reverse("registration:signup")) self.assertEqual(response.status_code, 200) @@ -105,15 +110,46 @@ class TestRegistration(TestCase): )) self.assertRedirects(response, reverse("index"), 302, 200) - def test_change_email(self): - self.user.email = "newaddressmail@example.com" - self.user.save() - self.assertEqual(self.user.email, self.user.username) + def test_user_detail(self): + response = self.client.get(reverse("registration:my_account_detail")) + self.assertRedirects(response, reverse("registration:user_detail", args=(self.user.pk,))) + + response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,))) + self.assertEqual(response.status_code, 200) + + def test_update_user(self): + for user, data in [(self.user, dict(role="Bot")), + (self.student, dict(student_class=11, school="Sky")), + (self.coach, dict(professional_activity="God"))]: + response = self.client.get(reverse("registration:update_user", args=(user.pk,))) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=dict( + first_name="Changed", + last_name="Name", + email="new_" + user.email, + give_contact_to_animath=True, + )) + self.assertEqual(response.status_code, 200) + + data.update( + first_name="Changed", + last_name="Name", + email="new_" + user.email, + give_contact_to_animath=True, + ) + response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=data) + self.assertRedirects(response, reverse("registration:user_detail", args=(user.pk,)), 302, 200) + user.refresh_from_db() + self.assertEqual(user.email, user.username) + self.assertFalse(user.registration.email_confirmed) + self.assertEqual(user.first_name, "Changed") def test_string_render(self): # TODO These string field tests will be removed when used in a template str(self.user.registration) self.assertRaises(NotImplementedError, lambda: Registration().type) + self.assertRaises(NotImplementedError, lambda: Registration().form_class) str(StudentRegistration().type) str(CoachRegistration().type) str(self.user.registration.type) # AdminRegistration diff --git a/apps/registration/urls.py b/apps/registration/urls.py index 324dc72..7bd4d95 100644 --- a/apps/registration/urls.py +++ b/apps/registration/urls.py @@ -1,6 +1,7 @@ from django.urls import path -from .views import SignupView, UserResendValidationEmailView, UserValidateView, UserValidationEmailSentView +from .views import MyAccountDetailView, SignupView, UserDetailView, UserResendValidationEmailView, UserUpdateView,\ + UserValidateView, UserValidationEmailSentView app_name = "registration" @@ -10,4 +11,7 @@ urlpatterns = [ path('validate_email/resend//', UserResendValidationEmailView.as_view(), name='email_validation_resend'), path('validate_email///', UserValidateView.as_view(), name='email_validation'), + path("user/", MyAccountDetailView.as_view(), name="my_account_detail"), + path("user//", UserDetailView.as_view(), name="user_detail"), + path("user//update/", UserUpdateView.as_view(), name="update_user"), ] diff --git a/apps/registration/views.py b/apps/registration/views.py index a9a21ba..ef33d99 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -8,9 +8,9 @@ from django.shortcuts import redirect, resolve_url from django.urls import reverse_lazy from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, TemplateView +from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView -from .forms import CoachRegistrationForm, SignupForm, StudentRegistrationForm +from .forms import CoachRegistrationForm, SignupForm, StudentRegistrationForm, UserForm class SignupView(CreateView): @@ -118,3 +118,40 @@ class UserResendValidationEmailView(LoginRequiredMixin, DetailView): user = self.get_object() user.registration.send_email_validation_link() return redirect('registration:email_validation_sent') + + +class MyAccountDetailView(LoginRequiredMixin, RedirectView): + def get_redirect_url(self, *args, **kwargs): + return reverse_lazy("registration:user_detail", args=(self.request.user.pk,)) + + +class UserDetailView(LoginRequiredMixin, DetailView): + model = User + template_name = "registration/user_detail.html" + + +class UserUpdateView(LoginRequiredMixin, UpdateView): + model = User + form_class = UserForm + template_name = "registration/update_user.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + user = self.get_object() + context["registration_form"] = user.registration.form_class(data=self.request.POST or None, + instance=self.object.registration) + return context + + @transaction.atomic + def form_valid(self, form): + user = form.instance + registration_form = user.registration.form_class(data=self.request.POST or None, + instance=self.object.registration) + if not registration_form.is_valid(): + return self.form_invalid(form) + + registration_form.save() + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("registration:user_detail", args=(self.object.pk,)) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 22b6098..aa518b5 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Corres2math\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-09-24 11:19+0200\n" +"POT-Creation-Date: 2020-09-24 18:38+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Yohann D'ANELLO \n" "Language-Team: LANGUAGE \n" @@ -34,6 +34,7 @@ msgstr "Cette tâche a échoué avec succès." #: apps/participation/templates/participation/join_team_modal.html:16 #: apps/participation/templates/participation/update_team_modal.html:16 #: apps/registration/templates/registration/login_modal.html:16 +#: apps/registration/templates/registration/update_user_modal.html:16 msgid "Close" msgstr "Fermer" @@ -41,7 +42,7 @@ msgstr "Fermer" msgid "Logs" msgstr "Logs" -#: apps/logs/models.py:22 apps/registration/models.py:16 +#: apps/logs/models.py:22 apps/registration/models.py:15 msgid "user" msgstr "utilisateur" @@ -102,7 +103,7 @@ msgstr "changelogs" msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" -#: apps/participation/forms.py:14 apps/participation/models.py:20 +#: apps/participation/forms.py:14 apps/participation/models.py:19 msgid "The trigram must be composed of three uppercase letters." msgstr "Le trigramme doit être composé de trois lettres majuscules." @@ -110,27 +111,27 @@ msgstr "Le trigramme doit être composé de trois lettres majuscules." msgid "No team was found with this access code." msgstr "Aucune équipe n'a été trouvée avec ce code d'accès." -#: apps/participation/models.py:13 +#: apps/participation/models.py:12 msgid "name" msgstr "nom" -#: apps/participation/models.py:19 +#: apps/participation/models.py:18 msgid "trigram" msgstr "trigramme" -#: apps/participation/models.py:27 +#: apps/participation/models.py:26 msgid "access code" msgstr "code d'accès" -#: apps/participation/models.py:28 +#: apps/participation/models.py:27 msgid "The access code let other people to join the team." msgstr "Le code d'accès permet aux autres participants de rejoindre l'équipe." -#: apps/participation/models.py:32 +#: apps/participation/models.py:31 msgid "Grant Animath to publish my video" msgstr "Autoriser Animath à publier ma vidéo" -#: apps/participation/models.py:33 +#: apps/participation/models.py:32 msgid "" "Give the authorisation to publish the video on the main website to promote " "the action." @@ -138,80 +139,80 @@ msgstr "" "Donner l'autorisation de publier la vidéo sur le site principal pour " "promouvoir les Correspondances." -#: apps/participation/models.py:43 +#: apps/participation/models.py:42 #, python-brace-format msgid "Team {name} ({trigram})" msgstr "Équipe {name} ({trigram})" -#: apps/participation/models.py:46 apps/participation/models.py:57 -#: apps/registration/models.py:73 apps/registration/models.py:106 +#: apps/participation/models.py:45 apps/participation/models.py:56 +#: apps/registration/models.py:76 apps/registration/models.py:114 msgid "team" msgstr "équipe" -#: apps/participation/models.py:47 +#: apps/participation/models.py:46 msgid "teams" msgstr "équipes" -#: apps/participation/models.py:61 +#: apps/participation/models.py:60 #, python-brace-format msgid "Problem #{problem:d}" msgstr "Problème n°{problem:d}" -#: apps/participation/models.py:64 +#: apps/participation/models.py:63 msgid "problem number" msgstr "numéro de problème" -#: apps/participation/models.py:73 +#: apps/participation/models.py:72 msgid "solution video" msgstr "vidéo de solution" -#: apps/participation/models.py:82 +#: apps/participation/models.py:81 msgid "received participation" msgstr "participation reçue" -#: apps/participation/models.py:91 +#: apps/participation/models.py:90 msgid "synthesis video" msgstr "vidéo de synthèse" -#: apps/participation/models.py:95 +#: apps/participation/models.py:94 #, python-brace-format msgid "Participation of the team {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})" -#: apps/participation/models.py:98 apps/participation/models.py:106 +#: apps/participation/models.py:97 apps/participation/models.py:105 msgid "participation" msgstr "participation" -#: apps/participation/models.py:99 +#: apps/participation/models.py:98 msgid "participations" msgstr "participations" -#: apps/participation/models.py:110 +#: apps/participation/models.py:109 msgid "link" msgstr "lien" -#: apps/participation/models.py:111 +#: apps/participation/models.py:110 msgid "The full video link." msgstr "Le lien complet de la vidéo." -#: apps/participation/models.py:117 +#: apps/participation/models.py:116 msgid "valid" msgstr "valide" -#: apps/participation/models.py:118 +#: apps/participation/models.py:117 msgid "The video got the validation of the administrators." msgstr "La vidéo a été validée par les administrateurs." -#: apps/participation/models.py:122 +#: apps/participation/models.py:121 #, python-brace-format msgid "Video of team {name} ({trigram})" msgstr "Vidéo de l'équipe {name} ({trigram})" -#: apps/participation/models.py:126 +#: apps/participation/models.py:125 msgid "video" msgstr "vidéo" -#: apps/participation/models.py:127 +#: apps/participation/models.py:126 msgid "videos" msgstr "vidéos" @@ -236,6 +237,7 @@ msgid "Join team" msgstr "Rejoindre une équipe" #: apps/participation/templates/participation/team_detail.html:6 +#: apps/registration/templates/registration/user_detail.html:6 msgid "any" msgstr "aucun" @@ -270,6 +272,9 @@ msgstr "Autoriser Animath à publier notre vidéo :" #: apps/participation/templates/participation/team_detail.html:37 #: apps/participation/templates/participation/update_team.html:12 #: apps/participation/templates/participation/update_team_modal.html:15 +#: apps/registration/templates/registration/update_user.html:12 +#: apps/registration/templates/registration/update_user_modal.html:15 +#: apps/registration/templates/registration/user_detail.html:42 msgid "Update" msgstr "Modifier" @@ -285,11 +290,11 @@ msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgid "You are already in a team." msgstr "Vous êtes déjà dans une équipe." -#: apps/participation/views.py:71 +#: apps/participation/views.py:72 msgid "You are not in a team." msgstr "Vous n'êtes pas dans une équipe." -#: apps/participation/views.py:72 +#: apps/participation/views.py:73 msgid "You don't participate, so you don't have any team." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." @@ -301,88 +306,88 @@ msgstr "rôle" msgid "participant" msgstr "participant" -#: apps/registration/forms.py:14 apps/registration/models.py:115 +#: apps/registration/forms.py:14 apps/registration/models.py:123 msgid "coach" msgstr "encadrant" -#: apps/registration/models.py:21 +#: apps/registration/models.py:20 msgid "Grant Animath to contact me in the future about other actions" msgstr "" "Autoriser Animath à me recontacter à l'avenir à propos d'autres actions" -#: apps/registration/models.py:26 +#: apps/registration/models.py:25 msgid "email confirmed" msgstr "email confirmé" -#: apps/registration/models.py:30 +#: apps/registration/models.py:29 msgid "Activate your Correspondances account" msgstr "Activez votre compte des Correspondances" -#: apps/registration/models.py:62 +#: apps/registration/models.py:65 msgid "registration" msgstr "inscription" -#: apps/registration/models.py:63 +#: apps/registration/models.py:66 msgid "registrations" msgstr "inscriptions" -#: apps/registration/models.py:78 +#: apps/registration/models.py:81 msgid "12th grade" msgstr "Terminale" -#: apps/registration/models.py:79 +#: apps/registration/models.py:82 msgid "11th grade" msgstr "Première" -#: apps/registration/models.py:80 +#: apps/registration/models.py:83 msgid "10th grade or lower" msgstr "Seconde ou inférieur" -#: apps/registration/models.py:82 +#: apps/registration/models.py:85 msgid "student class" msgstr "classe" -#: apps/registration/models.py:87 +#: apps/registration/models.py:90 msgid "school" msgstr "école" -#: apps/registration/models.py:92 +#: apps/registration/models.py:95 msgid "student" msgstr "étudiant" -#: apps/registration/models.py:95 +#: apps/registration/models.py:103 msgid "student registration" msgstr "inscription d'élève" -#: apps/registration/models.py:96 +#: apps/registration/models.py:104 msgid "student registrations" msgstr "inscriptions d'élève" -#: apps/registration/models.py:110 +#: apps/registration/models.py:118 msgid "professional activity" msgstr "activité professionnelle" -#: apps/registration/models.py:118 +#: apps/registration/models.py:131 msgid "coach registration" msgstr "inscription d'encadrant" -#: apps/registration/models.py:119 +#: apps/registration/models.py:132 msgid "coach registrations" msgstr "inscriptions d'encadrants" -#: apps/registration/models.py:124 +#: apps/registration/models.py:137 msgid "role of the administrator" msgstr "rôle de l'administrateur" -#: apps/registration/models.py:129 +#: apps/registration/models.py:142 msgid "admin" msgstr "admin" -#: apps/registration/models.py:132 +#: apps/registration/models.py:150 msgid "admin registration" msgstr "inscription d'administrateur" -#: apps/registration/models.py:133 +#: apps/registration/models.py:151 msgid "admin registrations" msgstr "inscriptions d'administrateur" @@ -538,6 +543,42 @@ msgstr "Réinitialiser mon mot de passe" msgid "Sign up" msgstr "Inscription" +#: apps/registration/templates/registration/update_user_modal.html:8 +msgid "Update user" +msgstr "Modifier l'utilisateur" + +#: apps/registration/templates/registration/user_detail.html:14 +msgid "Last name:" +msgstr "Nom de famille :" + +#: apps/registration/templates/registration/user_detail.html:17 +msgid "First name:" +msgstr "Prénom :" + +#: apps/registration/templates/registration/user_detail.html:20 +msgid "Email:" +msgstr "Adresse e-mail :" + +#: apps/registration/templates/registration/user_detail.html:24 +msgid "Student class:" +msgstr "Classe :" + +#: apps/registration/templates/registration/user_detail.html:27 +msgid "School:" +msgstr "École :" + +#: apps/registration/templates/registration/user_detail.html:30 +msgid "Profesional activity:" +msgstr "Activité professionnelle :" + +#: apps/registration/templates/registration/user_detail.html:33 +msgid "Role:" +msgstr "Rôle :" + +#: apps/registration/templates/registration/user_detail.html:37 +msgid "Grant Animath to contact me in the future about other actions:" +msgstr "Autorise Animath à recontacter à propos d'autres actions :" + #: apps/registration/views.py:55 msgid "Email validation" msgstr "Validation de l'adresse mail" @@ -558,11 +599,11 @@ msgstr "Mail de confirmation de l'adresse mail envoyé" msgid "Resend email validation link" msgstr "Renvoyé le lien de validation de l'adresse mail" -#: corres2math/settings.py:149 +#: corres2math/settings.py:150 msgid "English" msgstr "Anglais" -#: corres2math/settings.py:150 +#: corres2math/settings.py:151 msgid "French" msgstr "Français" @@ -644,10 +685,14 @@ msgid "Register" msgstr "S'inscrire" #: templates/base.html:120 +msgid "My account" +msgstr "Mon compte" + +#: templates/base.html:123 msgid "Log out" msgstr "Déconnexion" -#: templates/base.html:147 +#: templates/base.html:150 msgid "Contact us" msgstr "Nous contacter" diff --git a/templates/base.html b/templates/base.html index 5bac730..2289469 100644 --- a/templates/base.html +++ b/templates/base.html @@ -116,6 +116,9 @@