Skip to content

Commit 47c92a1

Browse files
feat: Add cancel volunteering option (#206)
* feat: Add cancel volunteering option * test: Add tests for cancel volunteering feature * fix: lint issues * Cleanup and adjust based on latest changes in the portal. Use markdown template Still show cancelled volunteers in the profile list Adjust tests --------- Co-authored-by: mariatta <mariatta.wijaya@gmail.com>
1 parent 94d6df7 commit 47c92a1

File tree

9 files changed

+561
-9
lines changed

9 files changed

+561
-9
lines changed

common/mixins.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.contrib.auth.mixins import UserPassesTestMixin
22

3+
from volunteer.models import VolunteerProfile
4+
35

46
class AdminRequiredMixin(UserPassesTestMixin):
57
"""Mixin for views that require administrative permission.
@@ -10,3 +12,20 @@ class AdminRequiredMixin(UserPassesTestMixin):
1012

1113
def test_func(self):
1214
return self.request.user.is_superuser or self.request.user.is_staff
15+
16+
17+
class VolunteerOrAdminRequiredMixin(UserPassesTestMixin):
18+
"""Mixin for views that require the user to be the owner of the object or an admin.
19+
20+
This is useful for views where users can only access their own data unless they have
21+
administrative privileges.
22+
"""
23+
24+
def test_func(self):
25+
pk = self.kwargs.get("pk")
26+
instance = VolunteerProfile.objects.filter(pk=pk).first()
27+
is_owner = False
28+
if instance and instance.user == self.request.user:
29+
is_owner = True
30+
is_admin = self.request.user.is_superuser or self.request.user.is_staff
31+
return is_owner or is_admin
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{% extends "emails/base_email.md" %}
2+
{% load i18n %}
3+
{% block content %}
4+
5+
Dear Team Lead,
6+
7+
This email is to inform you that a member of your team has been canceled from volunteering.
8+
9+
Please review the following details,
10+
and take the necessary steps to offboard the volunteer from your team and revoke their access to any resources they may have had.
11+
12+
## Volunteer Information
13+
14+
- **Name**: {{ profile.user.first_name }} {{ profile.user.last_name }}
15+
- **Username**: {{ profile.user.username }}
16+
- **Email**: {{ profile.user.email }}
17+
- **Team**: {{ team.short_name }}
18+
{% if profile.discord_username %}- **Discord**: {{ profile.discord_username }}{% endif %}
19+
{% if profile.discord_username %}- **GitHub**: {{ profile.discord_username }}{% endif %}
20+
21+
22+
## Action Items
23+
24+
As the team lead, you are responsible for completing the following offboarding tasks:
25+
26+
- **Remove the Volunteer role on Discord.** Their Discord username is: **{{ profile.discord_username }}**.
27+
- **Remove access to PyLadiesCon GDrive** (if applicable).
28+
- **Remove access to Jelly** (if applicable).
29+
- **Remove access to the PyLadiesCon Regular Meeting Calendar** (if applicable).
30+
- **Remove access to the PyLadiesCon 1Password account** (if applicable).
31+
- **Remove access to GitHub repositories** (if applicable).
32+
33+
**Please complete the above tasks within 24 hours.**
34+
35+
If you have any questions or need assistance with the offboarding process, please reach out to the admin team.
36+
37+
Best regards.
38+
39+
{% endblock content %}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{% extends "emails/base_email.md" %}
2+
{% load i18n %}
3+
{% block content %}
4+
5+
Dear {{ profile.user.first_name|default:profile.user.username }},
6+
7+
This email confirms that your Volunteer application for PyLadiesCon has been cancelled.
8+
9+
## Changes Made
10+
11+
Your Volunteer Profile status has been set to **"Cancelled"**
12+
13+
{% if teams_removed %}
14+
You have been removed from the following teams:
15+
16+
{% for team in teams_removed %}- {{ team.short_name }} {% endfor %}
17+
18+
{% endif %}
19+
20+
{% if roles_removed %}
21+
You have been removed from the following roles:
22+
23+
{% for role in roles_removed %}- {{ role.short_name }} {% endfor %}
24+
25+
{% endif %}
26+
27+
Team leads have been notified of your departure.
28+
29+
Your access to volunteer resources will be revoked within the next 24 hours.
30+
31+
We understand that circumstances can change, and we appreciate the time you were willing to dedicate to PyLadiesCon.
32+
33+
If you change your mind in the future and would like to volunteer again,
34+
you're welcome to submit a new volunteer application through our portal.
35+
36+
If this was a mistake, or you do not wish to cancel your application, please contact us so that we
37+
can rectify the situation.
38+
39+
Thank you for your interest in PyLadiesCon, and we hope to see you as a participant or volunteer in future events!
40+
41+
Best regards.
42+
43+
{% endblock content %}

templates/volunteer/volunteerprofile_detail.html

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,87 @@ <h5 class="card-title mb-0">
217217
{% if user.is_superuser and user.username != object.user.username %}
218218
<a class="btn btn-secondary"
219219
href="{% url 'volunteer:volunteers_list' %}">Back to Manage Volunteers</a>
220+
{% if object.application_status != "Cancelled" %}
221+
<button type="button"
222+
class="btn btn-danger ms-2"
223+
data-bs-toggle="modal"
224+
data-bs-target="#cancelVolunteeringModal">
225+
Cancel Volunteering
226+
</button>
227+
{% endif %}
220228
{% else %}
221-
<a class="btn btn-primary"
222-
href="{% url 'volunteer:volunteer_profile_edit' object.pk %}">Edit Profile</a>
229+
{% if object.application_status != "Cancelled" %}
230+
<a class="btn btn-primary"
231+
href="{% url 'volunteer:volunteer_profile_edit' object.pk %}">Edit Profile</a>
232+
<button type="button"
233+
class="btn btn-danger ms-2"
234+
data-bs-toggle="modal"
235+
data-bs-target="#cancelVolunteeringModal">
236+
Cancel My Volunteering
237+
</button>
238+
{% endif %}
223239
<a class="btn btn-secondary" href="{% url 'volunteer:index' %}">Back to Volunteer Dashboard</a>
224240
{% endif %}
225241
</div>
242+
<!-- Cancel Volunteering Modal -->
243+
{% if object.application_status != "Cancelled" %}
244+
<div class="modal fade"
245+
id="cancelVolunteeringModal"
246+
tabindex="-1"
247+
aria-labelledby="cancelVolunteeringModalLabel"
248+
aria-hidden="true">
249+
<div class="modal-dialog">
250+
<div class="modal-content">
251+
<div class="modal-header">
252+
<h5 class="modal-title" id="cancelVolunteeringModalLabel">
253+
Cancel Volunteering
254+
</h5>
255+
<button type="button"
256+
class="btn-close"
257+
data-bs-dismiss="modal"
258+
aria-label="Close">
259+
</button>
260+
</div>
261+
<div class="modal-body">
262+
<p>
263+
<strong>Are you sure you want to cancel your volunteer application?</strong>
264+
</p>
265+
<p>
266+
This action will:
267+
</p>
268+
<ul>
269+
<li>
270+
Set your application status to "Cancelled"
271+
</li>
272+
<li>
273+
Remove you from all assigned teams and roles
274+
</li>
275+
<li>
276+
Send a confirmation email to you
277+
</li>
278+
<li>
279+
Notify team leads about your departure
280+
</li>
281+
</ul>
282+
<p class="text-danger">
283+
<strong>This action cannot be undone.</strong> If you change your mind, you'll need to submit a new volunteer application.
284+
</p>
285+
</div>
286+
<div class="modal-footer">
287+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
288+
Keep Volunteering
289+
</button>
290+
<form method="post"
291+
action="{% url 'volunteer:cancel_volunteering' object.pk %}"
292+
class="d-inline">
293+
{% csrf_token %}
294+
<button type="submit" class="btn btn-danger">
295+
Yes, Cancel My Volunteering
296+
</button>
297+
</form>
298+
</div>
299+
</div>
300+
</div>
301+
</div>
302+
{% endif %}
226303
{% endblock content %}

tests/sponsorship/test_views.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
)
1010
from sponsorship.views import SponsorshipProfileFilter, SponsorshipProfileTable
1111
from volunteer.constants import ApplicationStatus
12-
from volunteer.models import LANGUAGES, VolunteerProfile
12+
from volunteer.models import VolunteerProfile
1313

1414

1515
@pytest.mark.django_db
@@ -23,7 +23,6 @@ def test_sponsors_list_view_forbidden_if_not_approved_volunteer(
2323

2424
# create the volunteer profile but is not approved
2525
profile = VolunteerProfile(user=portal_user)
26-
profile.languages_spoken = [LANGUAGES[0]]
2726
profile.save()
2827

2928
response = client.get(reverse("sponsorship:sponsorship_list"))
@@ -40,7 +39,6 @@ def test_sponsors_list_view_is_approved_volunteer(self, client, portal_user):
4039
profile = VolunteerProfile(
4140
user=portal_user, application_status=ApplicationStatus.APPROVED
4241
)
43-
profile.languages_spoken = [LANGUAGES[0]]
4442
profile.save()
4543

4644
client.force_login(portal_user)
@@ -116,7 +114,6 @@ def test_sponsors_table_render_progress_status_for_approved_volunteer(
116114
profile = VolunteerProfile(
117115
user=portal_user, application_status=ApplicationStatus.APPROVED
118116
)
119-
profile.languages_spoken = [LANGUAGES[0]]
120117
profile.save()
121118

122119
client.force_login(portal_user)

0 commit comments

Comments
 (0)