bifurqué depuis cliss21/benevalibre
fix(engagement): empêche de se retirer d une asso si ça créé une association orpheline
Parent
b9897febe0
révision
b1b6ba17cd
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
Chargement…
Référencer dans un nouveau ticket