Skip to content

Commit 943a54e

Browse files
authored
Merge pull request #165 from adamspd/appointment-view-use-forms
use a form for available slots
2 parents 65ea1d5 + db269f3 commit 943a54e

File tree

6 files changed

+51
-170
lines changed

6 files changed

+51
-170
lines changed

appointment/forms.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
WorkingHours
1717
)
1818
from .utils.db_helpers import get_user_model
19+
from .utils.validators import not_in_the_past
20+
21+
22+
class SlotForm(forms.Form):
23+
selected_date = forms.DateField(validators=[not_in_the_past])
24+
staff_member = forms.ModelChoiceField(StaffMember.objects.all(), error_messages={'invalid_choice': 'Staff member does not exist'})
1925

2026

2127
class AppointmentRequestForm(forms.ModelForm):
@@ -64,6 +70,11 @@ def __init__(self, *args, **kwargs):
6470
})
6571

6672

73+
class ClientDataForm(forms.Form):
74+
name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))
75+
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
76+
77+
6778
class PersonalInformationForm(forms.Form):
6879
# first_name, last_name, email
6980
first_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))

appointment/static/js/appointments.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function fetchNonWorkingDays(staffId, callback) {
158158
return; // Exit the function early
159159
}
160160
let ajaxData = {
161-
'staff_id': staffId,
161+
'staff_member': staffId,
162162
};
163163

164164
$.ajax({
@@ -231,7 +231,7 @@ function getAvailableSlots(selectedDate, staffId = null) {
231231

232232
let ajaxData = {
233233
'selected_date': selectedDate,
234-
'staff_id': staffId,
234+
'staff_member': staffId,
235235
};
236236
fetchNonWorkingDays(staffId, function (nonWorkingDays) {
237237
// Check if nonWorkingDays is an array
@@ -325,7 +325,7 @@ function requestNextAvailableSlot(serviceId) {
325325
return;
326326
}
327327
let ajaxData = {
328-
'staff_id': staffId,
328+
'staff_member': staffId,
329329
};
330330
$.ajax({
331331
url: requestNextAvailableSlotURL,

appointment/templates/appointment/appointment_client_information.html

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,11 @@ <h1 class="description-title">{% trans "Tell us a bit about yourself" %}</h1>
4444
</div>
4545
</div>
4646
<div class="name-email">
47-
<label for="name" class="name">{% trans "Full Name" %} *<br>
48-
<input type="text" placeholder="John DOE" id="name" class="client-name"
49-
maxlength="100" minlength="3" required name="name">
47+
<label for="{{ form.name.id_for_label }}" class="name">{% trans "Full Name" %} *<br>
48+
{{ client_data_form.name }}
5049
</label>
51-
<label for="email" class="email">{% trans "Email" %} *<br>
52-
<input type="email" placeholder="[email protected]" id="email"
53-
class="client-email" name="email"
54-
maxlength="100" required>
50+
<label for="{{ form.email.id_for_label }}" class="email">{% trans "Email" %} *<br>
51+
{{ client_data_form.email }}
5552
</label>
5653
</div>
5754
<div class="receive-email">

appointment/tests/test_views.py

Lines changed: 7 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@
2929
from appointment.tests.base.base_test import BaseTest
3030
from appointment.utils.db_helpers import Service, WorkingHours, create_user_with_username
3131
from appointment.utils.error_codes import ErrorCode
32-
from appointment.views import (
33-
create_appointment, get_appointment_data_from_post_request, get_client_data_from_post,
34-
redirect_to_payment_or_thank_you_page, verify_user_and_login
35-
)
32+
from appointment.views import create_appointment, redirect_to_payment_or_thank_you_page, verify_user_and_login
3633

3734

3835
class SlotTestCase(BaseTest):
@@ -43,21 +40,23 @@ def setUp(self):
4340

4441
def test_get_available_slots_ajax(self):
4542
"""get_available_slots_ajax view should return a JSON response with available slots for the selected date."""
46-
response = self.client.get(self.url, {'selected_date': date.today().isoformat()},
43+
response = self.client.get(self.url, {'selected_date': date.today().isoformat(), 'staff_member': self.staff_member1.id},
4744
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
48-
self.assertEqual(response.status_code, 403)
4945
response_data = response.json()
5046
self.assertIn('date_chosen', response_data)
5147
self.assertIn('available_slots', response_data)
5248
self.assertFalse(response_data.get('error'))
5349

54-
def test_get_available_slots_ajax_past_date(self):
50+
def test_get_available_slots_ajax_invalid_form(self):
5551
"""get_available_slots_ajax view should return an error if the selected date is in the past."""
5652
past_date = (date.today() - timedelta(days=1)).isoformat()
5753
response = self.client.get(self.url, {'selected_date': past_date}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
58-
self.assertEqual(response.status_code, 200)
5954
self.assertEqual(response.json()['error'], True)
6055
self.assertEqual(response.json()['message'], 'Date is in the past')
56+
# invalid staff id
57+
response = self.client.get(self.url, {'selected_date': date.today(), 'staff_member': 999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
58+
self.assertEqual(response.json()['error'], True)
59+
self.assertEqual(response.json()['message'], 'Staff member does not exist')
6160

6261

6362
class AppointmentRequestTestCase(BaseTest):
@@ -1043,114 +1042,6 @@ def test_notify_admin_about_reschedule_called(self, mock_notify_admin):
10431042
self.assertTrue(mock_notify_admin.called)
10441043

10451044

1046-
class GetAppointmentDataFromPostRequestTests(BaseTest):
1047-
def setUp(self):
1048-
self.factory = RequestFactory()
1049-
self.post_data = {
1050-
'phone': '1234567890',
1051-
'want_reminder': 'on',
1052-
'address': '123 Test St, Test City',
1053-
'additional_info': 'Please ring the bell.'
1054-
}
1055-
1056-
def test_get_appointment_data_from_post_request_with_data(self):
1057-
"""Test retrieving appointment data from a POST request with all data provided."""
1058-
request = self.factory.post('/fake-url/', self.post_data)
1059-
1060-
appointment_data = get_appointment_data_from_post_request(request)
1061-
1062-
self.assertEqual(appointment_data['phone'], self.post_data['phone'])
1063-
self.assertTrue(appointment_data['want_reminder'])
1064-
self.assertEqual(appointment_data['address'], self.post_data['address'])
1065-
self.assertEqual(appointment_data['additional_info'], self.post_data['additional_info'])
1066-
1067-
def test_get_appointment_data_from_post_request_partial_data(self):
1068-
"""Test retrieving appointment data from a POST request with partial data provided."""
1069-
partial_post_data = {
1070-
'phone': '1234567890',
1071-
# 'want_reminder' omitted to simulate unchecked checkbox
1072-
'address': '123 Test St, Test City',
1073-
# 'additional_info' omitted to simulate empty field
1074-
}
1075-
request = self.factory.post('/fake-url/', partial_post_data)
1076-
1077-
appointment_data = get_appointment_data_from_post_request(request)
1078-
1079-
self.assertEqual(appointment_data['phone'], partial_post_data['phone'])
1080-
self.assertFalse(appointment_data['want_reminder'], "want_reminder should be False if not 'on'")
1081-
self.assertEqual(appointment_data['address'], partial_post_data['address'])
1082-
self.assertEqual(appointment_data['additional_info'], None, "additional_info should be None if not provided")
1083-
1084-
def test_get_appointment_data_from_post_request_missing_data(self):
1085-
"""Test retrieving appointment data from a POST request with missing data."""
1086-
missing_data_post = {}
1087-
request = self.factory.post('/fake-url/', missing_data_post)
1088-
1089-
appointment_data = get_appointment_data_from_post_request(request)
1090-
1091-
self.assertEqual(appointment_data['phone'], None, "phone should be None if not provided")
1092-
self.assertFalse(appointment_data['want_reminder'], "want_reminder should be False if not provided")
1093-
self.assertEqual(appointment_data['address'], None, "address should be None if not provided")
1094-
self.assertEqual(appointment_data['additional_info'], None, "additional_info should be None if not provided")
1095-
1096-
1097-
class GetClientDataFromPostTests(BaseTest):
1098-
def setUp(self):
1099-
self.factory = RequestFactory()
1100-
1101-
def test_get_client_data_with_full_data(self):
1102-
"""Test retrieving client data from a POST request with all fields provided."""
1103-
post_data = {
1104-
'name': 'John Doe',
1105-
'email': '[email protected]',
1106-
}
1107-
request = self.factory.post('/fake-url/', post_data)
1108-
1109-
client_data = get_client_data_from_post(request)
1110-
1111-
self.assertEqual(client_data['name'], post_data['name'])
1112-
self.assertEqual(client_data['email'], post_data['email'])
1113-
1114-
def test_get_client_data_with_missing_name(self):
1115-
"""Test retrieving client data from a POST request with the name missing."""
1116-
post_data = {
1117-
# 'name' is missing
1118-
'email': '[email protected]',
1119-
}
1120-
request = self.factory.post('/fake-url/', post_data)
1121-
1122-
client_data = get_client_data_from_post(request)
1123-
1124-
self.assertIsNone(client_data['name'], "name should be None if not provided")
1125-
self.assertEqual(client_data['email'], post_data['email'])
1126-
1127-
def test_get_client_data_with_missing_email(self):
1128-
"""Test retrieving client data from a POST request with the email missing."""
1129-
post_data = {
1130-
'name': 'John Doe',
1131-
# 'email' is missing
1132-
}
1133-
request = self.factory.post('/fake-url/', post_data)
1134-
1135-
client_data = get_client_data_from_post(request)
1136-
1137-
self.assertEqual(client_data['name'], post_data['name'])
1138-
self.assertIsNone(client_data['email'], "email should be None if not provided")
1139-
1140-
def test_get_client_data_with_empty_fields(self):
1141-
"""Test retrieving client data from a POST request with empty fields."""
1142-
post_data = {
1143-
'name': '',
1144-
'email': '',
1145-
}
1146-
request = self.factory.post('/fake-url/', post_data)
1147-
1148-
client_data = get_client_data_from_post(request)
1149-
1150-
self.assertEqual(client_data['name'], '', "name should be empty string if provided as such")
1151-
self.assertEqual(client_data['email'], '', "email should be empty string if provided as such")
1152-
1153-
11541045
class RedirectToPaymentOrThankYouPageTests(BaseTest):
11551046
def setUp(self):
11561047
super().setUp()

appointment/utils/validators.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.core.exceptions import ValidationError
2+
from django.utils.translation import gettext_lazy as _
3+
4+
5+
def not_in_the_past(date):
6+
if date < date.today():
7+
raise ValidationError(_('Date is in the past'))

appointment/views.py

Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from django.utils.timezone import get_current_timezone_name
2222
from django.utils.translation import gettext as _
2323

24-
from appointment.forms import AppointmentForm, AppointmentRequestForm
24+
from appointment.forms import AppointmentForm, AppointmentRequestForm, SlotForm, ClientDataForm
2525
from appointment.logger_config import logger
2626
from appointment.models import (
2727
Appointment, AppointmentRequest, AppointmentRescheduleHistory, Config, DayOff, EmailVerificationCode,
@@ -57,28 +57,23 @@ def get_available_slots_ajax(request):
5757
:param request: The request instance.
5858
:return: A JSON response containing available slots, selected date, an error flag, and an optional error message.
5959
"""
60-
selected_date = convert_str_to_date(request.GET.get('selected_date'))
61-
staff_id = request.GET.get('staff_id')
6260

63-
if selected_date < date.today():
61+
slot_form = SlotForm(request.GET)
62+
if not slot_form.is_valid():
6463
custom_data = {'error': True, 'available_slots': [], 'date_chosen': ''}
65-
message = _('Date is in the past')
64+
if 'selected_date' in slot_form.errors:
65+
error_code = ErrorCode.PAST_DATE
66+
elif 'staff_member' in slot_form.errors:
67+
error_code = ErrorCode.STAFF_ID_REQUIRED
68+
message = list(slot_form.errors.as_data().items())[0][1][0].messages[0] # dirty way to keep existing behavior
6669
return json_response(message=message, custom_data=custom_data, success=False,
67-
error_code=ErrorCode.PAST_DATE)
70+
error_code=error_code)
6871

72+
selected_date = slot_form.cleaned_data['selected_date']
73+
sm = slot_form.cleaned_data['staff_member']
6974
date_chosen = selected_date.strftime("%a, %B %d, %Y")
7075
custom_data = {'date_chosen': date_chosen}
7176

72-
# If no staff_id provided, return an empty list of slots
73-
if not staff_id or staff_id == 'none':
74-
custom_data['available_slots'] = []
75-
custom_data['error'] = False
76-
message = _('No staff member selected')
77-
return json_response(message=message, custom_data=custom_data, success=False,
78-
error_code=ErrorCode.STAFF_ID_REQUIRED, status=403)
79-
80-
sm = get_object_or_404(StaffMember, pk=staff_id)
81-
custom_data['staff_member'] = sm.get_staff_member_name()
8277
days_off_exist = check_day_off_for_staff(staff_member=sm, date=selected_date)
8378
if days_off_exist:
8479
message = _("Day off. Please select another date!")
@@ -87,6 +82,8 @@ def get_available_slots_ajax(request):
8782
# if selected_date is not a working day for the staff, return an empty list of slots and 'message' is Day Off
8883
weekday_num = get_weekday_num_from_date(selected_date)
8984
is_working_day_ = is_working_day(staff_member=sm, day=weekday_num)
85+
86+
custom_data['staff_member'] = sm.get_staff_member_name()
9087
if not is_working_day_:
9188
message = _("Not a working day for {staff_member}. Please select another date!").format(
9289
staff_member=sm.get_staff_member_first_name())
@@ -108,6 +105,7 @@ def get_available_slots_ajax(request):
108105
return json_response(message='Successfully retrieved available slots', custom_data=custom_data, success=True)
109106

110107

108+
# TODO: service id and staff id are not checked
111109
@require_ajax
112110
def get_next_available_date_ajax(request, service_id):
113111
"""This view function handles AJAX requests to get the next available date for a service.
@@ -292,32 +290,6 @@ def create_appointment(request, appointment_request_obj, client_data, appointmen
292290
return redirect_to_payment_or_thank_you_page(appointment)
293291

294292

295-
def get_client_data_from_post(request):
296-
"""This function retrieves client data from the POST request.
297-
298-
:param request: The request instance.
299-
:return: The client data.
300-
"""
301-
return {
302-
'name': request.POST.get('name'),
303-
'email': request.POST.get('email'),
304-
}
305-
306-
307-
def get_appointment_data_from_post_request(request):
308-
"""This function retrieves appointment data from the POST request.
309-
310-
:param request: The request instance.
311-
:return: The appointment data.
312-
"""
313-
return {
314-
'phone': request.POST.get('phone'),
315-
'want_reminder': request.POST.get('want_reminder') == 'on',
316-
'address': request.POST.get('address'),
317-
'additional_info': request.POST.get('additional_info'),
318-
}
319-
320-
321293
def appointment_client_information(request, appointment_request_id, id_request):
322294
"""This view function handles client information submission for an appointment.
323295
@@ -333,10 +305,11 @@ def appointment_client_information(request, appointment_request_id, id_request):
333305

334306
if request.method == 'POST':
335307
appointment_form = AppointmentForm(request.POST)
308+
client_data_form = ClientDataForm(request.POST)
336309

337-
if appointment_form.is_valid():
310+
if appointment_form.is_valid() and client_data_form.is_valid():
338311
appointment_data = appointment_form.cleaned_data
339-
client_data = get_client_data_from_post(request)
312+
client_data = client_data_form.cleaned_data
340313
payment_type = request.POST.get('payment_type')
341314
ar.payment_type = payment_type
342315
ar.save()
@@ -356,11 +329,13 @@ def appointment_client_information(request, appointment_request_id, id_request):
356329
return response
357330
else:
358331
appointment_form = AppointmentForm()
332+
client_data_form = ClientDataForm()
359333

360334
extra_context = {
361335
'ar': ar,
362336
'APPOINTMENT_PAYMENT_URL': APPOINTMENT_PAYMENT_URL,
363337
'form': appointment_form,
338+
'client_data_form': client_data_form,
364339
'service_name': ar.service.name,
365340
}
366341
context = get_generic_context_with_extra(request, extra_context, admin=False)

0 commit comments

Comments
 (0)