Skip to content

Commit 965251a

Browse files
authored
User with no staff admin instance can't create appointment (#67)
* User with no staff admin instance can't create appointment * New version release * Removing console.log and print before release
1 parent a7c8ce6 commit 965251a

File tree

11 files changed

+309
-136
lines changed

11 files changed

+309
-136
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
⚠️ **IMPORTANT**: If upgrading from a version before 2.x.x, please note significant database changes were introduced in
1515
Version 2.0.0 introduces significant database changes. Please read
1616
the [migration guide](https://github.com/adamspd/django-appointment/tree/main/docs/migration_guides/v2_1_0.md) before
17-
updating. No significant database changes were introduced in version 3.0.0.
17+
updating. No database changes were introduced in version 3.0.1.
1818

1919
Django-Appointment is a Django app engineered for managing appointment scheduling with ease and flexibility. It enables
2020
users to define custom configurations for time slots, lead time, and finish time, or utilize the default values
@@ -43,7 +43,7 @@ notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_note
4343
- For more information, please refer to
4444
this [documentation](https://github.com/adamspd/django-appointment/tree/main/docs/history/readme_v2_1_1.md).
4545

46-
## Added Features in version 3.0.0
46+
## Added Features in version 3.0.1
4747

4848
This release of Django Appointment brings a series of improvements and updates aimed at enhancing the overall
4949
functionality and user experience:
@@ -76,7 +76,7 @@ our commitment to providing a high-quality and user-friendly appointment managem
7676
See the [release notes](https://github.com/adamspd/django-appointment/tree/main/docs/release_notes/latest.md)
7777
for more information.
7878

79-
### Breaking Changes in version 3.0.0:
79+
### Breaking Changes in version 3.0.1:
8080

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

appointment/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
__description__ = "Managing appointment scheduling with customizable slots, staff features, and conflict handling."
55
__package_name__ = "django-appointment"
66
__url__ = "https://github.com/adamspd/django-appointment"
7-
__version__ = "3.0.0"
7+
__version__ = "3.0.1"
88
__test_version__ = False

appointment/static/js/app_admin/staff_index.js

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const Constants = {
1010

1111
// Application State
1212
const AppState = {
13-
eventIdSelected: null, calendar: null, isEditingAppointment: false, isCreating: false,
13+
eventIdSelected: null, calendar: null, isEditingAppointment: false, isCreating: false, isUserStaffAdmin: true,
1414
};
1515

1616
document.addEventListener("DOMContentLoaded", initializeCalendar);
@@ -35,7 +35,9 @@ window.addEventListener('resize', function () {
3535

3636
document.addEventListener("DOMContentLoaded", function () {
3737
// Wait for a 50ms after the DOM is ready before initializing the calendar
38-
setTimeout(initializeCalendar, 50);
38+
setUserStaffAdminFlag().then(() => {
39+
setTimeout(initializeCalendar, 50);
40+
});
3941
});
4042

4143

@@ -149,10 +151,12 @@ function getCalendarConfig(events) {
149151

150152
if (dayCell.date >= currentDate && !tabletCheck()) {
151153
// Attach right-click event listener only if the day is not in the past
152-
dayCell.el.addEventListener('contextmenu', function (event) {
153-
event.preventDefault();
154-
handleCalendarRightClick(event, dayCell.date);
155-
});
154+
if (AppState.isUserStaffAdmin) {
155+
dayCell.el.addEventListener('contextmenu', function (event) {
156+
event.preventDefault();
157+
handleCalendarRightClick(event, dayCell.date);
158+
});
159+
}
156160
}
157161
},
158162
};
@@ -223,7 +227,29 @@ function getCalendarHeight() {
223227
return '850px';
224228
}
225229

230+
function setUserStaffAdminFlag() {
231+
return fetch(isUserStaffAdminURL)
232+
.then(response => response.json())
233+
.then(data => {
234+
if (data.is_staff_admin) {
235+
AppState.isUserStaffAdmin = true;
236+
} else {
237+
console.error(data.message || "Error fetching user details.");
238+
AppState.isUserStaffAdmin = false;
239+
}
240+
})
241+
.catch(error => {
242+
console.error("Error checking user's staff admin status: ", error);
243+
AppState.isUserStaffAdmin = false;
244+
});
245+
}
246+
247+
226248
function handleCalendarRightClick(event, date) {
249+
if (!AppState.isUserStaffAdmin) {
250+
showErrorModal(notStaffMemberTxt)
251+
return;
252+
}
227253
const contextMenu = document.getElementById("customContextMenu");
228254
contextMenu.style.top = `${event.pageY}px`;
229255
contextMenu.style.left = `${event.pageX}px`;
@@ -352,6 +378,9 @@ function fetchServices(isEditMode = false) {
352378

353379
async function populateServices(selectedServiceId, isEditMode = false) {
354380
const services = await fetchServices(isEditMode);
381+
if (!services) {
382+
showErrorModal(noServiceOfferedTxt)
383+
}
355384
const selectElement = document.createElement('select');
356385
services.forEach(service => {
357386
const option = document.createElement('option');
@@ -543,7 +572,6 @@ function adjustModalButtonsVisibility(isEditMode, isCreatingMode) {
543572
// ################################################################ //
544573

545574
function toggleEditMode() {
546-
console.log("I was called in toggleEditMode")
547575
const modal = document.getElementById("eventDetailsModal");
548576
const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
549577
AppState.isCreating = false; // Turn off creating mode

appointment/templates/administration/staff_index.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@
312312
const updateApptMinInfoURL = "{% url 'appointment:update_appt_min_info' %}";
313313
const updateApptDateURL = "{% url 'appointment:update_appt_date_time' %}";
314314
const validateApptDateURL = "{% url 'appointment:validate_appointment_date' %}";
315+
const isUserStaffAdminURL = "{% url 'appointment:is_user_staff_admin' %}";
315316
</script>
316317
<script>
317318
{# Text for translation #}
@@ -322,9 +323,11 @@
322323
const noEventTxt = "{% trans 'No events for this day.' %}";
323324
const newEventTxt = "{% trans 'New Event' %}";
324325
const successTxt = "{% trans 'Success' %}";
325-
const errorTxt = "{% trans 'Error' %}"
326-
const updateApptErrorTitleTxt = "{% trans 'Error: Unable to delete appointment.' %}"
327-
const apptNotFoundTxt = "{% trans 'Appointment not found.' %}"
326+
const errorTxt = "{% trans 'Error' %}";
327+
const updateApptErrorTitleTxt = "{% trans 'Error: Unable to delete appointment.' %}";
328+
const apptNotFoundTxt = "{% trans 'Appointment not found.' %}";
329+
const notStaffMemberTxt = "{% trans "You're not a staff member. Can't perform this action !" %}";
330+
const noServiceOfferedTxt = "{% trans "You don't offer any service. Add new service from your profile." %}";
328331
</script>
329332

330333
<script src="{% static 'js/modal/error_modal.js' %}"></script>

appointment/tests/test_views.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,40 @@ def test_fetch_service_list_for_staff(self):
272272
[{"id": service.id, "name": service.name} for service in staff_member_services]
273273
)
274274

275+
def test_fetch_service_list_for_staff_no_staff_member_instance(self):
276+
"""Test that a superuser without a StaffMember instance receives an appropriate error message."""
277+
self.need_superuser_login()
278+
279+
# Ensure the superuser does not have a StaffMember instance
280+
StaffMember.objects.filter(user=self.user1).delete()
281+
282+
url = reverse('appointment:fetch_service_list_for_staff')
283+
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
284+
285+
# Check the response status code and content
286+
self.assertEqual(response.status_code, 400)
287+
response_data = response.json()
288+
self.assertIn('message', response_data)
289+
self.assertEqual(response_data['message'], _("You're not a staff member. Can't perform this action !"))
290+
self.assertFalse(response_data['success'])
291+
292+
def test_fetch_service_list_for_staff_no_services_offered(self):
293+
"""Test fetching services for a staff member who offers no services."""
294+
self.need_staff_login()
295+
296+
# Assuming self.staff_member1 offers no services
297+
self.staff_member1.services_offered.clear()
298+
299+
url = reverse('appointment:fetch_service_list_for_staff')
300+
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
301+
302+
# Check response status code and content
303+
self.assertEqual(response.status_code, 404)
304+
response_data = response.json()
305+
self.assertIn('message', response_data)
306+
self.assertEqual(response_data['message'], _("No services offered by this staff member."))
307+
self.assertFalse(response_data['success'])
308+
275309
def test_update_appt_min_info_create(self):
276310
self.need_staff_login()
277311

@@ -441,3 +475,36 @@ def test_make_staff_member_without_superuser(self):
441475

442476
# Check for a forbidden status code, as only superusers should be able to create staff members
443477
self.assertEqual(response.status_code, 403)
478+
479+
def test_is_user_staff_admin_with_staff_member(self):
480+
"""Test that a user with a StaffMember instance is identified as a staff admin."""
481+
self.need_staff_login()
482+
483+
# Ensure the user has a StaffMember instance
484+
if not StaffMember.objects.filter(user=self.user1).exists():
485+
StaffMember.objects.create(user=self.user1)
486+
487+
url = reverse('appointment:is_user_staff_admin')
488+
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
489+
490+
# Check the response status code and content
491+
self.assertEqual(response.status_code, 200)
492+
response_data = response.json()
493+
self.assertIn('message', response_data)
494+
self.assertEqual(response_data['message'], _("User is a staff member."))
495+
496+
def test_is_user_staff_admin_without_staff_member(self):
497+
"""Test that a user without a StaffMember instance is not identified as a staff admin."""
498+
self.need_staff_login()
499+
500+
# Ensure the user does not have a StaffMember instance
501+
StaffMember.objects.filter(user=self.user1).delete()
502+
503+
url = reverse('appointment:is_user_staff_admin')
504+
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
505+
506+
# Check the response status code and content
507+
self.assertEqual(response.status_code, 200)
508+
response_data = response.json()
509+
self.assertIn('message', response_data)
510+
self.assertEqual(response_data['message'], _("User is not a staff member."))

appointment/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from appointment.views_admin import add_day_off, add_or_update_service, add_or_update_staff_info, add_working_hours, \
1515
create_new_staff_member, delete_appointment, delete_appointment_ajax, delete_day_off, delete_service, \
1616
delete_working_hours, display_appointment, email_change_verification_code, fetch_service_list_for_staff, \
17-
get_service_list, get_user_appointments, make_superuser_staff_member, remove_staff_member, \
17+
get_service_list, get_user_appointments, is_user_staff_admin, make_superuser_staff_member, remove_staff_member, \
1818
remove_superuser_staff_member, update_appt_date_time, update_appt_min_info, update_day_off, update_personal_info, \
1919
update_working_hours, user_profile, validate_appointment_date
2020

@@ -88,6 +88,7 @@
8888
path('validate_appointment_date/', validate_appointment_date, name="validate_appointment_date"),
8989
# delete appointment ajax
9090
path('delete_appointment/', delete_appointment_ajax, name="delete_appointment_ajax"),
91+
path('is_user_staff_admin/', is_user_staff_admin, name="is_user_staff_admin"),
9192
]
9293

9394
urlpatterns = [

appointment/views_admin.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,19 @@ def fetch_service_list_for_staff(request):
239239
# Ensure the staff member is associated with this appointment
240240
if not Appointment.objects.filter(id=appointment_id,
241241
appointment_request__staff_member=staff_member).exists():
242-
return json_response("You do not have permission to access this appointment.", status_code=403)
242+
return json_response(_("You do not have permission to access this appointment."), status_code=403)
243243
else:
244244
# Fetch all services for the staff member (create mode)
245-
staff_member = StaffMember.objects.get(user=request.user)
245+
try:
246+
staff_member = StaffMember.objects.get(user=request.user)
247+
except StaffMember.DoesNotExist:
248+
return json_response(_("You're not a staff member. Can't perform this action !"), status=400, success=False)
246249

247250
services = list(staff_member.get_services_offered().values('id', 'name'))
248-
return json_response("Successfully fetched services.", custom_data={'services_offered': services})
251+
if len(services) == 0:
252+
return json_response(_("No services offered by this staff member."), status=404, success=False,
253+
error_code=ErrorCode.SERVICE_NOT_FOUND)
254+
return json_response(_("Successfully fetched services."), custom_data={'services_offered': services})
249255

250256

251257
@require_user_authenticated
@@ -497,4 +503,15 @@ def delete_appointment_ajax(request):
497503
appointment_id = data.get("appointment_id")
498504
appointment = get_object_or_404(Appointment, pk=appointment_id)
499505
appointment.delete()
500-
return json_response("Appointment deleted successfully.")
506+
return json_response(_("Appointment deleted successfully."))
507+
508+
509+
@require_user_authenticated
510+
@require_staff_or_superuser
511+
def is_user_staff_admin(request):
512+
user = request.user
513+
try:
514+
StaffMember.objects.get(user=user)
515+
return json_response(_("User is a staff member."), custom_data={'is_staff_admin': True})
516+
except StaffMember.DoesNotExist:
517+
return json_response(_("User is not a staff member."), custom_data={'is_staff_admin': False})

docs/migration_guides/latest.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
## Migration Guide for Version 3.0.0 🚀
1+
## Migration Guide for Version 3.0.1 🚀
22

3-
Version 3.0.0 of django-appointment is focused on enhancing functionality, documentation, and internationalization, with
3+
Version 3.x.x of django-appointment is focused on enhancing functionality, documentation, and internationalization, with
44
no significant database schema changes introduced. This guide provides the steps to ensure a smooth upgrade from version
55
2.1.1 or any earlier versions post 2.0.0.
66

7-
### Steps for Upgrading to Version 3.0.0:
7+
### Steps for Upgrading to Version 3.0.1:
88

99
1. **Backup Your Database**:
1010
- As a best practice, always back up your current database before performing an upgrade. This precaution ensures you
@@ -13,7 +13,7 @@ no significant database schema changes introduced. This guide provides the steps
1313
2. **Update Package**:
1414
- Upgrade to the latest version by running:
1515
```bash
16-
pip install django-appointment==3.0.0
16+
pip install django-appointment==3.0.1
1717
```
1818

1919
3. **Run Migrations** (if any):
@@ -27,17 +27,17 @@ no significant database schema changes introduced. This guide provides the steps
2727
4. **Review and Test**:
2828
- After upgrading, thoroughly test your application to ensure all functionalities are working as expected with the
2929
new version.
30-
- Pay special attention to features affected by the updates in version 3.0.0, as detailed in the release notes.
30+
- Pay special attention to features affected by the updates in version 3.x.x, as detailed in the release notes.
3131
3232
### Troubleshooting:
3333
3434
- **Issues Post Migration**:
35-
- If you encounter issues after migration, consult the release notes for version 3.0.0 for specific updates that
35+
- If you encounter issues after migration, consult the release notes for version 3.0.1 for specific updates that
3636
might affect your setup.
3737
- Check the Django logs for any error messages that can provide insights into issues.
3838
3939
### Important Notes 📝:
4040
41-
- No database schema changes were introduced in version 3.0.0, so the migration process should be straightforward.
41+
- No database schema changes were introduced in version 3.0.1, so the migration process should be straightforward.
4242
- As with any upgrade, testing in a development or staging environment before applying changes to your production
4343
environment is highly recommended.

0 commit comments

Comments
 (0)