Merge branch 'Aufinal/account_update_forms' into 'master'

Refactor la vue `account_update`

Closes #232 and #119

See merge request klub-dev-ens/gestioCOF!490
This commit is contained in:
Tom Hubrecht 2021-03-16 23:02:12 +01:00
commit 6adfaba8e9
8 changed files with 158 additions and 151 deletions

View file

@ -284,7 +284,11 @@ class TemporaryAuthTests(TestCase):
self.perm = Permission.objects.get(
content_type__app_label="kfet", codename="is_team"
)
self.user2.user_permissions.add(self.perm)
self.perm2 = Permission.objects.get(
content_type__app_label="kfet", codename="can_force_close"
)
self.user1.user_permissions.add(self.perm)
self.user2.user_permissions.add(self.perm, self.perm2)
def test_context_processor(self):
"""
@ -295,7 +299,7 @@ class TemporaryAuthTests(TestCase):
r = self.client.post("/k-fet/accounts/000/edit", HTTP_KFETPASSWORD="kfet_user2")
self.assertEqual(r.context["user"], self.user1)
self.assertNotIn("kfet.is_team", r.context["perms"])
self.assertNotIn("kfet.can_force_close", r.context["perms"])
def test_auth_not_persistent(self):
"""

View file

@ -85,31 +85,39 @@ class AccountNoTriForm(AccountForm):
exclude = ["trigramme"]
class AccountRestrictForm(AccountForm):
class Meta(AccountForm.Meta):
fields = ["is_frozen"]
class AccountPwdForm(forms.Form):
pwd1 = forms.CharField(
label="Mot de passe K-Fêt",
required=False,
help_text="Le mot de passe doit contenir au moins huit caractères",
widget=forms.PasswordInput,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
min_length=8,
)
pwd2 = forms.CharField(
label="Confirmer le mot de passe", required=False, widget=forms.PasswordInput
label="Confirmer le mot de passe",
required=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
)
def __init__(self, *args, account=None, **kwargs):
super().__init__(*args, **kwargs)
self.account = account
def clean(self):
pwd1 = self.cleaned_data.get("pwd1", "")
pwd2 = self.cleaned_data.get("pwd2", "")
if len(pwd1) < 8:
raise ValidationError("Mot de passe trop court")
if pwd1 != pwd2:
raise ValidationError("Les mots de passes sont différents")
self.add_error("pwd2", "Les mots de passe doivent être identiques !")
super().clean()
def save(self, commit=True):
password = self.cleaned_data["pwd1"]
self.account.change_pwd(password)
if commit:
self.account.save()
return self.account
class CofForm(forms.ModelForm):
def clean_is_cof(self):

View file

@ -41,10 +41,19 @@
}
.frozen-account {
background:#5072e0;
background:#000FBA;
color:#fff;
}
.frozen-account .btn-default {
color: #aaa;
}
.frozen-account .btn-default:hover, .frozen-account .btn-default.focus,
.frozen-account .btn-default:focus {
color: #ed2545;
}
.main .table a:not(.btn) {
color: inherit;

View file

@ -69,6 +69,8 @@ var AccountView = Backbone.View.extend({
attr_data_balance: function () {
if (this.model.id == 0) {
return '';
} else if (this.model.get("is_frozen")) {
return "frozen";
} else if (this.model.get("balance") < 0) {
return 'neg';
} else if (this.model.get("balance") <= 5) {

View file

@ -6,29 +6,29 @@
{% block title %}
{% if account.user == request.user %}
Modification de mes informations
Modification de mes informations
{% else %}
{{ account.trigramme }} - Édition
{{ account.trigramme }} - Édition
{% endif %}
{% endblock %}
{% block header-title %}
{% if account.user == request.user %}
Modification de mes informations
Modification de mes informations
{% else %}
Édition du compte {{ account.trigramme }}
Édition du compte {{ account.trigramme }}
{% endif %}
{% endblock %}
{% block footer %}
{% if not account.is_team %}
{% include "kfet/base_footer.html" %}
{% include "kfet/base_footer.html" %}
{% endif %}
{% endblock %}
{% block main %}
<form action="" method="post" class="form-horizontal">
<form action="" method="post" class="form-horizontal" autocomplete="off">
{% csrf_token %}
{% include 'kfet/form_snippet.html' with form=user_info_form %}
{% include 'kfet/form_snippet.html' with form=account_form %}
@ -36,20 +36,20 @@
{% include 'kfet/form_snippet.html' with form=pwd_form %}
{% include 'kfet/form_snippet.html' with form=negative_form %}
{% if perms.kfet.is_team %}
{% include 'kfet/form_authentication_snippet.html' %}
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
</form>
<script type="text/javascript">
$(document).ready(function() {
$(document).ready(function () {
$('#id_authz_overdraft_until').datetimepicker({
format: 'YYYY-MM-DD HH:mm',
stepping: 5,
locale: 'fr',
format: 'YYYY-MM-DD HH:mm',
stepping: 5,
locale: 'fr',
});
});
});
</script>

View file

@ -11,13 +11,11 @@
</div>
</div>
{% if perms.kfet.is_team %}
<div class="buttons">
<a class="btn btn-default" href="{% url 'kfet.account.update' account.trigramme %}">
<span class="glyphicon glyphicon-cog"></span><span>Éditer</span>
</a>
<a class="btn btn-default" disabled>
<span class="glyphicon glyphicon-credit-card"></span><span>Créditer</span>
</a>
{% if perms.kfet.delete_account %}
<hr>
<button class="btn btn-default" id="button-delete">
@ -28,6 +26,7 @@
</form>
{% endif %}
</div>
{% endif %}
<div class="text">
<h4>{{ account.name|title }}</h4>

View file

@ -11,6 +11,7 @@ from django.utils import timezone
from .. import KFET_DELETED_TRIGRAMME
from ..auth import KFET_GENERIC_TRIGRAMME
from ..auth.models import KFetGroup
from ..auth.utils import hash_password
from ..config import kfet_config
from ..models import (
Account,
@ -296,8 +297,8 @@ class AccountReadViewTests(ViewTestCaseMixin, TestCase):
class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
url_name = "kfet.account.update"
url_kwargs = {"trigramme": "001"}
url_expected = "/k-fet/accounts/001/edit"
url_kwargs = {"trigramme": "100"}
url_expected = "/k-fet/accounts/100/edit"
http_methods = ["GET", "POST"]
@ -317,26 +318,16 @@ class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
"promo": "",
# 'is_frozen': not checked
# Account password
"pwd1": "",
"pwd2": "",
"pwd1": "changed_pwd",
"pwd2": "changed_pwd",
}
def get_users_extra(self):
return {
"user1": create_user("user1", "001"),
"team1": create_team("team1", "101", perms=["kfet.change_account"]),
"team2": create_team("team2", "102"),
}
# Users with forbidden access users should get a 404 here, to avoid leaking trigrams
# See issue #224
def test_forbidden(self):
for method in ["get", "post"]:
for user in self.auth_forbidden:
self.assertRedirectsToLoginOr404(user, method, self.url_expected)
self.assertRedirectsToLoginOr404(
user, method, "/k-fet/accounts/NEX/edit"
)
def assertRedirectsToLoginOr404(self, user, method, url):
client = Client()
meth = getattr(client, method)
@ -356,46 +347,55 @@ class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
def test_get_ok_self(self):
client = Client()
client.login(username="user1", password="user1")
r = client.get(self.url)
self.assertEqual(r.status_code, 200)
def test_post_ok(self):
client = Client()
client.login(username="team1", password="team1")
r = client.post(self.url, self.post_data)
r = client.post(self.url, self.post_data, follow=True)
self.assertRedirects(r, reverse("kfet.account.read", args=["051"]))
self.accounts["user1"].refresh_from_db()
self.users["user1"].refresh_from_db()
# Comportement attendu : compte modifié,
# utilisateur/mdp inchangé, warning pour le mdp
self.accounts["team"].refresh_from_db()
self.users["team"].refresh_from_db()
self.assertInstanceExpected(
self.accounts["user1"],
{"first_name": "first", "last_name": "last", "trigramme": "051"},
self.accounts["team"],
{"first_name": "team", "last_name": "member", "trigramme": "051"},
)
self.assertEqual(self.accounts["team"].password, hash_password("kfetpwd_team"))
self.assertTrue(
any("mot de passe" in str(msg).casefold() for msg in r.context["messages"])
)
def test_post_ok_self(self):
client = Client()
client.login(username="user1", password="user1")
r = self.client.post(self.url, self.post_data, follow=True)
self.assertRedirects(r, reverse("kfet.account.read", args=["051"]))
post_data = {"first_name": "The first", "last_name": "The last"}
self.accounts["team"].refresh_from_db()
self.users["team"].refresh_from_db()
r = client.post(self.url, post_data)
self.assertRedirects(r, reverse("kfet.account.read", args=["001"]))
self.accounts["user1"].refresh_from_db()
self.users["user1"].refresh_from_db()
# Comportement attendu : compte/mdp modifié, utilisateur inchangé
self.assertInstanceExpected(
self.accounts["user1"], {"first_name": "first", "last_name": "last"}
self.accounts["team"],
{"first_name": "team", "last_name": "member", "trigramme": "051"},
)
self.assertEqual(self.accounts["team"].password, hash_password("changed_pwd"))
def test_post_forbidden(self):
r = self.client.post(self.url, self.post_data)
self.assertForbiddenKfet(r)
client = Client()
client.login(username="team2", password="team2")
r = client.post(self.url, self.post_data)
self.assertTrue(
any(
"permission refusée" in str(msg).casefold()
for msg in r.context["messages"]
)
)
class AccountDeleteViewTests(ViewTestCaseMixin, TestCase):

View file

@ -16,7 +16,12 @@ from django.core.exceptions import SuspiciousOperation
from django.db import transaction
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
from django.forms import formset_factory
from django.http import Http404, HttpResponseBadRequest, JsonResponse
from django.http import (
Http404,
HttpResponseBadRequest,
HttpResponseForbidden,
JsonResponse,
)
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
@ -36,7 +41,6 @@ from kfet.forms import (
AccountNegativeForm,
AccountNoTriForm,
AccountPwdForm,
AccountRestrictForm,
AccountStatForm,
AccountTriForm,
AddcostForm,
@ -332,109 +336,89 @@ def account_read(request, trigramme):
# Account - Update
@login_required
@teamkfet_required
@kfet_password_auth
def account_update(request, trigramme):
account = get_object_or_404(Account, trigramme=trigramme)
# Checking permissions
if not account.editable or (
not request.user.has_perm("kfet.is_team") and request.user != account.user
):
raise Http404
if not account.editable:
# Plus de leak de trigramme !
return HttpResponseForbidden
user_info_form = UserInfoForm(instance=account.user)
if request.user.has_perm("kfet.is_team"):
group_form = UserGroupForm(instance=account.user)
account_form = AccountForm(instance=account)
pwd_form = AccountPwdForm()
if account.balance < 0 and not hasattr(account, "negative"):
AccountNegative.objects.create(account=account, start=timezone.now())
account.refresh_from_db()
if hasattr(account, "negative"):
negative_form = AccountNegativeForm(instance=account.negative)
else:
negative_form = None
group_form = UserGroupForm(instance=account.user)
account_form = AccountForm(instance=account)
pwd_form = AccountPwdForm()
if hasattr(account, "negative"):
negative_form = AccountNegativeForm(instance=account.negative)
else:
account_form = AccountRestrictForm(instance=account)
group_form = None
negative_form = None
pwd_form = None
if request.method == "POST":
# Update attempt
success = False
missing_perm = True
self_update = request.user == account.user
account_form = AccountForm(request.POST, instance=account)
group_form = UserGroupForm(request.POST, instance=account.user)
pwd_form = AccountPwdForm(request.POST, account=account)
if request.user.has_perm("kfet.is_team"):
account_form = AccountForm(request.POST, instance=account)
group_form = UserGroupForm(request.POST, instance=account.user)
pwd_form = AccountPwdForm(request.POST)
if hasattr(account, "negative"):
negative_form = AccountNegativeForm(
request.POST, instance=account.negative
)
forms = []
warnings = []
if request.user.has_perm("kfet.change_account") and account_form.is_valid():
missing_perm = False
if self_update or request.user.has_perm("kfet.change_account"):
forms.append(account_form)
elif account_form.has_changed():
warnings.append("compte")
# Updating
account_form.save()
if request.user.has_perm("kfet.manage_perms"):
forms.append(group_form)
elif group_form.has_changed():
warnings.append("statut d'équipe")
# Checking perm to update password
if (
request.user.has_perm("kfet.change_account_password")
and pwd_form.is_valid()
):
pwd = pwd_form.cleaned_data["pwd1"]
account.change_pwd(pwd)
account.save()
messages.success(request, "Mot de passe mis à jour")
if hasattr(account, "negative"):
negative_form = AccountNegativeForm(request.POST, instance=account.negative)
# Checking perm to manage perms
if request.user.has_perm("kfet.manage_perms") and group_form.is_valid():
group_form.save()
if request.user.has_perm("kfet.change_accountnegative"):
forms.append(negative_form)
elif negative_form.has_changed():
warnings.append("négatifs")
if (
hasattr(account, "negative")
and request.user.has_perm("kfet.change_accountnegative")
and negative_form.is_valid()
):
negative_form.save()
# Il ne faut pas valider `pwd_form` si elle est inchangée
if pwd_form.has_changed():
if self_update or request.user.has_perm("kfet.change_account_password"):
forms.append(pwd_form)
else:
warnings.append("mot de passe")
success = True
messages.success(
request,
"Informations du compte %s mises à jour" % account.trigramme,
)
# Modification de ses propres informations
if request.user == account.user:
missing_perm = False
account.refresh_from_db()
account_form = AccountRestrictForm(request.POST, instance=account)
pwd_form = AccountPwdForm(request.POST)
if account_form.is_valid():
account_form.save()
success = True
messages.success(request, "Vos informations ont été mises à jour")
if request.user.has_perm("kfet.is_team") and pwd_form.is_valid():
pwd = pwd_form.cleaned_data["pwd1"]
account.change_pwd(pwd)
account.save()
messages.success(request, "Votre mot de passe a été mis à jour")
if missing_perm:
messages.error(request, "Permission refusée")
if success:
return redirect("kfet.account.read", account.trigramme)
else:
# Updating account info
if forms == []:
messages.error(
request, "Informations non mises à jour. Corrigez les erreurs"
request, "Informations non mises à jour : permission refusée"
)
else:
if all(form.is_valid() for form in forms):
for form in forms:
form.save()
if len(warnings):
messages.warning(
request,
"Permissions insuffisantes pour modifier"
" les informations suivantes : {}.".format(", ".join(warnings)),
)
if self_update:
messages.success(request, "Vos informations ont été mises à jour !")
else:
messages.success(
request,
"Informations du compte %s mises à jour" % account.trigramme,
)
return redirect("kfet.account.read", account.trigramme)
else:
messages.error(
request, "Informations non mises à jour : corrigez les erreurs"
)
return render(
request,
@ -449,7 +433,8 @@ def account_update(request, trigramme):
},
)
# Account - Delete
# Account - Delete
class AccountDelete(PermissionRequiredMixin, DeleteView):