fix(engagement): empêche de se retirer d une asso si ça créé une association orpheline

master
François Poulain 2019-12-14 14:24:01 +01:00 commité par François Poulain
Parent b9897febe0
révision b1b6ba17cd
5 fichiers modifiés avec 127 ajouts et 21 suppressions

Voir le fichier

@ -480,9 +480,6 @@ class Engagement(HTMLDocString, AbstractAssociated):
super().save(*args, **kwargs)
def clean(self):
# FIXME: une association ne devrait pas pouvoir perdre son dernier
# dirigeant
errors = []
if self.association_id:
@ -510,9 +507,39 @@ class Engagement(HTMLDocString, AbstractAssociated):
"Modifiez le rôle existant plutôt que d'en ajouter un."
)
)
if (
self.id
and self.role
and not self.role.manage_association
and not Engagement.objects.filter(
~models.Q(id=self.id),
association_id=self.association_id,
role__manage_association=True,
).exists()
):
errors.append(
ValidationError(
"Modifier cet engagement rendrait impossible la "
"gestion de l'association. Avant ça, vous devez "
"donner le rôle de dirigeant à une autre personne.",
)
)
if errors:
raise ValidationError(errors)
def delete(self):
if not Engagement.objects.filter(
~models.Q(id=self.id),
association_id=self.association_id,
role__manage_association=True,
).exists():
raise ValidationError(
"Supprimer cet engagement rendrait impossible la "
"gestion de l'association. Avant ça, vous devez "
"donner le rôle de dirigeant à une autre personne.",
)
return super().delete()
def __str__(self):
return "{} est {}".format(self.user, self.role)

Voir le fichier

@ -36,14 +36,25 @@ class TestAssociationManagementButtons:
'role_name, expected',
[('Bénévole', 0), ('Animat⋅eur⋅rice', 5), ('Dirigeant', 7)],
)
def test_role(self, association, user, engagement, role_name, expected):
def test_role(
self, association, user, foreign, engagement, role_name, expected
):
# upgrade foreign pour avoir au moins toujours un dirigeant
role = association.role_set.get_dirigeant()
association.engagement_set.create(user=foreign, role=role)
# bidouille l'engagement
engagement = association.get_engagement(user)
engagement.role = association.role_set.get(name=role_name)
engagement.save()
assert len(get_management_buttons(association, user)) == expected
def test_role_list_users(self, association, user, engagement):
def test_role_list_users(self, association, user, foreign, engagement):
# upgrade foreign pour avoir au moins toujours un dirigeant
role = association.role_set.get_dirigeant()
association.engagement_set.create(user=foreign, role=role)
role = association.role_set.get_benevole()
role.list_users = True
role.save()
@ -610,7 +621,7 @@ class TestEngagement(ManagerOnlyViewMixin):
assert len(table_row) == 1
assert "Dirigeant" in str(table_row[-1])
# 2. on modifie notre rôle
# 2. on échoue à modifier notre rôle
url = (
bs(response.content, 'html.parser')
.find('tbody')
@ -621,22 +632,38 @@ class TestEngagement(ManagerOnlyViewMixin):
response = client.post(url, data, follow=True)
assert response.status_code == 200
messages = [m.message for m in get_messages(response.wsgi_request)]
assert len(messages) == 0
assert count_text_in_content(response, "rendrait impossible la gesti")
# 3. on modifie notre rôle
# 3.1 on créé un nouveau dirigeant
engagement = self.association.engagement_set.create(
user=foreign, role=dirigeant_role
)
# 3.2 on arrive à modifier notre rôle
data = {'role': animateur_role.id}
response = client.post(url, data, follow=True)
assert response.status_code == 200
messages = [m.message for m in get_messages(response.wsgi_request)]
assert len(messages) == 1
assert "a été modifié" in messages[0]
table_row = (
bs(response.content, 'html.parser').find('tbody').find_all('tr')
)
assert len(table_row) == 1
assert "Animat⋅eur⋅rice" in str(table_row[-1])
assert len(table_row) == 2
assert "Animat⋅eur⋅rice" in str(table_row[-2])
# 3. on tente de redevenir dirigeant mais on n'est plus en capacité
# 4. on tente de redevenir dirigeant mais on n'est plus en capacité
url = (
bs(response.content, 'html.parser')
.find('tbody')
.find_all('a', class_='update-link')[-1]
.find_all('a', class_='update-link')[-2]
.attrs['href']
)
data = {'role': dirigeant_role.id}
response = client.post(url, data, follow=True)
assert response.status_code == 200
@ -653,10 +680,7 @@ class TestEngagement(ManagerOnlyViewMixin):
str(animateur_role.id),
]
# 4 on tente de modifier un dirigeant mais on n'est pas en capacité
engagement = self.association.engagement_set.create(
user=foreign, role=dirigeant_role
)
# 5. on tente de modifier un dirigeant mais on n'est pas en capacité
url2 = reverse(
'association:engagement:update',
args=[self.association.id, engagement.id],
@ -672,7 +696,7 @@ class TestEngagement(ManagerOnlyViewMixin):
str(dirigeant_role.id),
]
# 5 un dirigeant nous modifie et on reçoit une notification
# 6. un dirigeant nous modifie et on reçoit une notification
client.logout()
client.force_login(foreign)
data = {'role': dirigeant_role.id}
@ -682,7 +706,7 @@ class TestEngagement(ManagerOnlyViewMixin):
assert user.get_short_name() in mailoutbox[0].body
assert "Mise à jour de votre engagement" in mailoutbox[0].subject
def test_self_erase_go_back_to_home(self, client, user, manager):
def test_self_erase_do_not_create_orpheans(self, client, user, manager):
client.force_login(user)
assert self.association.can_manage_engagements(user)
@ -691,7 +715,38 @@ class TestEngagement(ManagerOnlyViewMixin):
response = client.post(
reverse(
'association:engagement:delete',
args=[self.association.id, manager.id],
args=[self.association.id, user.engagement_set.first().id],
),
data,
follow=True,
)
assert response.status_code == 200
assert (
response.resolver_match.view_name
== 'association:engagement:delete'
)
assert count_text_in_content(response, "Impossible de supprimer")
assert count_text_in_content(
response, "rendrait impossible la gestion"
)
def test_self_erase_go_back_to_home(self, client, foreign, user, manager):
# we need to define the next leader before leaving
r = self.association.role_set.get_dirigeant()
self.association.engagement_set.create(user=foreign, role=r)
assert self.association.can_manage_engagements(user)
assert self.association.can_manage_engagements(foreign)
client.force_login(user)
data = {'confirm': True}
response = client.post(
reverse(
'association:engagement:delete',
args=[self.association.id, user.engagement_set.first().id],
),
data,
follow=True,

Voir le fichier

@ -631,4 +631,16 @@ class EngagementUpdate(
class EngagementDelete(EngagementViewMixin, CruditorDeleteView):
pass
def delete(self, request, *args, **kwargs):
"""
Call ``perform_delete`` method and redirect to the success URL with a
nice success message. If there are protected related objects, an error
message is shown instead with the output of ``format_linked_objects``.
"""
self.object = self.get_object()
try:
return super().delete(request, *args, **kwargs)
except models.ValidationError as e:
return self.render_to_response(
self.get_context_data(error_message=e.message)
)

Voir le fichier

@ -1,7 +1,17 @@
{% extends "cruditor/delete.html" %}
{% load i18n %}
{% block form_actions_right %}
<button class="btn btn-outline-danger" type="submit" name="save">
{{ form_save_button_label|default:"Confirmer la suppression" }}
</button>
{% endblock %}
{% block content %}
{% if error_message %}
<div class="alert alert-danger">{% trans "Unable to delete this item." %}</div>
<p>{{ error_message }}</p>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

Voir le fichier

@ -8,9 +8,11 @@ http://www.sphinx-doc.org/en/master/config
import os
import sys
import django
sys.path.insert(0, os.path.abspath('../..'))
from benevalibre.settings import DJANGO_SETTINGS_MODULE
os.environ.setdefault('DJANGO_SETTINGS_MODULE', DJANGO_SETTINGS_MODULE)
from benevalibre.settings import DJANGO_SETTINGS_MODULE
os.environ.setdefault('DJANGO_SETTINGS_MODULE', DJANGO_SETTINGS_MODULE)
django.setup()