Skip to content

Commit d1909fc

Browse files
Merge pull request #2013 from betagouv/feat/add-info-from-invites
Ajout information formulaire invitation
2 parents a01c63b + e5344c5 commit d1909fc

4 files changed

Lines changed: 141 additions & 59 deletions

File tree

recoco/apps/invites/forms.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
"""
99

1010
from django import forms
11+
from django.contrib.auth import models as auth_models
12+
from django.contrib.auth.forms import SetPasswordMixin
13+
from phonenumber_field.formfields import PhoneNumberField
14+
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
15+
16+
from recoco.apps.addressbook import models as addressbook_models
1117

1218
from . import models
1319

@@ -20,10 +26,49 @@ class Meta:
2026
fields = ["email", "message"]
2127

2228

23-
class InviteAcceptForm(forms.Form):
29+
class InviteAcceptForm(SetPasswordMixin, forms.Form):
2430
"""Complementary informations when accepting an invitation"""
2531

2632
first_name = forms.CharField(required=True)
2733
last_name = forms.CharField(required=True)
2834
organization = forms.CharField(required=True)
2935
position = forms.CharField(required=True)
36+
phone_no = PhoneNumberField(
37+
required=True,
38+
label="Numéro de téléphone",
39+
widget=PhoneNumberInternationalFallbackWidget(),
40+
)
41+
password, password_confirm = SetPasswordMixin.create_password_fields()
42+
43+
def clean(self):
44+
self.validate_passwords("password", "password_confirm")
45+
return super().clean()
46+
47+
def __init__(self, *args, **kwargs):
48+
self.invite = kwargs.pop("invite")
49+
self.site = kwargs.pop("site")
50+
super().__init__(*args, **kwargs)
51+
52+
def _post_clean(self):
53+
super()._post_clean()
54+
self.user = auth_models.User(
55+
username=self.invite.email,
56+
email=self.invite.email,
57+
first_name=self.cleaned_data.get("first_name"),
58+
last_name=self.cleaned_data.get("last_name"),
59+
)
60+
self.validate_password_for_user(self.user, "password")
61+
62+
def save(self):
63+
self.set_password_and_save(self.user, "password")
64+
65+
org_name = self.cleaned_data.get("organization")
66+
organization = addressbook_models.Organization.get_or_create(org_name)
67+
organization.sites.add(self.site)
68+
69+
self.user.profile.organization = organization
70+
self.user.profile.organization_position = self.cleaned_data.get("position")
71+
self.user.profile.phone_no = self.cleaned_data.get("phone_no")
72+
73+
self.user.profile.save()
74+
return self.user

recoco/apps/invites/templates/invites/fragments/invite_form.html

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ <h3>Créez votre compte {{ request.site.name }}</h3>
6363
</div>
6464
<div class="fr-grid-row fr-grid-row--gutters fr-mt-1w">
6565
<div class="fr-col-6">
66-
<div class="fr-input-group">
66+
<div class="fr-input-group {% if form.first_name.errors %}fr-input-group--error{% endif %}">
6767
<label class="fr-label" for="input-first-name">Prénom</label>
6868
<input type="text"
69-
class="fr-input {% if form.first_name.errors %}is-invalid{% endif %}"
69+
class="fr-input"
7070
id="input-first-name"
7171
name="{{ form.first_name.name }}"
7272
placeholder="Camille"
@@ -76,10 +76,10 @@ <h3>Créez votre compte {{ request.site.name }}</h3>
7676
</div>
7777
</div>
7878
<div class="fr-col-6">
79-
<div class="fr-input-group">
79+
<div class="fr-input-group {% if form.last_name.errors %}fr-input-group--error{% endif %}">
8080
<label class="fr-label" for="input-last-name">Nom</label>
8181
<input type="text"
82-
class="fr-input {% if form.last_name.errors %}is-invalid{% endif %}"
82+
class="fr-input"
8383
id="input-last-name"
8484
name="{{ form.last_name.name }}"
8585
placeholder="Dupont"
@@ -91,10 +91,10 @@ <h3>Créez votre compte {{ request.site.name }}</h3>
9191
</div>
9292
<div class="fr-grid-row fr-grid-row--gutters fr-mt-1w">
9393
<div class="fr-col-6">
94-
<div class="fr-input-group">
94+
<div class="fr-input-group {% if form.organization.errors %}fr-input-group--error{% endif %}">
9595
<label class="fr-label" for="input-organization">Organisation</label>
9696
<input type="text"
97-
class="fr-input {% if form.organization.errors %}is-invalid{% endif %}"
97+
class="fr-input"
9898
id="input-organization"
9999
name="{{ form.organization.name }}"
100100
placeholder="DEFR59"
@@ -104,10 +104,10 @@ <h3>Créez votre compte {{ request.site.name }}</h3>
104104
</div>
105105
</div>
106106
<div class="fr-col-6">
107-
<div class="fr-input-group">
107+
<div class="fr-input-group {% if form.position.errors %}fr-input-group--error{% endif %}">
108108
<label class="fr-label" for="input-position">Fonction</label>
109109
<input type="text"
110-
class="fr-input {% if form.position.errors %}is-invalid{% endif %}"
110+
class="fr-input"
111111
id="input-position"
112112
name="{{ form.position.name }}"
113113
placeholder="Chargée de mission"
@@ -117,6 +117,44 @@ <h3>Créez votre compte {{ request.site.name }}</h3>
117117
</div>
118118
</div>
119119
</div>
120+
<div class="fr-input-group fr-mt-1w fr-mb-0 fr-pt-3v {% if form.phone_no.errors %}fr-input-group--error{% endif %}">
121+
<label class="fr-label" for="input-phone-no">Numéro de téléphone</label>
122+
<input type="tel"
123+
class="fr-input"
124+
id="input-phone-no"
125+
name="{{ form.phone_no.name }}"
126+
placeholder="0600000000"
127+
value="{{ form.phone_no.value|default:'' }}"
128+
required>
129+
{% for error in form.phone_no.errors %}<div class="text-danger text-end">{{ error }}</div>{% endfor %}
130+
</div>
131+
<div class="fr-grid-row fr-grid-row--gutters fr-mt-1w fr-grid-row--bottom">
132+
<div class="fr-col-6">
133+
<div class="fr-input-group {% if form.password.errors or form.password_confirm.errors %}fr-input-group--error{% endif %}">
134+
<label class="fr-label" for="input-password">
135+
Mot de passe
136+
<span class="fr-hint-text">{{ form.password.help_text }}</span>
137+
</label>
138+
<input type="password"
139+
class="fr-input"
140+
id="input-password"
141+
name="{{ form.password.name }}"
142+
required>
143+
</div>
144+
</div>
145+
<div class="fr-col-6">
146+
<div class="fr-input-group {% if form.password_confirm.errors %}fr-input-group--error{% endif %}">
147+
<label class="fr-label" for="input-password-confirm">Confirmation du mot de passe</label>
148+
<input type="password"
149+
class="fr-input"
150+
id="input-password-confirm"
151+
name="{{ form.password_confirm.name }}"
152+
required>
153+
</div>
154+
</div>
155+
{% for error in form.password.errors %}<div class="text-danger text-end fr-ml-2w">{{ error }}</div>{% endfor %}
156+
{% for error in form.password_confirm.errors %}<div class="text-danger text-end fr-ml-2w">{{ error }}</div>{% endfor %}
157+
</div>
120158
{% endif %}
121159
<ul class="fr-btns-group fr-btns-group--inline-sm fr-mt-4w">
122160
<li class="w-50">

recoco/apps/invites/tests.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,9 @@ def test_anonymous_accepts_invite_as_switchtender(request, client, project):
586586
"last_name": "Last",
587587
"organization": "Some Organization",
588588
"position": "Doing Stuff",
589+
"phone_no": "0102030405",
590+
"password": "Recoco2000",
591+
"password_confirm": "Recoco2000",
589592
}
590593

591594
url = reverse("invites-invite-accept", args=[invite.pk])
@@ -624,6 +627,9 @@ def test_anonymous_accepts_invite_as_collaborator(request, client, project):
624627
"last_name": "Last",
625628
"organization": "Some Organization",
626629
"position": "Doing Stuff",
630+
"phone_no": "0102030405",
631+
"password": "Recoco2000",
632+
"password_confirm": "Recoco2000",
627633
}
628634

629635
url = reverse("invites-invite-accept", args=[invite.pk])
@@ -664,6 +670,9 @@ def test_accepting_invitation_assigns_organization_to_current_site(
664670
"last_name": "Last",
665671
"organization": "New Organization",
666672
"position": "Doing Stuff",
673+
"phone_no": "0102030405",
674+
"password": "Recoco2000",
675+
"password_confirm": "Recoco2000",
667676
}
668677

669678
url = reverse("invites-invite-accept", args=[invite.pk])
@@ -694,6 +703,9 @@ def test_accepting_invitation_updates_organization_with_current_site(
694703
"last_name": "Last",
695704
"organization": "New Organization",
696705
"position": "Doing Stuff",
706+
"phone_no": "0102030405",
707+
"password": "Recoco2000",
708+
"password_confirm": "Recoco2000",
697709
}
698710

699711
baker.make(addressbook_models.Organization, name=data["organization"])

recoco/apps/invites/views.py

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from django.urls import reverse
66
from django.utils import timezone
77

8-
from recoco.apps.addressbook import models as addressbook_models
98
from recoco.apps.home.models import SiteConfiguration
109
from recoco.apps.projects import signals as projects_signals
1110
from recoco.apps.projects.utils import (
@@ -54,7 +53,7 @@ def invite_accept(request, invite_id):
5453
pass
5554

5655
if request.method == "POST":
57-
form = forms.InviteAcceptForm(request.POST)
56+
form = forms.InviteAcceptForm(request.POST, invite=invite, site=current_site)
5857
user = None
5958

6059
if existing_account:
@@ -63,61 +62,49 @@ def invite_accept(request, invite_id):
6362
user = request.user
6463

6564
# New account
66-
else:
65+
elif request.user.is_authenticated:
6766
# we shouldn't be logged in at this point
68-
if request.user.is_authenticated:
69-
return HttpResponseForbidden()
67+
return HttpResponseForbidden()
7068

71-
if form.is_valid():
72-
user = auth_models.User.objects.create(
73-
username=invite.email,
74-
email=invite.email,
75-
first_name=form.cleaned_data.get("first_name"),
76-
last_name=form.cleaned_data.get("last_name"),
77-
)
69+
elif form.is_valid():
70+
user = form.save()
71+
login(request, user, backend="django.contrib.auth.backends.ModelBackend")
7872

79-
org_name = form.cleaned_data.get("organization")
80-
organization = addressbook_models.Organization.get_or_create(org_name)
81-
organization.sites.add(request.site)
73+
else:
74+
# Form invalid for new account: re-render with errors
75+
return render(request, "invites/invite_details.html", locals())
8276

83-
user.profile.organization = organization
84-
user.profile.organization_position = form.cleaned_data.get("position")
77+
if not user:
78+
return redirect(reverse("invites-invite-details", args=(invite.pk,)))
8579

86-
user.profile.save()
80+
# user now has access to site
81+
user.profile.sites.add(current_site)
8782

88-
login(
89-
request, user, backend="django.contrib.auth.backends.ModelBackend"
83+
# Now, grant the user her new rights
84+
if invite.role == "SWITCHTENDER":
85+
if assign_advisor(user, project, current_site):
86+
projects_signals.project_switchtender_joined.send(
87+
sender=request.user, project=project
88+
)
89+
elif invite.role == "OBSERVER":
90+
if assign_observer(user, project, current_site):
91+
projects_signals.project_observer_joined.send(
92+
sender=request.user, project=project
9093
)
94+
else:
95+
if assign_collaborator(user, project):
96+
projects_signals.project_member_joined.send(
97+
sender=request.user, project=project
98+
)
99+
elif project.owner == user:
100+
projects_signals.project_owner_joined.send(
101+
sender=request.user, project=project
102+
)
103+
104+
invite.accepted_on = timezone.now()
105+
invite.save()
91106

92-
if user:
93-
# user now has access to site
94-
user.profile.sites.add(current_site)
95-
96-
# Now, grant the user her new rights
97-
if invite.role == "SWITCHTENDER":
98-
if assign_advisor(user, project, current_site):
99-
projects_signals.project_switchtender_joined.send(
100-
sender=request.user, project=project
101-
)
102-
elif invite.role == "OBSERVER":
103-
if assign_observer(user, project, current_site):
104-
projects_signals.project_observer_joined.send(
105-
sender=request.user, project=project
106-
)
107-
else:
108-
if assign_collaborator(user, project):
109-
projects_signals.project_member_joined.send(
110-
sender=request.user, project=project
111-
)
112-
elif project.owner == user:
113-
projects_signals.project_owner_joined.send(
114-
sender=request.user, project=project
115-
)
116-
117-
invite.accepted_on = timezone.now()
118-
invite.save()
119-
120-
return redirect(project.get_absolute_url())
107+
return redirect(project.get_absolute_url())
121108

122109
return redirect(reverse("invites-invite-details", args=(invite.pk,)))
123110

@@ -179,6 +166,6 @@ def invite_details(request, invite_id):
179166
+ reverse("invites-invite-details", args=(invite.pk,))
180167
)
181168

182-
form = forms.InviteAcceptForm(request.GET or None)
169+
form = forms.InviteAcceptForm(request.GET or None, invite=invite, site=request.site)
183170

184171
return render(request, "invites/invite_details.html", locals())

0 commit comments

Comments
 (0)