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:
commit
ae64f09869
9 changed files with 275 additions and 44 deletions
|
@ -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):
|
||||
|
|
18
bds/forms.py
18
bds/forms.py
|
@ -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"})}
|
||||
|
|
117
bds/mixins.py
117
bds/mixins.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
33
bds/templates/bds/user_create.html
Normal file
33
bds/templates/bds/user_create.html
Normal 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 %}
|
|
@ -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">
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
109
bds/views.py
109
bds/views.py
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue