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)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# FIXME: une association ne devrait pas pouvoir perdre son dernier
|
|
||||||
# dirigeant
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
if self.association_id:
|
if self.association_id:
|
||||||
|
@ -510,9 +507,39 @@ class Engagement(HTMLDocString, AbstractAssociated):
|
||||||
"Modifiez le rôle existant plutôt que d'en ajouter un."
|
"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:
|
if errors:
|
||||||
raise ValidationError(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):
|
def __str__(self):
|
||||||
return "{} est {}".format(self.user, self.role)
|
return "{} est {}".format(self.user, self.role)
|
||||||
|
|
|
@ -36,14 +36,25 @@ class TestAssociationManagementButtons:
|
||||||
'role_name, expected',
|
'role_name, expected',
|
||||||
[('Bénévole', 0), ('Animat⋅eur⋅rice', 5), ('Dirigeant', 7)],
|
[('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 = association.get_engagement(user)
|
||||||
engagement.role = association.role_set.get(name=role_name)
|
engagement.role = association.role_set.get(name=role_name)
|
||||||
engagement.save()
|
engagement.save()
|
||||||
|
|
||||||
assert len(get_management_buttons(association, user)) == expected
|
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 = association.role_set.get_benevole()
|
||||||
role.list_users = True
|
role.list_users = True
|
||||||
role.save()
|
role.save()
|
||||||
|
@ -610,7 +621,7 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
assert len(table_row) == 1
|
assert len(table_row) == 1
|
||||||
assert "Dirigeant" in str(table_row[-1])
|
assert "Dirigeant" in str(table_row[-1])
|
||||||
|
|
||||||
# 2. on modifie notre rôle
|
# 2. on échoue à modifier notre rôle
|
||||||
url = (
|
url = (
|
||||||
bs(response.content, 'html.parser')
|
bs(response.content, 'html.parser')
|
||||||
.find('tbody')
|
.find('tbody')
|
||||||
|
@ -621,22 +632,38 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
response = client.post(url, data, follow=True)
|
response = client.post(url, data, follow=True)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
messages = [m.message for m in get_messages(response.wsgi_request)]
|
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 len(messages) == 1
|
||||||
assert "a été modifié" in messages[0]
|
assert "a été modifié" in messages[0]
|
||||||
|
|
||||||
table_row = (
|
table_row = (
|
||||||
bs(response.content, 'html.parser').find('tbody').find_all('tr')
|
bs(response.content, 'html.parser').find('tbody').find_all('tr')
|
||||||
)
|
)
|
||||||
assert len(table_row) == 1
|
assert len(table_row) == 2
|
||||||
assert "Animat⋅eur⋅rice" in str(table_row[-1])
|
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 = (
|
url = (
|
||||||
bs(response.content, 'html.parser')
|
bs(response.content, 'html.parser')
|
||||||
.find('tbody')
|
.find('tbody')
|
||||||
.find_all('a', class_='update-link')[-1]
|
.find_all('a', class_='update-link')[-2]
|
||||||
.attrs['href']
|
.attrs['href']
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {'role': dirigeant_role.id}
|
data = {'role': dirigeant_role.id}
|
||||||
response = client.post(url, data, follow=True)
|
response = client.post(url, data, follow=True)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -653,10 +680,7 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
str(animateur_role.id),
|
str(animateur_role.id),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 4 on tente de modifier un dirigeant mais on n'est pas en capacité
|
# 5. on tente de modifier un dirigeant mais on n'est pas en capacité
|
||||||
engagement = self.association.engagement_set.create(
|
|
||||||
user=foreign, role=dirigeant_role
|
|
||||||
)
|
|
||||||
url2 = reverse(
|
url2 = reverse(
|
||||||
'association:engagement:update',
|
'association:engagement:update',
|
||||||
args=[self.association.id, engagement.id],
|
args=[self.association.id, engagement.id],
|
||||||
|
@ -672,7 +696,7 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
str(dirigeant_role.id),
|
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.logout()
|
||||||
client.force_login(foreign)
|
client.force_login(foreign)
|
||||||
data = {'role': dirigeant_role.id}
|
data = {'role': dirigeant_role.id}
|
||||||
|
@ -682,7 +706,7 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
assert user.get_short_name() in mailoutbox[0].body
|
assert user.get_short_name() in mailoutbox[0].body
|
||||||
assert "Mise à jour de votre engagement" in mailoutbox[0].subject
|
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)
|
client.force_login(user)
|
||||||
|
|
||||||
assert self.association.can_manage_engagements(user)
|
assert self.association.can_manage_engagements(user)
|
||||||
|
@ -691,7 +715,38 @@ class TestEngagement(ManagerOnlyViewMixin):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
reverse(
|
reverse(
|
||||||
'association:engagement:delete',
|
'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,
|
data,
|
||||||
follow=True,
|
follow=True,
|
||||||
|
|
|
@ -631,4 +631,16 @@ class EngagementUpdate(
|
||||||
|
|
||||||
|
|
||||||
class EngagementDelete(EngagementViewMixin, CruditorDeleteView):
|
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" %}
|
{% extends "cruditor/delete.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block form_actions_right %}
|
{% block form_actions_right %}
|
||||||
<button class="btn btn-outline-danger" type="submit" name="save">
|
<button class="btn btn-outline-danger" type="submit" name="save">
|
||||||
{{ form_save_button_label|default:"Confirmer la suppression" }}
|
{{ form_save_button_label|default:"Confirmer la suppression" }}
|
||||||
</button>
|
</button>
|
||||||
{% endblock %}
|
{% 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 os
|
||||||
import sys
|
import sys
|
||||||
import django
|
import django
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
sys.path.insert(0, os.path.abspath('../..'))
|
||||||
from benevalibre.settings import DJANGO_SETTINGS_MODULE
|
from benevalibre.settings import DJANGO_SETTINGS_MODULE
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', DJANGO_SETTINGS_MODULE)
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', DJANGO_SETTINGS_MODULE)
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
|
|
Chargement…
Référencer dans un nouveau ticket