Merge branch 'Aufinal/bds_create_user' into 'master'

Création d'utilisateurs pour le BDS

See merge request klub-dev-ens/gestioCOF!433
This commit is contained in:
Martin Pepin 2020-07-27 21:41:12 +02:00
commit ae64f09869
9 changed files with 275 additions and 44 deletions

View file

@ -1,5 +1,8 @@
from urllib.parse import urlencode
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from shared import autocomplete
@ -21,7 +24,7 @@ class BDSMemberSearch(autocomplete.ModelSearch):
return user.username
def result_link(self, user):
return "#TODO"
return reverse("bds:user.update", args=(user.pk,))
class BDSOthersSearch(autocomplete.ModelSearch):
@ -38,12 +41,15 @@ class BDSOthersSearch(autocomplete.ModelSearch):
return user.username
def result_link(self, user):
return "#TODO"
return reverse("bds:user.update", args=(user.pk,))
class BDSLDAPSearch(autocomplete.LDAPSearch):
def result_link(self, clipper):
return "#TODO"
url = reverse("bds:user.create.fromclipper", args=(clipper.clipper,))
get = {"fullname": clipper.fullname}
return "{}?{}".format(url, urlencode(get))
class BDSSearch(autocomplete.Compose):

View file

@ -1,5 +1,6 @@
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from bds.models import BDSProfile
@ -12,7 +13,24 @@ class UserForm(forms.ModelForm):
fields = ["email", "first_name", "last_name"]
class UserFromClipperForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["username"].disabled = True
class Meta:
model = User
fields = ["username", "email", "first_name", "last_name"]
class UserFromScratchForm(UserCreationForm):
class Meta:
model = User
fields = ["username", "email", "first_name", "last_name"]
class ProfileForm(forms.ModelForm):
class Meta:
model = BDSProfile
exclude = ["user"]
widgets = {"birthdate": forms.DateInput(attrs={"type": "date"})}

View file

@ -1,5 +1,122 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
class StaffRequiredMixin(PermissionRequiredMixin):
permission_required = "bds.is_team"
class MultipleFormMixin(ContextMixin):
""" Mixin pour gérer plusieurs formulaires dans la même vue.
Le fonctionnement est relativement identique à celui de
FormMixin, dont la documentation est disponible ici :
https://docs.djangoproject.com/en/3.0/ref/class-based-views/mixins-editing/
Les principales différences sont :
- au lieu de form_class, il faut donner comme attribut un dict de la forme
{<form_name>: <form_class>}, avec tous les formulaires à instancier. On
peut aussi redéfinir `get_form_classes`
- les données initiales se récupèrent pour chaque form via l'attribut
`<form_name>_initial` ou la fonction `get_<form_name>_initial`. De même,
si certaines forms sont des `ModelForm`s, on peut définir la fonction
`get_<form_name>_instance`.
- chaque form a un préfixe rajouté, par défaut <form_name>, mais qui peut
être customisé via `prefixes` ou `get_prefixes`.
"""
form_classes = {}
prefixes = {}
initial = {}
success_url = None
def get_form_classes(self):
return self.form_classes
def get_initial(self, form_name):
initial_attr = "%s_initial" % form_name
initial_method = "get_%s_initial" % form_name
initial_method = getattr(self, initial_method, None)
if hasattr(self, initial_attr):
return getattr(self, initial_attr)
elif callable(initial_method):
return initial_method()
else:
return self.initial.copy()
def get_prefix(self, form_name):
return self.prefixes.get(form_name, form_name)
def get_instance(self, form_name):
# Au cas où certaines des forms soient des ModelForms
instance_method = "get_%s_instance" % form_name
instance_method = getattr(self, instance_method, None)
if callable(instance_method):
return instance_method()
else:
return None
def get_form_kwargs(self, form_name):
kwargs = {
"initial": self.get_initial(form_name),
"prefix": self.get_prefix(form_name),
"instance": self.get_instance(form_name),
}
if self.request.method in ("POST", "PUT"):
kwargs.update({"data": self.request.POST, "files": self.request.FILES})
return kwargs
def get_forms(self):
form_classes = self.get_form_classes()
return {
form_name: form_class(**self.get_form_kwargs(form_name))
for form_name, form_class in form_classes.items()
}
def get_success_url(self):
if not self.success_url:
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
return str(self.success_url)
def form_valid(self, forms):
# on garde le nom form_valid pour l'interface avec SuccessMessageMixin
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, forms):
"""If the form is invalid, render the invalid form."""
return self.render_to_response(self.get_context_data(forms=forms))
class ProcessMultipleFormView(View):
""" Équivalent de `ProcessFormView` pour plusieurs forms.
Note : il faut que *tous* les formulaires soient valides pour
qu'ils soient sauvegardés !
"""
def get(self, request, *args, **kwargs):
forms = self.get_forms()
return self.render_to_response(self.get_context_data(forms=forms))
def post(self, request, *args, **kwargs):
forms = self.get_forms()
if all(form.is_valid() for form in forms.values()):
return self.form_valid(forms)
else:
return self.form_invalid(forms)
class BaseMultipleFormView(MultipleFormMixin, ProcessMultipleFormView):
pass
class MultipleFormView(TemplateResponseMixin, BaseMultipleFormView):
pass

View file

@ -62,6 +62,8 @@ class BDSProfile(models.Model):
null=True,
)
is_member = models.BooleanField(_("adhérent⋅e du BDS"), default=False)
mails_bds = models.BooleanField(_("recevoir les mails du BDS"), default=False)
has_certificate = models.BooleanField(_("certificat médical"), default=False)
@ -77,8 +79,6 @@ class BDSProfile(models.Model):
FFSU_number = models.CharField(
_("numéro FFSU"), max_length=50, blank=True, null=True
)
is_member = models.BooleanField(_("adhérent⋅e du BDS"), default=False)
cotisation_period = models.CharField(
_("inscription"), default="NO", choices=COTIZ_DURATION_CHOICES, max_length=3
)

View file

@ -14,7 +14,7 @@
{% endif %}
</li>
<li class="autocomplete-new">
<a class="autocomplete-item" href="#TODO">
<a class="autocomplete-item" href="{% url "bds:user.create" %}">
{% trans "Créer un compte" %}
</a>
</li>

View file

@ -0,0 +1,33 @@
{% extends "bds/base.html" %}
{% load i18n %}
{% block content %}
{% for form in forms.values %}
{% for error in form.non_field_errors %}
<div class="notification is-danger">
{{ error }}
</div>
{% endfor %}
{% endfor %}
<h1 class="title">{% trans "Création d'un profil" %}</h1>
<div class="container">
<form action="" method="post">
{% csrf_token %}
{% for form in forms.values %}
{% include "bds/forms/form.html" with form=form errors=False %}
{% endfor %}
<div class="field">
<p class="control">
<input class="button is-fullwidth" type="submit" value="Enregistrer">
</p>
</div>
</form>
</div>
{% endblock %}

View file

@ -4,21 +4,23 @@
{% block content %}
{% for error in user_form.non_field_errors %}
<p class="error">{{ error }}</p>
{% endfor %}
{% for error in profile_form.non_field_errors %}
<p class="error">{{ error }}</p>
{% for form in forms.values %}
{% for error in form.non_field_errors %}
<div class="notification is-danger">
{{ error }}
</div>
{% endfor %}
{% endfor %}
<h1 class="title">{% trans "Modification de l'utilisateur " %}{{user_form.instance.username}}</h1>
<h1 class="title">{% trans "Modification du profil " %}{{ view.user.username }}</h1>
<div class="container">
<form action="" method="post">
{% csrf_token %}
{% include "bds/forms/form.html" with form=user_form errors=False %}
{% include "bds/forms/form.html" with form=profile_form errors=False %}
{% for form in forms.values %}
{% include "bds/forms/form.html" with form=form errors=False %}
{% endfor %}
<div class="field">
<p class="control">

View file

@ -7,4 +7,10 @@ urlpatterns = [
path("", views.Home.as_view(), name="home"),
path("autocomplete", views.BDSAutocompleteView.as_view(), name="autocomplete"),
path("user/update/<int:pk>", views.UserUpdateView.as_view(), name="user.update"),
path("user/create/", views.UserCreateView.as_view(), name="user.create"),
path(
"user/create/<slug:clipper>",
views.UserCreateView.as_view(),
name="user.create.fromclipper",
),
]

View file

@ -1,12 +1,13 @@
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from bds.autocomplete import bds_search
from bds.forms import ProfileForm, UserForm
from bds.mixins import StaffRequiredMixin
from bds.forms import ProfileForm, UserForm, UserFromClipperForm, UserFromScratchForm
from bds.mixins import MultipleFormView, StaffRequiredMixin
from shared.views import AutocompleteView
User = get_user_model()
@ -21,42 +22,90 @@ class Home(StaffRequiredMixin, TemplateView):
template_name = "bds/home.html"
class UserUpdateView(StaffRequiredMixin, TemplateView):
class UserUpdateView(StaffRequiredMixin, MultipleFormView):
template_name = "bds/user_update.html"
def get_user(self):
return get_object_or_404(User, pk=self.kwargs["pk"])
form_classes = {
"user": UserForm,
"profile": ProfileForm,
}
def get_user_form(self, data=None):
return UserForm(prefix="u", instance=self.user, data=data)
def dispatch(self, request, *args, **kwargs):
self.user = get_object_or_404(User, pk=self.kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_profile_form(self, data=None):
profile = getattr(self.user, "bds", None)
return ProfileForm(prefix="p", instance=profile, data=data)
def get_user_instance(self):
return self.user
def get_context_data(self, user_form=None, profile_form=None, **kwargs):
return {
"user_form": user_form or self.get_user_form(),
"profile_form": profile_form or self.get_profile_form(),
}
def get_profile_instance(self):
return getattr(self.user, "bds", None)
def get(self, *args, **kwargs):
self.user = self.get_user()
return super().get(*args, **kwargs)
def get_success_url(self):
return reverse("bds:user.update", args=(self.user.pk,))
def post(self, request, *args, **kwargs):
self.user = self.get_user()
user_form = self.get_user_form(data=request.POST)
profile_form = self.get_profile_form(data=request.POST)
def form_valid(self, forms):
user = forms["user"].save()
profile = forms["profile"].save(commit=False)
profile.user = user
profile.save()
messages.success(self.request, _("Profil mis à jour avec succès !"))
if user_form.is_valid() and profile_form.is_valid():
self.user = user_form.save()
profile = profile_form.save(commit=False)
profile.user = self.user
profile.save()
messages.success(self.request, _("Profil mis à jour avec succès"))
return super().form_valid(forms)
def form_invalid(self, forms):
messages.error(self.request, _("Veuillez corriger les erreurs ci-dessous"))
return super().form_invalid(forms)
class UserCreateView(StaffRequiredMixin, MultipleFormView):
template_name = "bds/user_create.html"
def get_form_classes(self):
profile_class = ProfileForm
if "clipper" in self.kwargs:
user_class = UserFromClipperForm
else:
messages.error(self.request, _("Veuillez corriger les erreurs ci-dessous"))
user_class = UserFromScratchForm
return self.render_to_response(self.get_context_data(user_form, profile_form))
return {"user": user_class, "profile": profile_class}
def get_user_initial(self):
if "clipper" in self.kwargs:
clipper = self.kwargs["clipper"]
email = "{}@clipper.ens.fr".format(clipper)
fullname = self.request.GET.get("fullname", None)
if fullname:
# Heuristique : le premier mot est le prénom
first_name = fullname.split()[0]
last_name = " ".join(fullname.split()[1:])
else:
first_name = ""
last_name = ""
return {
"username": clipper,
"email": email,
"first_name": first_name,
"last_name": last_name,
}
else:
return {}
def get_success_url(self):
return reverse("bds:user.update", args=(self.user.pk,))
def form_valid(self, forms):
# On redéfinit self.user pour get_success_url
self.user = forms["user"].save()
profile = forms["profile"].save(commit=False)
profile.user = self.user
profile.save()
messages.success(self.request, _("Profil créé avec succès !"))
return super().form_valid(forms)
def form_invalid(self, forms):
messages.error(self.request, _("Veuillez corriger les erreurs ci-dessous"))
return super().form_invalid(forms)