Skip to content

Commit c8fa713

Browse files
authored
User can delete appointment either with view or ajax related view (#49)
* User can delete appointment either with view or ajax related view * Updated documentation
1 parent 20eb5a2 commit c8fa713

File tree

5 files changed

+182
-54
lines changed

5 files changed

+182
-54
lines changed

README.md

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,52 +39,40 @@ notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_note
3939
- For more information, please refer to
4040
this [documentation](https://github.com/adamspd/django-appointment/tree/main/docs/history/readme_v2_1_1.md).
4141

42-
## Added Features in version 2.1.3
42+
## Added Features in version 2.1.5
4343

4444
This release of Django Appointment brings a series of improvements and updates aimed at enhancing the overall
4545
functionality and user experience:
4646

47-
1. **Dynamic Label Customization in Appointment Pages (#19)**:
48-
- Added a new configuration option `app_offered_by_label` to the `Config` model.
49-
- This feature allows for dynamic labeling in the appointment HTML page to showcase the staff members or services
50-
offering the appointment.
51-
- The default value is "Offered by", which can be customized to fit different contexts, such as "Provided by" or "
52-
Choose Photographer" for photography services.
47+
1. **Dynamic Label Customization in Appointment Pages (#19)**
5348

54-
2. **Updated Documentation and Workflow Enhancements (#25, #26, #27)**:
55-
- Improved clarity and consistency in the project's documentation, making it more accessible and user-friendly.
56-
- Updated workflow processes to streamline development and issue tracking.
49+
2. **Updated Documentation and Workflow Enhancements (#25, #26, #27)**
5750

58-
3. **Community Engagement and Standards (#21, #22, #23, #24)**:
59-
- Introduced a `CODE_OF_CONDUCT.md` to foster a respectful and inclusive community environment.
60-
- Created `CONTRIBUTING.md` to guide contributors through the process of making contributions to the project.
61-
- Established a `SECURITY.md` policy to address security protocols and reporting.
62-
- Refined issue templates for bug reports and feature requests, enhancing the efficiency of community contributions
63-
and feedback.
51+
3. **Community Engagement and Standards (#21, #22, #23, #24)**
6452

65-
4. **Library Updates and Security Patches (#14, #15, #18)**:
66-
- Updated dependencies such as `phonenumbers` and `django` to their latest versions, ensuring better performance and
67-
security.
53+
4. **Library Updates and Security Patches (#14, #15, #18)**
6854

69-
5. **Enhanced Project Visibility (#16)**:
70-
- Added GitHub Badges to the README for better visibility and quick access to project metrics like build status,
71-
versioning, and contribution activities.
55+
5. **Enhanced Project Visibility (#16)**
7256

73-
6. **Translation Refinements (#31)**:
74-
- Removed inconsistencies in translations, improving the internationalization aspect of the application.
57+
6. **Translation Refinements (#31)**
7558

76-
These updates collectively contribute to the robustness and versatility of the Django Appointment package, aligning with
77-
our commitment to providing a high-quality and user-friendly appointment management solution.
59+
7. **Bug Fixes (#48)**
7860

79-
### Breaking Changes in version 2.1.2:
61+
See more at the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md).
8062

81-
- None
63+
These updates collectively contribute to the robustness and versatility of the Django Appointment package, aligning with
64+
our commitment to providing a high-quality and user-friendly appointment management solution.
8265

83-
### New Features & Bug Fixes 🆕
66+
### Bug Fixes 🆕
8467

8568
See the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md)
8669
for more information.
8770

71+
### Breaking Changes in version 2.1.5:
72+
73+
- None
74+
75+
8876
## Quick Start 🚀
8977

9078
1. Add "appointment" to your `INSTALLED_APPS` setting like so:

appointment/tests/test_views.py

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ def setUp(self):
3838
middleware = MessageMiddleware(lambda req: None)
3939
middleware.process_request(self.request)
4040
self.request.session.save()
41+
self.appointment = self.create_appointment_for_user1()
42+
43+
def need_staff_login(self):
44+
self.user1.is_staff = True
45+
self.user1.save()
46+
self.client.force_login(self.user1)
47+
48+
def clean_staff_member_objects(self):
49+
"""Delete all AppointmentRequests and Appointments linked to the StaffMember instance of self.user1."""
50+
AppointmentRequest.objects.filter(staff_member__user=self.user1).delete()
51+
Appointment.objects.filter(appointment_request__staff_member__user=self.user1).delete()
4152

4253
def test_get_available_slots_ajax(self):
4354
"""get_available_slots_ajax view should return a JSON response with available slots for the selected date."""
@@ -142,17 +153,13 @@ def test_default_thank_you(self):
142153

143154
def test_staff_user_without_staff_member_instance(self):
144155
"""Test that a staff user without a staff member instance receives an appropriate error message."""
145-
self.user1.is_staff = True
146-
147-
# Delete any AppointmentRequests and Appointments linked to the StaffMember instance of self.user1
148-
AppointmentRequest.objects.filter(staff_member__user=self.user1).delete()
149-
Appointment.objects.filter(appointment_request__staff_member__user=self.user1).delete()
156+
self.clean_staff_member_objects()
150157

151158
# Now safely delete the StaffMember instance
152159
StaffMember.objects.filter(user=self.user1).delete()
153160

154161
self.user1.save() # Save the user to the database after updating
155-
self.client.force_login(self.user1) # Log in as self.user1
162+
self.need_staff_login()
156163

157164
url = reverse('appointment:get_user_appointments')
158165
response = self.client.get(url)
@@ -161,5 +168,55 @@ def test_staff_user_without_staff_member_instance(self):
161168
self.assertTrue(any(
162169
message.message == "User doesn't have a staff member instance. Please contact the administrator." for
163170
message in message_list),
164-
"Expected error message not found in messages.")
171+
"Expected error message not found in messages.")
172+
173+
def test_delete_appointment(self):
174+
self.need_staff_login()
175+
176+
url = reverse('appointment:delete_appointment', args=[self.appointment.id])
177+
response = self.client.get(url)
178+
179+
self.assertEqual(response.status_code, 302) # Redirect status code
180+
self.assertRedirects(response, reverse('appointment:get_user_appointments'))
181+
182+
# Check for success messages
183+
messages_list = list(get_messages(response.wsgi_request))
184+
self.assertTrue(any(_("Appointment deleted successfully!") in str(message) for message in messages_list))
185+
186+
# Check if appointment is deleted
187+
appointment_exists = Appointment.objects.filter(pk=self.appointment.id).exists()
188+
self.assertFalse(appointment_exists, "Appointment should be deleted but still exists.")
189+
190+
def test_delete_appointment_ajax(self):
191+
self.need_staff_login()
192+
193+
url = reverse('appointment:delete_appointment_ajax')
194+
data = json.dumps({'appointment_id': self.appointment.id})
195+
response = self.client.post(url, data, content_type='application/json')
196+
197+
self.assertEqual(response.status_code, 200)
198+
# Expecting both 'message' and 'success' in the response
199+
expected_response = {"message": "Appointment deleted successfully.", "success": True}
200+
self.assertEqual(json.loads(response.content), expected_response)
201+
202+
# Check if appointment is deleted
203+
appointment_exists = Appointment.objects.filter(pk=self.appointment.id).exists()
204+
self.assertFalse(appointment_exists, "Appointment should be deleted but still exists.")
205+
206+
def test_remove_staff_member(self):
207+
self.need_staff_login()
208+
self.clean_staff_member_objects()
209+
210+
url = reverse('appointment:remove_staff_member', args=[self.staff_member.user_id])
211+
response = self.client.get(url)
212+
213+
self.assertEqual(response.status_code, 302) # Redirect status code
214+
self.assertRedirects(response, reverse('appointment:user_profile'))
215+
216+
# Check for success messages
217+
messages_list = list(get_messages(response.wsgi_request))
218+
self.assertTrue(any(_("Staff member deleted successfully!") in str(message) for message in messages_list))
165219

220+
# Check if staff member is deleted
221+
staff_member_exists = StaffMember.objects.filter(pk=self.staff_member.id).exists()
222+
self.assertFalse(staff_member_exists, "Appointment should be deleted but still exists.")

appointment/urls.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
from appointment.views import appointment_request, get_available_slots_ajax, get_next_available_date_ajax, \
1212
appointment_request_submit, appointment_client_information, default_thank_you, enter_verification_code, \
1313
get_non_working_days_ajax
14-
from appointment.views_admin import get_user_appointments, display_appointment, user_profile, update_working_hours, \
14+
from appointment.views_admin import delete_appointment, delete_appointment_ajax, get_user_appointments, \
15+
display_appointment, user_profile, \
16+
update_working_hours, \
1517
add_working_hours, delete_working_hours, add_day_off, update_day_off, delete_day_off, add_or_update_staff_info, \
1618
fetch_service_list_for_staff, update_appt_min_info, update_appt_date_time, validate_appointment_date, \
1719
email_change_verification_code, update_personal_info, create_new_staff_member, make_superuser_staff_member, \
@@ -70,6 +72,9 @@
7072
path('update-working-hours/<int:working_hours_id>/', update_working_hours, name='update_working_hours'),
7173
path('add-working-hours/', add_working_hours, name='add_working_hours'),
7274
path('delete-working-hours/<int:working_hours_id>/', delete_working_hours, name='delete_working_hours'),
75+
76+
# delete appointment
77+
path('delete-appointment/<int:appointment_id>/', delete_appointment, name='delete_appointment'),
7378
]
7479

7580
ajax_urlpatterns = [
@@ -81,6 +86,8 @@
8186
path('update_appt_min_info/', update_appt_min_info, name="update_appt_min_info"),
8287
path('update_appt_date_time/', update_appt_date_time, name="update_appt_date_time"),
8388
path('validate_appointment_date/', validate_appointment_date, name="validate_appointment_date"),
89+
# delete appointment ajax
90+
path('delete_appointment/', delete_appointment_ajax, name="delete_appointment_ajax"),
8491
]
8592

8693
urlpatterns = [

appointment/views_admin.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,24 @@
1111
import json
1212

1313
from django.contrib import messages
14-
from django.shortcuts import render, get_object_or_404, redirect
14+
from django.shortcuts import get_object_or_404, redirect, render
1515
from django.utils.translation import gettext as _
1616
from django.views.decorators.http import require_POST
1717

18-
from appointment.decorators import require_user_authenticated, require_staff_or_superuser, require_ajax, \
19-
require_superuser
20-
from appointment.forms import StaffAppointmentInformationForm, PersonalInformationForm, ServiceForm
21-
from appointment.models import Appointment, StaffMember, WorkingHours, DayOff
22-
from appointment.services import fetch_user_appointments, prepare_appointment_display_data, prepare_user_profile_data, \
23-
handle_entity_management_request, save_appointment, save_appt_date_time, update_personal_info_service, \
24-
email_change_verification_service, create_staff_member_service, handle_service_management_request
25-
from appointment.utils.db_helpers import get_user_model, get_staff_member_by_user_id, get_day_off_by_id, \
26-
get_working_hours_by_id, Service
18+
from appointment.decorators import require_ajax, require_staff_or_superuser, require_superuser, \
19+
require_user_authenticated
20+
from appointment.forms import PersonalInformationForm, ServiceForm, StaffAppointmentInformationForm
21+
from appointment.models import Appointment, DayOff, StaffMember, WorkingHours
22+
from appointment.services import create_staff_member_service, email_change_verification_service, \
23+
fetch_user_appointments, handle_entity_management_request, handle_service_management_request, \
24+
prepare_appointment_display_data, prepare_user_profile_data, save_appointment, save_appt_date_time, \
25+
update_personal_info_service
26+
from appointment.utils.db_helpers import Service, get_day_off_by_id, get_staff_member_by_user_id, get_user_model, \
27+
get_working_hours_by_id
2728
from appointment.utils.error_codes import ErrorCode
28-
from appointment.utils.json_context import convert_appointment_to_json, json_response, \
29-
get_generic_context_with_extra, get_generic_context, handle_unauthorized_response
30-
from appointment.utils.permissions import check_permissions, check_extensive_permissions
29+
from appointment.utils.json_context import convert_appointment_to_json, get_generic_context, \
30+
get_generic_context_with_extra, handle_unauthorized_response, json_response
31+
from appointment.utils.permissions import check_extensive_permissions, check_permissions
3132

3233

3334
###############################################################
@@ -466,3 +467,22 @@ def get_service_list(request, response_type='html'):
466467
return json_response("Successfully fetched services.", custom_data={'services': service_data}, safe=False)
467468
context = get_generic_context_with_extra(request=request, extra={'services': services})
468469
return render(request, 'administration/service_list.html', context=context)
470+
471+
472+
@require_user_authenticated
473+
@require_staff_or_superuser
474+
def delete_appointment(request, appointment_id):
475+
appointment = get_object_or_404(Appointment, pk=appointment_id)
476+
appointment.delete()
477+
messages.success(request, _("Appointment deleted successfully!"))
478+
return redirect('appointment:get_user_appointments')
479+
480+
481+
@require_user_authenticated
482+
@require_staff_or_superuser
483+
def delete_appointment_ajax(request):
484+
data = json.loads(request.body)
485+
appointment_id = data.get("appointment_id")
486+
appointment = get_object_or_404(Appointment, pk=appointment_id)
487+
appointment.delete()
488+
return json_response("Appointment deleted successfully.")

docs/release_notes/latest.md

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ internationalization, alongside some crucial library updates and new dynamic fea
1414

1515
### Dynamic Label Customization in Appointment Pages (#19)
1616

17-
- Added a new configuration option `app_offered_by_label` in the `Config` model.
18-
- Enables dynamic labeling in the appointment HTML page, showcasing the staff members or services offering the
19-
appointment.
20-
- Default label is "Offered by", customizable to suit different service contexts.
17+
- Added a new configuration option `app_offered_by_label` to the `Config` model.
18+
- This feature allows for dynamic labeling in the appointment HTML page to showcase the staff members or services
19+
offering the appointment.
20+
- The default value is "Offered by", which can be customized to fit different contexts, such as "Provided by" or "
21+
Choose Photographer" for photography services.
2122

2223
### Updated Documentation and Workflow Enhancements (#25, #26, #27)
2324

@@ -43,6 +44,61 @@ internationalization, alongside some crucial library updates and new dynamic fea
4344

4445
- Inconsistencies in translations removed, improving the internationalization aspect.
4546

47+
### Provided an endpoint to delete an appointment (#49)
48+
49+
- Added an endpoint to delete an appointment. Either with an ajax call or a simple request.
50+
51+
## Bug Fixes 🐛
52+
53+
---
54+
55+
- Fixed a bug where a stack trace was displayed when a user that is staff but didn't have a staff member profile tried
56+
to access its appointment's page list (/app-admin/user-event/)
57+
58+
#### Description of the bug
59+
If a staff (Django-related role) is authenticated and tries to retrieved this endpoint :
60+
`/app-admin/user-event/` he'll get the following error if debug = true
61+
```
62+
Traceback (most recent call last):
63+
File ".../django/core/handlers/exception.py", line 55, in inner
64+
response = get_response(request)
65+
^^^^^^^^^^^^^^^^^^^^^
66+
File ".../django/core/handlers/base.py", line 197, in _get_response
67+
response = wrapped_callback(request, *callback_args, **callback_kwargs)
68+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69+
File ".../appointment/decorators.py", line 26, in wrapper
70+
return func(request, *args, **kwargs)
71+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
File ".../appointment/decorators.py", line 39, in wrapper
73+
return func(request, *args, **kwargs)
74+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
File ".../appointment/views_admin.py", line 39, in get_user_appointments
76+
appointments = fetch_user_appointments(request.user)
77+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
78+
File ".../appointment/services.py", line 44, in fetch_user_appointments
79+
staff_member_instance = user.staffmember
80+
^^^^^^^^^^^^^^^^
81+
File ".../django/utils/functional.py", line 268, in inner
82+
return func(_wrapped, *args)
83+
^^^^^^^^^^^^^^^^^^^^^
84+
File ".../django/db/models/fields/related_descriptors.py", line 492, in __get__
85+
raise self.RelatedObjectDoesNotExist(
86+
client.models.UserClient.staffmember.RelatedObjectDoesNotExist: UserClient has no staffmember.
87+
```
88+
If debug = false, the user will get a 500 error
89+
#### To Reproduce
90+
##### Steps to reproduce the behavior:
91+
92+
Create a user/account (user1)
93+
Login as admin/superuser (admin) and add user1 to staff.
94+
Login as user1 and go to /appointment/app-admin/user-event/
95+
See error
96+
97+
#### Expected behavior
98+
Not an error but a redirection or anything more concise than just an error or a 5xx code return.
99+
100+
---
101+
46102
### Breaking Changes 🚨
47103
48104
- None

0 commit comments

Comments
 (0)