Skip to content

Commit 5ea883b

Browse files
authored
Merge pull request #168 from adamspd/fix/staff-member-display-appt-and-admin-all-rights
Fix #159 bug introductions
2 parents d6855b3 + ea7ee21 commit 5ea883b

File tree

5 files changed

+103
-20
lines changed

5 files changed

+103
-20
lines changed

appointment/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
__package_name__ = "django-appointment"
66
__url__ = "https://github.com/adamspd/django-appointment"
77
__package_website__ = "https://django-appt.adamspierredavid.com/"
8-
__version__ = "3.4.0"
8+
__version__ = "3.4.1"
99
__test_version__ = False

appointment/static/js/app_admin/staff_index.js

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,46 @@ document.addEventListener("DOMContentLoaded", function () {
4040
});
4141
});
4242

43+
const AppStateProxy = new Proxy(AppState, {
44+
set(target, property, value) {
45+
console.log(`Setting ${property} to ${value}`)
46+
// Check if the property being changed is 'isCreating'
47+
if (value === true) {
48+
attachEventListeners(); // Attach event listeners if isCreating becomes true
49+
// (didn't check if property is isCreating, since AppStateProxy is only set with it)
50+
}
51+
target[property] = value; // Set the property value
52+
return true; // Indicate successful setting
53+
}
54+
});
55+
56+
function attachEventListeners() {
57+
// Checks if the DOM is already loaded
58+
if (document.readyState === "complete" || document.readyState === "interactive") {
59+
// DOM is already ready, attach event listeners directly
60+
attachEventListenersToDropdown();
61+
} else {
62+
// If the DOM is not yet ready, then wait for the DOMContentLoaded event
63+
document.addEventListener('DOMContentLoaded', function () {
64+
attachEventListenersToDropdown();
65+
});
66+
}
67+
}
68+
69+
function attachEventListenersToDropdown() {
70+
const staffDropdown = document.getElementById('staffSelect');
71+
if (staffDropdown && !staffDropdown.getAttribute('listener-attached')) {
72+
staffDropdown.setAttribute('listener-attached', 'true');
73+
staffDropdown.addEventListener('change', async function () {
74+
const selectedStaffId = this.value;
75+
const servicesDropdown = document.getElementById('serviceSelect');
76+
const services = await fetchServicesForStaffMember(selectedStaffId);
77+
updateServicesDropdown(servicesDropdown, services);
78+
});
79+
}
80+
}
81+
82+
4383
function initializeCalendar() {
4484
const formattedAppointments = formatAppointmentsForCalendar(appointments);
4585
const calendarEl = document.getElementById('calendar');
@@ -285,7 +325,7 @@ function closeModal() {
285325
cancelButton.style.display = "none";
286326

287327
// Reset the editing flag
288-
AppState.isEditingAppointment = false;
328+
AppStateProxy.isEditingAppointment = false;
289329

290330
// Close the modal
291331
$('#eventDetailsModal').modal('hide');
@@ -419,6 +459,32 @@ async function populateStaffMembers(selectedStaffId, isEditMode = false) {
419459
return selectElement;
420460
}
421461

462+
// Function to fetch services for a specific staff member
463+
async function fetchServicesForStaffMember(staffId) {
464+
const url = `${fetchServiceListForStaffURL}?staff_id=${staffId}`;
465+
try {
466+
const response = await fetch(url);
467+
if (!response.ok) throw new Error('Network response was not ok');
468+
const data = await response.json();
469+
return data.services_offered || [];
470+
} catch (error) {
471+
console.error("Error fetching services: ", error);
472+
return []; // Return an empty array in case of error
473+
}
474+
}
475+
476+
// Function to update services dropdown options
477+
function updateServicesDropdown(dropdown, services) {
478+
// Clear existing options
479+
dropdown.innerHTML = '';
480+
481+
// Populate with new options
482+
services.forEach(service => {
483+
const option = new Option(service.name, service.id); // Assuming service object has id and name properties
484+
dropdown.add(option);
485+
});
486+
}
487+
422488
function getCSRFToken() {
423489
const metaTag = document.querySelector('meta[name="csrf-token"]');
424490
if (metaTag) {
@@ -510,14 +576,17 @@ function createNewAppointment(dateInput) {
510576

511577
async function showCreateAppointmentModal(defaultStartTime, formattedDate) {
512578
const servicesDropdown = await populateServices(null, false);
513-
const staffDropdown = await populateStaffMembers(null, false);
579+
let staffDropdown = null;
580+
if (isUserSuperUser) {
581+
staffDropdown = await populateStaffMembers(null, false);
582+
}
514583
servicesDropdown.id = "serviceSelect";
515584
servicesDropdown.disabled = false; // Enable dropdown
516585

517586
document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate);
518587

519588
adjustCreateAppointmentModalButtons();
520-
AppState.isCreating = true;
589+
AppStateProxy.isCreating = true;
521590
$('#eventDetailsModal').modal('show');
522591
}
523592

@@ -581,7 +650,10 @@ async function showEventModal(eventId = null, isEditMode, isCreatingMode = false
581650
if (!appointment) return;
582651

583652
const servicesDropdown = await getServiceDropdown(appointment.service_id, isEditMode);
584-
const staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
653+
let staffDropdown = null;
654+
if (isUserSuperUser) {
655+
staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
656+
}
585657

586658
document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown);
587659
adjustModalButtonsVisibility(isEditMode, isCreatingMode);
@@ -608,11 +680,11 @@ function adjustModalButtonsVisibility(isEditMode, isCreatingMode) {
608680
function toggleEditMode() {
609681
const modal = document.getElementById("eventDetailsModal");
610682
const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
611-
AppState.isCreating = false; // Turn off creating mode
683+
AppStateProxy.isCreating = false; // Turn off creating mode
612684

613685
// Proceed only if an appointment is found
614686
if (appointment) {
615-
AppState.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state
687+
AppStateProxy.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state
616688
updateModalUIForEditMode(modal, AppState.isEditingAppointment);
617689
} else {
618690
console.error("Appointment not found!");

appointment/templates/administration/staff_index.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@
316316
const updateApptDateURL = "{% url 'appointment:update_appt_date_time' %}";
317317
const validateApptDateURL = "{% url 'appointment:validate_appointment_date' %}";
318318
const isUserStaffAdminURL = "{% url 'appointment:is_user_staff_admin' %}";
319-
const isUserSuperUser = "{{ is_superuser }}";
319+
const isUserSuperUser = "{{ is_superuser }}" === "True";
320320
</script>
321321
<script>
322322
{# Text for translation #}
@@ -341,15 +341,12 @@
341341
<script src="{% static 'js/js-utils.js' %}"></script>
342342

343343
<script>
344-
console.log("isUserSuperUser", isUserSuperUser)
345-
346344
function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime, staffDropdown) {
347345
const startTimeValue = isEditMode ? moment(appointment.start_time).format('HH:mm:ss') : defaultStartTime;
348346
const disabledAttribute = isEditMode ? '' : 'disabled';
349347

350-
const isSuperuser = isUserSuperUser === "True";
351348
let superuserInputField = '';
352-
if (isSuperuser) {
349+
if (isUserSuperUser) {
353350
superuserInputField = `
354351
<div class="flex-container-appt">
355352
<label>{% trans 'Staff Member' %}:</label>

appointment/tests/test_views.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def test_fetch_service_list_for_staff(self):
339339
)
340340

341341
def test_fetch_service_list_for_staff_no_staff_member_instance(self):
342-
"""Test that a superuser without a StaffMember instance receives an appropriate error message."""
342+
"""Test that a superuser without a StaffMember instance receives no inappropriate error message."""
343343
self.need_superuser_login()
344344

345345
# Ensure the superuser does not have a StaffMember instance
@@ -349,11 +349,9 @@ def test_fetch_service_list_for_staff_no_staff_member_instance(self):
349349
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
350350

351351
# Check the response status code and content
352-
self.assertEqual(response.status_code, 400)
352+
self.assertEqual(response.status_code, 200)
353353
response_data = response.json()
354354
self.assertIn('message', response_data)
355-
self.assertEqual(response_data['message'], _("You're not a staff member. Can't perform this action !"))
356-
self.assertFalse(response_data['success'])
357355

358356
def test_fetch_service_list_for_staff_no_services_offered(self):
359357
"""Test fetching services for a staff member who offers no services."""

appointment/views_admin.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def get_user_appointments(request, response_type='html'):
5656
}
5757
context = get_generic_context_with_extra(request=request, extra=extra_context)
5858
# if appointment is empty and user doesn't have a staff-member instance, put a message
59-
if not appointments and not StaffMember.objects.filter(user=request.user).exists() and request.user.is_staff:
59+
# TODO: Refactor this logic, it's not clean
60+
if not appointments and not StaffMember.objects.filter(
61+
user=request.user).exists() and not request.user.is_superuser:
6062
messages.error(request, _("User doesn't have a staff member instance. Please contact the administrator."))
6163
return render(request, 'administration/staff_index.html', context)
6264

@@ -226,10 +228,12 @@ def add_or_update_staff_info(request, user_id=None):
226228
return render(request, 'administration/manage_staff_member.html', context)
227229

228230

231+
# TODO: Refactor this function, handle the different cases better.
229232
@require_user_authenticated
230233
@require_staff_or_superuser
231234
def fetch_service_list_for_staff(request):
232235
appointment_id = request.GET.get('appointmentId')
236+
staff_id = request.GET.get('staff_id')
233237
if appointment_id:
234238
# Fetch services for a specific appointment (edit mode)
235239
if request.user.is_superuser:
@@ -241,14 +245,23 @@ def fetch_service_list_for_staff(request):
241245
if not Appointment.objects.filter(id=appointment_id,
242246
appointment_request__staff_member=staff_member).exists():
243247
return json_response(_("You do not have permission to access this appointment."), status_code=403)
248+
services = list(staff_member.get_services_offered().values('id', 'name'))
249+
elif staff_id:
250+
# Fetch services for the specified staff member (new mode based on staff member selection)
251+
staff_member = get_object_or_404(StaffMember, id=staff_id)
252+
services = list(staff_member.get_services_offered().values('id', 'name'))
244253
else:
245254
# Fetch all services for the staff member (create mode)
246255
try:
247256
staff_member = StaffMember.objects.get(user=request.user)
257+
services = list(staff_member.get_services_offered().values('id', 'name'))
248258
except StaffMember.DoesNotExist:
249-
return json_response(_("You're not a staff member. Can't perform this action !"), status=400, success=False)
259+
if not request.user.is_superuser:
260+
return json_response(_("You're not a staff member. Can't perform this action !"), status=400,
261+
success=False)
262+
else:
263+
services = list(Service.objects.all().values('id', 'name'))
250264

251-
services = list(staff_member.get_services_offered().values('id', 'name'))
252265
if len(services) == 0:
253266
return json_response(_("No services offered by this staff member."), status=404, success=False,
254267
error_code=ErrorCode.SERVICE_NOT_FOUND)
@@ -536,4 +549,7 @@ def is_user_staff_admin(request):
536549
StaffMember.objects.get(user=user)
537550
return json_response(_("User is a staff member."), custom_data={'is_staff_admin': True})
538551
except StaffMember.DoesNotExist:
539-
return json_response(_("User is not a staff member."), custom_data={'is_staff_admin': False})
552+
# if superuser, all rights are granted even if not a staff member
553+
if not user.is_superuser:
554+
return json_response(_("User is not a staff member."), custom_data={'is_staff_admin': False})
555+
return json_response(_("User is a superuser."), custom_data={'is_staff_admin': True})

0 commit comments

Comments
 (0)