force CGU/EULA approval on account creation

This commit is contained in:
imperosol
2026-09-18 11:27:46 +02:00
parent a264a9e26a
commit 2f95db211a
8 changed files with 75 additions and 55 deletions
@@ -0,0 +1,15 @@
# Generated by Django 5.2.17 on 2026-09-15 11:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0050_alter_sithfile_moderator")]
operations = [
migrations.AddField(
model_name="user",
name="cgu_approved",
field=models.BooleanField(default=False, verbose_name="EULA approved"),
),
]
+1
View File
@@ -291,6 +291,7 @@ class User(AbstractUser):
), ),
blank=True, blank=True,
) )
cgu_approved = models.BooleanField(_("EULA approved"), default=False)
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True) godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
objects = CustomUserManager() objects = CustomUserManager()
+6 -44
View File
@@ -49,67 +49,29 @@ body {
max-width: 500px; max-width: 500px;
margin-top: 20px; margin-top: 20px;
>p, input[type="submit"] {
>div {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%; width: 100%;
margin: 0;
>label {
width: 100%;
@media (min-width: 500px) {
width: 300px;
}
}
}
>input,
>p>input,
>div>input {
box-sizing: border-box;
width: 100%;
max-width: 500px;
@media (min-width: 500px) {
max-width: 300px; max-width: 300px;
} margin: 0;
} }
>.errorlist { .errorlist {
color: red; color: red;
text-align: center; text-align: center;
margin: 10px 0 0 0; margin: 10px 0 0 0;
list-style-type: none; list-style-type: none;
} }
>.required>.helptext { div, fieldset {
text-align: center;
font-style: italic;
}
>.required:last-of-type {
box-sizing: border-box;
max-width: 300px; max-width: 300px;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
>label {
width: 100%;
} }
.captcha {
box-sizing: border-box;
>img { >img {
width: 70px; width: 70px;
object-fit: contain; object-fit: contain;
} }
>input {
width: 200px;
}
} }
} }
} }
+4 -5
View File
@@ -50,13 +50,12 @@
<input type="hidden" name="next" value="{{ next }}"> <input type="hidden" name="next" value="{{ next }}">
<input type="submit" value="{% trans %}Login{% endtrans %}"> <input type="submit" value="{% trans %}Login{% endtrans %}">
<div>
{# Assumes you setup the password_reset view in your URLconf #}
<p>
<a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a> <a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a>
&nbsp;&nbsp; </div>
<div>
<a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a> <a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a>
</p> </div>
</form> </form>
{% endblock %} {% endblock %}
+12 -2
View File
@@ -18,7 +18,17 @@
<form action="{{ url('core:register') }}" method="post"> <form action="{{ url('core:register') }}" method="post">
{% csrf_token %} {% csrf_token %}
{% render_honeypot_field %} {% render_honeypot_field %}
{{ form.as_p() }} {% for field in form %}
<input type="submit" value="{% trans %}Register{% endtrans %}" /> {% if field.name not in ["cgu_approved", "captcha"] %}
<div>{{ field.as_field_group() }}</div>
{% endif %}
{% endfor %}
<div class="captcha">{{ form.captcha.as_field_group() }}</div>
<fieldset>
{{ form.cgu_approved.errors }}
{{ form.cgu_approved }}
{{ form.cgu_approved.label_tag() }}
</fieldset>
<input type="submit" class="btn btn-blue" value="{% trans %}Register{% endtrans %}" />
</form> </form>
{% endblock %} {% endblock %}
+2
View File
@@ -55,6 +55,7 @@ class TestUserRegistration:
"password2": "plop", "password2": "plop",
"captcha_0": "dummy-value", "captcha_0": "dummy-value",
"captcha_1": "PASSED", "captcha_1": "PASSED",
"cgu_approved": True,
} }
@pytest.fixture() @pytest.fixture()
@@ -92,6 +93,7 @@ class TestUserRegistration:
({"first_name": ""}, "Ce champ est obligatoire."), ({"first_name": ""}, "Ce champ est obligatoire."),
({"last_name": ""}, "Ce champ est obligatoire."), ({"last_name": ""}, "Ce champ est obligatoire."),
({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"), ({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"),
({"cgu_approved": ""}, "Ce champ est obligatoire."),
], ],
) )
def test_register_user_form_fail( def test_register_user_form_fail(
+19 -1
View File
@@ -42,6 +42,8 @@ from django.forms import (
TextInput, TextInput,
Widget, Widget,
) )
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import RegionalPhoneNumberWidget from phonenumber_field.widgets import RegionalPhoneNumberWidget
@@ -146,9 +148,25 @@ class RegisteringForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ("first_name", "last_name", "email") fields = ("first_name", "last_name", "email", "cgu_approved")
field_classes = {"email": AntiSpamEmailField} field_classes = {"email": AntiSpamEmailField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["cgu_approved"].required = True
self.fields["cgu_approved"].label_suffix = ""
self.fields["cgu_approved"].label = mark_safe(
_(
"I have read and I approve the "
'<a href="%(url)s" target="_blank">End User License Agreement</a>'
)
% {
"url": reverse(
"core:download", kwargs={"file_id": settings.SITH_CGU_FILE_ID}
)
}
)
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):
"""Form handling the user profile, managing the files""" """Form handling the user profile, managing the files"""
+15 -2
View File
@@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-09 07:21+0200\n" "POT-Creation-Date: 2026-09-15 23:49+0200\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -1835,6 +1835,10 @@ msgstr ""
"Même si ce profil est caché, les utilisateurs sur cette liste pourront " "Même si ce profil est caché, les utilisateurs sur cette liste pourront "
"toujours le voir." "toujours le voir."
#: core/models.py
msgid "EULA approved"
msgstr "CGU approuvées"
#: core/models.py #: core/models.py
msgid "A user with that username already exists" msgid "A user with that username already exists"
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
@@ -3137,6 +3141,15 @@ msgstr "Assurez-vous que cet horodatage est dans le futur"
msgid "Username, email, or account number" msgid "Username, email, or account number"
msgstr "Nom d'utilisateur, email, ou numéro de compte AE" msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
#: core/views/forms.py
#, python-format
msgid ""
"I have read and I approve the <a href=\"%(url)s\" target=\"_blank\">End User "
"License Agreement</a>"
msgstr ""
"J'ai lu et j'approuve les <a href=\"%(url)s\" target=\"_blank\">Conditions "
"Générales d'utilisation</a>"
#: core/views/forms.py #: core/views/forms.py
msgid "" msgid ""
"Profile: you need to be visible on the picture, in order to be recognized " "Profile: you need to be visible on the picture, in order to be recognized "
@@ -3778,8 +3791,8 @@ msgid "Emptied"
msgstr "Coffre vidé" msgstr "Coffre vidé"
#: counter/templates/counter/cash_summary_list.jinja counter/views/cash.py #: counter/templates/counter/cash_summary_list.jinja counter/views/cash.py
#: pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja
#: pedagogy/templates/pedagogy/moderation.jinja #: pedagogy/templates/pedagogy/moderation.jinja
#: pedagogy/templates/pedagogy/ue_detail.jinja
#: trombi/templates/trombi/comment.jinja #: trombi/templates/trombi/comment.jinja
#: trombi/templates/trombi/user_tools.jinja #: trombi/templates/trombi/user_tools.jinja
msgid "Comment" msgid "Comment"