Implementation

Main Tools




Card image cap

Python was the language that we used for the backend to program the flow of data through the model, view and the template.


Card image cap

Django framework was the main tool to implement the webapp. We used its built in methods to implement our application including the authentication system and admin site.


Card image cap

Bootstrap is a CSS framework containing HTML and CSS base design templates. It was used for our front-end development.


Card image cap

SQLite is used as for our embedded database.



Implementation Overview


Authentication


In Django, LoginRequiredMixin is an inheritance class that only allows requests to a view class from logged-in users. In this class, any unauthorised user will be redirected to the log-in page. This system implements a view class called UserView which can be found in UserView.py. It inherits LoginRequiredMixin. All other view classes extend UserView, therefore, all successful HTTP requests to our application require the user to be logged in.


                                class UserView(LoginRequiredMixin, View):
                                    '''A base class for the rest of the views
                                        to extend and utilise, automatically fetches
                                        username and firstname for logged in user
                                        N.B. restrict what is returned to the webpage'''
                                
                                    username = None
                                    first_name = None
                                
                                    def fetch_details(self, request):
                                        self.first_name = request.user.get_short_name()
                                        self.username = request.user.username
                                    
                                


Admin Responsibilities


Django's built-in admin site allows admins to manage users as well as user groups and permissions.

At admin.py, admin.site.register(model) functions are used to register data models to the admin site so that these models can easily be created, destroyed and modified through the user interface.

Card image cap


Database Connection


SQLite it used as the database in this system. Django has a built-in ORM system which means database tables are directly mapped to model classes. Each instance of one of these classes represents a particular record in the database.

In settings.py there exists a dictionary that contains the settings for the databases that would be used in the web app. NAME specifies the name of the database and ENGINE specifies the type of database.


                                DATABASES = {
                                    'default' {
                                        'ENGINE': 'django.db.backends.sqlite3',
                                            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
                                    }
                                }
                            

If connecting to another type of database, other parameters may be required, such as username, password and host.



Display Daily Rota


This section describes how the user's daily rota on my_rota.html is implemented. It is necessary to understand this process as generating the weekly rota on the same page and the team rota on team_rota.html are variations of this process.

Displaying the daily rota is handled by RotaView in RotaView.py.


1. Identify logged in user

The UserView class has a fetch_details(self, request) function which automatically fetches the username and the first name for logged in users. It is a base class and is extended by RotaView. At RotaView, the user object can be queried as the user's username has been identified. The user object is stored in the variable user.


2. Identify the required date

A datetime picker is imported from bootstrap_datepicker_plus.widgets. It works alongside jQuery and Django forms to provide the functionality to select a particular date on a GUI. When the user sends a valid form, either by entering the date manually or clicking on the graphical calendar, urls.py directs the request to RotaView. The variable selected_date (set to the current date if no user input) is used to store the user input.


3. Generate appointments for the rota

Nearly all of the information to generate the rota (user and selected_date) has been obtained. The appointments for the rota still need to be retrieved. All non-cancelled appointments with this user on selected_date are retrieved and ordered by start time with the lines of code below. As seen below w Django's built-in .filter() and .exclude() functions are used to query the database.


                            def generate_rota_appointments_ordered(self, selected_date, user):
                                cancelled = AppointmentStatus.objects.all().filter(
                                    status='Cancelled')[0]
                                rota_appointments_ordered = list(Appointment.objects.all().filter(
                                    appointment_date=selected_date, team_member=user).exclude(
                                        appointment_status=cancelled))
                                rota_appointments_ordered.sort(
                                    key=lambda x: (x.appointment_start_time))

                                return rota_appointments_ordered
                            
4. Generate rota

The generate_day_rota(self, appointments, tmp_rota) function is called to assign each appointment to its time slots. This is done by looking at the start time of each appointment and the appointment type. The appointment type determines the number of slots taken up. Each appointment is filled into its timeslots on tmp_rota, which is an empty dictionary with time slots as keys and appointment objects as values. tmp_rota is a duplicate of DAY_ROTA which can be found in constants.py. RotaView then renders the template my_rota.html and returns this filled in rota in the response.


                            def generate_day_rota(self, appointments, tmp_rota):
                                for appointment in appointments:
                                        appointment_code = (
                                            appointment.appointment_code.appointment_description)
                                        appointment_length = SLOTS[appointment_code]
                                        start_time = str(appointment.appointment_start_time)[:5]
                                        start_index = DAY_ROTA_KEYS.index(start_time)

                                        for i in range(start_index, start_index + appointment_length):
                                            key = DAY_ROTA_KEYS[i]
                                            tmp_rota[key] = appointment

                                return tmp_rota
                            
5. Display

my_rota.html receives the rota from the RotaView. The rota is displayed graphically by using a loop and a table. Looping through the dictionary creates the rows in the table. If statements are used to determine how the row should look, depending on whether the dictionary value is populated or empty; the different row colours are implemented using Bootstrap.

The 'Add New Appointment' button is implemented using Bootstrap, it is embedded with a link and clicking the button will take the user to an AppointmentForm.

The patient name and appointment ID are displayed on the table, they are embedded with a links to other templates which show more details to the user. Clicking the the patient name takes the user to patient_detail.html, clicking the appointment id will take the user to appointment_detail.html.

Card image cap



Display Weekly Rota


This section explains how the weekly rota is displayed. This process is nearly identical to displaying a daily rota as above in the 'Daily Rota' section. Please read 'Daily Rota' section before reading this section.

Displaying the weekly rota is handled by RotaView in RotaView.py.


1. Identify logged in user

*The same case as the 'Daily Rota' section.


2. Identify required dates

As above, in the 'Daily Rota' section, the user selects a date manually or through the GUI.

To add to this, an array of 7 consecutive days is generated from this date, this is the week that will be displayed on the rota. It is stored under the dates variable.


                                dates = [str(selected_date + timedelta(days=x)) for x in range(0, 8)]

3. Generate rotas

Step 3 is very similar to above in the 'Daily Rota' section. Instead, generate_day_rota() is called for each date and it stores these rotas in an array, called weekly_rota. This behaviour is handled by generate_weekly_rota(self, weekly_rota, dates, user).


                            def generate_weekly_rota(self, weekly_rota, dates, user):
                                cancelled = AppointmentStatus.objects.all().filter(
                                    status='Cancelled')[0]
                                for d in dates:
                                    rota_appointments_ordered = list(Appointment.objects.all().filter(
                                        appointment_date=d, team_member=user).exclude(
                                            appointment_status=cancelled))
                                    rota_appointments_ordered.sort(
                                        key=lambda x: (x.appointment_start_time))
                                    tmp_rota = DAY_ROTA.copy()
                                    rota = self.generate_day_rota(rota_appointments_ordered, tmp_rota)
                                    date_rota_tuple = (d, rota)

                                    weekly_rota.append(date_rota_tuple)

                                return weekly_rota
                            

RotaView passes this weekly_rota to the template (my_rota.html) in the response.


4. Display

Step 4 is very similar to above in the 'Daily Rota' section. Instead, the array of rotas is looped through and each one is displayed side by side. Each rota is displayed in an identical fashion as described in the 'Daily Rota' section.



Display Team Rota


This section explains how the team rota is displayed. This process is nearly identical to displaying a daily rota as above in the 'Daily Rota' section. Please read the 'Daily Rota' section before reading this section.

Displaying the team rota is handled by TeamView in TeamView.py.


1. Identify selected users

Users are stored in the database as models. Users selected in the form (stored in members_selected) are saved to a variable called team_members this is done with the following lines of code. We are left with an array of user objects.


                            if len(members_selected) == 0:
                                team_members = list(User.objects.filter(
                                    groups__name='Ocularist'))
                            else:
                                team_members = []
                                for member_key in members_selected:
                                    team_members.append(User.objects.get(
                                        pk=int(member_key)))
                            

2. Identify required date

Similar to above, in the 'Daily Rota' section, the user selects a date manually or through the GUI.

Their choice is stored under the selected_date variable.


3. Generate rotas

Step 3 is very similar to above in the 'Daily Rota' section. Instead, generate_rota() is called for each user and it stores these rotas in an array, called team_rota. This behaviour is handled by generate_team_rota(self, selected_date, team_rota, team_members).


                            def generate_team_rota(self, selected_date, team_rota, team_members):
                                cancelled = AppointmentStatus.objects.all().filter(
                                    status='Cancelled')[0]
                                for user in team_members:
                                    rota_appointments_ordered = list(Appointment.objects.all().filter(
                                        appointment_date=selected_date, team_member=user).exclude(
                                            appointment_status=cancelled))
                                    rota_appointments_ordered.sort(
                                        key=lambda x: (x.appointment_start_time))
                                    tmp_rota = DAY_ROTA.copy()
                                    rota = self.generate_rota(rota_appointments_ordered, tmp_rota)
                                    name_rota_tuple = (user.first_name +  ' ' + user.last_name, rota)
                                    team_rota.append(name_rota_tuple)

                                return team_rota
                            

TeamView passes this team_rota to the template (team_rota.html) in the response.


4. Display

Step 4 is very similar to above in the 'Daily Rota' section. Instead, the array of rotas is looped through and each one is displayed side by side. Each rota is displayed in an identical fashion as described in the 'Daily Rota' section.



Display Patient List

This section describes how the patients are displayed on wait_list.html

Displaying the patients is handled by WaitingListView in WaitingListView.py


1. Fetch data

In WaitingListView, patients are separated into bookedPatients and waitingPatients. These queries are passed to wait_list.html in the response.


                                bookedPatients = Patient.objects.all().filter(patient_status=booked)
                                waitingPatients = Patient.objects.all().exclude(patient_status=booked)
                                

2. Display

Bootstrap cards are used as content containers in the wait_list.html page. For loops are used to generate a card and to dynamically fill in a patient's data to that card.



Display Appointment List


This section describes how the appointments are displayed on appointments.html

Displaying the appointments is handled by AppointmentView in AppointmentView.py


1. Fetch data

In AppointmentView, appointments are separated into past (past appointments), appointments (upcoming appointments) and cancelled (cancelled appointments). This is done so through a simple for loop which can be seen in the code snippet below.

All non-cancelled appointments are looped through. For each appointment, if appointment_date is before today it goes into past otherwise it goes to appointments.


                                    temporaryAppointments = Appointment.objects.all().exclude(
                                        appointment_status=cancelled)
                                    appointments = []
                                    past = []
                                    for appointment in temporaryAppointments:
                                        if appointment.appointment_date < today:
                                            past.append(appointment)
                                        else:
                                            appointments.append(appointment)
                                    

Cancelled appointments are collected with a query.


                            cancelledAppointments = Appointment.objects.all().filter(
                                appointment_status=cancelled)
                            

These queries and arrays are passed to appointments.html by AppointmentView in the response.


2. Display

Bootstrap cards are used as content containers in appointments.html. For each array/query given to the template, a separate loop is executed. The loops generate a card for each appointment and dynamically fill in the appointment's data to that card. The loop will also add buttons, such as cancel and update, to the card if necessary.



PAS Checklist


This section describes how the PAS checklist is implemented on checklist.html.

The PAS checklist is used by admin staff. Appointments that haven't been confirmed on PAS are displayed on this page. Once an admin staff has input the changes into PAS, they can tick of the appointment from the checklist.html page.

The PAS checklist is handled by ChecklistView which can be found in ChecklistView.py.


Fetch Appointments

In ChecklistView appointments that aren't confirmed on PAS are retrieved from the database and returned to the template (checklist.html) in the response.


                            def get(self, request):
                                super().fetch_details(request)
                                appointmentsn = Appointment.objects.filter(
                                    status_confirmed_on_pas='n')
                                appointmentsN = Appointment.objects.filter(
                                    status_confirmed_on_pas='N')
                        
                                appointments = appointmentsn | appointmentsN
                                return(request, 'website/checklist.html',
                                                {'appointments' appointments,
                                                'first_name' self.first_name})
                        
                            
Display Appointments

These appointments are displayed with a Bootstrap card. They are displayed in a very similar fashion to how patients are displayed on the patients page (described in the 'Display Patient List' section), and appointments on the appointments page (described in the 'Display Appointment List' section). Each appointment has a button to tick it off.


Tick Appointments

When a user clicks the button to tick the appointment the following actions are taken:

1. User is taken to /tick_appointment/pk.
2. urls.py sends to user to TickAppointmentView in TickAppointmentView.py.
3. TickAppointmentView sets status_confirmed_on_pas to 'Y' and sets changes_to_be_confirmed_on_pas to '', effectively deleting the contents.
4. User gets redirected to checklist.html.

This appointment will no longer appear on the checklist as its status_confirmed_on_pas is 'Y' and neither 'N' nor 'n'.



Update Patient


This section describes how updating a patient is implemented on update_patient.html.

Updating an appointment is very similar to this process.

Updating an appointment is handled by UpdatePatientView in UpdatePatientView.py


Model Forms

It is necessary to understand how the Django ModelForm class works in order to understand this section along with the following sections of this document, 'Add Patient', 'Update Appointment' and 'Update Patient'.

This class creates a form from a Django model. These forms are defined in forms.py, each attribute in the model is given a field to enter information. The code snippet below doesn't show all of the fields but it illustrates how the code is laid out.


                                class PatientForm(ModelForm):
                                    class Meta
                                        model = Patient
                                        fields = ('PAS_number', 'first_name', 'middle_name',
                                                'surname', 'DOB', 'priority_code',
                                                'patient_information', 'patient_status')

                                        widgets = {
                                            'PAS_number': forms.NumberInput(
                                                attrs={'class': 'form-control'})
                                                ...
                                        }
                                

is_valid()

It is also necessary to understand how the .is_valid() function works to understand this section along with the following sections of this document, 'Add Patient', 'Update Appointment' and 'Update Patient'.

By default, this function checks the data input into the fields. If and only if there is a violation regarding the type of data input to the field, then .is_valid() returns False. For example, using the code snippet above, if 'hello' is input to the PAS_number field then .is_valid() will return False. However, if 123 was input to the field, then .is_valid() would return True.


Get Method

If the user is getting the update patient page, the functionality works as follows:

1. User clicks update patient button which brings them to the URL, /update_patient/pk.
2. urls.py sends the user to UpdatePatientView in UpdatePatientView.py.
3. UpdatePatientView uses this pk to fetch the patient object with this primary key.
4. UpdatePatientView creates a PatientForm and fills in the form with the patient's information.
5. UpdatePatientView renders this form in the template (update_patient.html).


                                class UpdatePatientView(UserView):
                                    def get(self, request, patient_id):
                                        super().fetch_details(request)
                                        patient = Patient.objects.get(pk=patient_id)
                                        form = PatientForm(request.POST or None, instance=patient)
                                        return render(request, 'website/update_patient.html',
                                                    {'patient' patient, 'form' form,
                                                    'first_name' self.first_name})

                                
Post Method

If the user is posting to the update patient page the functionality works as follows.

1. UpdatePatientView receives the form and checks if it is valid using .is_valid()
2. If it is valid then the form is saved to the database and the user gets redirected to /wait_list.html.
3. Else, the user cant submit the form and an error message will explain what is wrong with the form.


                                class UpdatePatientView(UserView):
                                    ...

                                    def post(self, request, patient_id):
                                        super().fetch_details(request)
                                        patient = Patient.objects.get(pk=patient_id)
                                        form = PatientForm(request.POST or None, instance=patient)
                                        if form.is_valid():
                                            form.save()
                                            return redirect('/wait_list')
                                        return render(request, 'website/update_patient.html',
                                                    {'patient' patient, 'form' form,
                                                     'first_name' self.first_name})
    
                                    

Update Appointment


This section describes how updating an appointment works is implemented on update_appointment.html.

Updating an appointment is very similar to updating a patient. Please fully read the 'Update Patient' section before reading this section.

Updating an appointment is handled by UpdateAppointmentView in UpdateAppointmentView.py


Clean Functions

It is necessary to understand how the clean functions work to understand this section along with the 'Add Appointment' section.

Clean functions are used by .is_valid() to check the data. If a clean function isn't defined, for example in our implementation of a patient form, then .is_valid() works as described in the 'Update Patient' section. However, when updating appointments the system should also check for appointment clashes so a clean function is defined which performs the checks that are performed by the default is_valid() function and also checks for appointment clashes.

Effectively, we define our own way to check if a form is valid or not.

This function raises validation errors if there are any problems with the inputs. These validation errors will be displayed to the user if they cant submit the form.

*This code snippet is quite large, however, it can be found in forms.py.


Get Method

If the user is getting the update appointment page, the functionality works as follows:

1. User clicks update appointment button which brings them to the URL, /update_appointment/pk.
2. urls.py sends the user to UpdateAppointmentView.
3. UpdateAppointmentView uses this pk to fetch the appointment object with this primary key.
4. UpdateAppointmentView creates an AppointmentForm for an appointment and fills in the form with the appointment's information.
5. UpdateAppointmentView renders this form in the template.

*This works identically to updating a patient.


Post Method

If the user is posting to the update appointment page the functionality works as follows.

1. UpdateAppointmentView receives the form and checks if it is valid using .is_valid(), this is_valid() function uses the clean function that that has beendefined. So it also checks for appointment clashes.
2. If it is valid then the end time is calculated, the form is saved to the database and the user gets redirected to appointments.html
3. Else, the user cant submit the form and an error message will explain what is wrong with the form.


                            def post(self, request, appointment_id):
                                super().fetch_details(request)
                                appointment = Appointment.objects.get(appointment_ID=appointment_id)
                                form = AppointmentForm(request.POST or None, instance=appointment)
                        
                                if form.is_valid():
                                    start_time = form.cleaned_data['appointment_start_time']
                                    appointment_code_obj = form.cleaned_data['appointment_code']
                                    appointment_type = appointment_code_obj.appointment_description
                                    timing = APPOINTMENT_TIMING[appointment_type]
                                    end_time = datetime.combine(date.today(), start_time) + timing
                        
                                    form.instance.appointment_end_time = end_time
                                    form.instance.status_confirmed_on_pas = 'N'
                                    form.save()
                        
                                    return HttpResponseRedirect('/appointments')
                                return render(request, 'website/update_appointment.html',
                                            {'appointment' appointment, 'form' form,
                                            'first_name' self.first_name})
                                    

Add Patient


This section describes how adding a patient is implemented on add_patient.html.

Updating a patient's record is very similar to this process, please read the 'Update Patient' section before reading this section.

Adding a patient is handled by AddPatientView in AddPatientView.py.


Get Method

If the user is getting the update patient page, the functionality works as follows:

1. User clicks update patient button which brings them to the URL, /add_patient.
2. urls.py sends the user to AddPatientView.
3. AddPatientView creates an empty PatientForm.
4. AddPatientView renders this form in the template (add_patient.html).


Post Method

If the user is posting to the add patient page the functionality works as follows.

1. AddPatientView recieves the form and checks if it is vaild using .is_valid(). .is_valid() works as described in the Update Patient' section.
2. If it is valid then the form is saved to the database and the user gets redirected to appointments.html.
3. Else, the user cant submit the form and an error message will explain what is wrong with the form.


Add Appointment


This section describes how adding an appointment is implemented on add_appointment.html.

Updating an appointment is very similar to updating a patient. Please fully read the 'Update Appointment' section bnefore reading this section.

Adding an appointment is handled by AddAppointmentView in AddAppointmenttView.py.


Get Method

If the user is getting the add appointment page, the functionality works as follows:

1. User clicks add appointment button which brings them to the URL, /add_appointment.
2. urls.py sends the user to AddAppointmentView.
3. AddAppointmentView creates an empty AppointmentForm.
4. AddAppointmentView renders this form in the template.

*This works identically to the get method in AddPatientView.


Post Method

If the user is posting to the add appointment page the functionality works as follows.

1. AddAppointmentView receives the form and checks if it is valid using .is_valid(), this .is_valid() function uses the clean function that has been defined in AppointmentForm. So it also checks for appointment clashes. Read about the clean method in the 'Update Appointment' section.
2. If it is valid then the end time is calculated, the form is saved to the database and the user gets redirected
3. Else, the user cant submit the form and an error message will explain what is wrong with the form.


Delete Patient


This section explains how deleting a patient is implemented.

Deleting a patient is handled by DeletePatientView in DeletePatientView.py.


1. Delete a patient if they exist.

This functionality is accessed by the URL /delete_patient/pk. To check that a patient with this pk exists, patient with this pk are queried for. If an error is thrown then they don't exist. If an error is not thrown then they exist and the patient is deleted.

2. Generate message.

A message is also created for the user, this message will be displayed in the notification panel on the interface. This message is stored in the variable msg.

3. Display.

DeletePatientView sends msg to the template (wait_list.html). This template will display all of the patients that still exist on the database. msg is displayed in the notification panel.


Cancel Appointment


This section explains how cancelling an appointment is implemented.

Cancelling an appointments is handled by CancelAppointmentView in CancelAppointmentView.py.


Get Method
1. Cancel the Appointment

This functionality is accessed by the URL cancel_appointment/pk, a user can get to this URl if they click a 'Cancel Appointment' button. This appointment object is queried with this pk. appointment_status is set to 'Cancelled' and status_confirmed_on_pas is set to 'N'.

2. PAS Notifications

If it is less than 2 days before the appointment, a message is added to the appointment, this message can be viewed on the PAS checklist.

3. View Template

Variables for appointments are generated and appointments.html is rendered with these variables.


Filtering

This section explains how filtering is implemented on the appointments.html and wait_list.html pages. This functionality is implemented with AppointmentView and WaitingListView respectively.

*filter functionality for appointments and patients is implemented identically. for simplicity's sake, this document will only discuss appointment filtering.

Forms

Filter functionality for the appointment page is implemented using a Django form. This is a form but it is not a model form. The form used is called SearchAppointmentForm in forms.py. It is a series of search bars with a single submit button. The below code snippet shows one of the search bars on the form.


                                class SearchAppointmentForm(forms.Form):
                                    appointment_ID = forms.CharField(
                                        label='Search by ID',
                                        max_length=100, required=False)
                                    ...
                                
Processing Input

User inputs to the forms are used to filter objects using processQuery() function which is defined in AppointmentsView. Below is a section of code for filtering appointments. The parameter dic is defined by dic = form.cleaned_data. dic contains the user's input. For each key:value pair in dic, if the value is not None then appointment objects are filtered to find the particular appointments that match the search request. These appointments are sent to the appointments page to be displayed.


                                def processQuery(self, dic, appointments):
                                    appointment_ID = dic['appointment_ID']
                                    PAS_number = dic['PAS_number']
                                    ...
                                    if appointment_ID:
                                        appointments = appointments.filter
                                            (appointment_ID=appointment_ID)
                                    if PAS_number:
                                       appointments = appointments.filter
                                            (PAS_number=PAS_number)
                                    ...
                                    return appointments