Skip to content

A flight booking flow built using Django, htmx, JavaScript and Tailwind CSS

Notifications You must be signed in to change notification settings

CarlMurray/flyUX-pp4

Repository files navigation

Cover image

Table of Contents

📄 Project Background

👀 Overview

  • This Django web development project is a continuation of a UX Design project I completed during my Diploma in UX Design with the UX Design Institute which goes through the full UX process from user research all the way through to prototyping and handover.
  • The end result was a user-friendly flight booking flow which was informed by user research and prototyped to a medium fidelity in Figma.
  • Revisiting the project a year later, I am now using it as a foundation to build upon and bring it from Figma prototype, to a fully functioning Django-based full-stack web application, combining my passions for user-centric design and web development to build a holistic solution.

See here for the full UX Design case study

🔁 Process

❓ Problem Statement:

Your client is a start-up airline. They’re looking to create an online experience that is fast, easy and intuitive: one that’s based on a deep understanding of their target users.

🔎 Research

  • Competitive benchmarking was carried out to better understand what current industry leaders are doing and to understand common conventions and user expectations from a flight booking flow.
  • Usability testing of existing flows were carried out with users on the Aer Lingus, Ryanair and EasyJet websites to observe user behaviour, mental models, positives and pain points.
  • This research data was used to inform my solution, which aimed to provide a simple, straight-forward, upsell-free and easy-to-use flight booking flow.

See here for the full UX Design case study

🎨 Design

  • The design process began with sketching out low-fidelity wireframes to explore different layout options and to get a feel for the flow.
  • The wireframes were then translated into a medium-fidelity prototype in Figma.
  • Handover documentation was created to provide a detailed overview of the design and to provide guidance for the development process.
  • The design was informed by the research data and aimed to provide a simple, straight-forward, upsell-free and easy-to-use flight booking flow.
  • Branding was kept minimal and the design was kept clean and simple to allow for easy navigation and to avoid overwhelming the user with too much information.
  • The logo, typeface and colour scheme were chosen to reflect the brand's values of simplicity, speed and efficiency.

Colour palette

See here for the full UX Design case study

👨‍💻 Development

  • The development process was carried out using an Agile methodology with a focus on iterative development and continuous improvement.
  • The project was managed using a GitHub Project board with user stories and tasks.
  • User Stories were sized using T-shirt sizing (XS, S, M, L, XL) and prioritised based on the MoSCoW method (Must have, Should have, Could have, Won't have).

👤 User Stories

  1. As a user, I want to search for flights based on the selected departure airport, destination airport, and date, so that I can view the available flight options.
  2. As a user, I want to see a list of available flights after performing a search, including relevant details such as flight number, departure time, arrival time, and price, so that I can choose a suitable flight.
  3. As a user, I want to checkout quickly and securely on the site, so that I can finalise my booking.
  4. As a user, I want to provide my personal information (e.g. name, email, phone number) during the booking process, so that I can receive information about the flight.
  5. As a user, I want to review the booking details before finalising the booking, so that I can ensure everything is correct.
  6. As a user, I want to confirm the booking and receive a confirmation message or email, so that I know my flight has been successfully booked.
  7. As an admin, I want to view and update the details of a specific flight booking, including passenger information and booking status, so that I can handle customer inquiries and make necessary changes.
  8. As an admin, I want to manage user accounts, including creating, editing, and disabling user profiles, so that I can maintain control over site access and user privileges.
  9. As an admin, I want to log in to an admin panel securely, so that I can access the site's administrative features.
  10. As an admin, I want to manage the list of flights, including adding, editing, and deleting flight details, so that I can update flight schedules and availability.
  11. As an admin, I want to manage the list of airports, including adding, editing, and deleting airport entries, so that I can ensure accurate and up-to-date information.
  12. As a user, I want to create an account so that I can keep track of and edit my existing bookings.
  13. As a user I want to explore blog posts to get travel inspiration for my next trip.

🧮 Data Models

The data models for the project are shown below:

Database schema

  • Users app:
    • User - custom user model which extends the Django AbstractUser model. Default username field is replaced with email field.

  • Core app:
    • Flight - represents a flight. Contains origin, destination, outbound date, flight number, aircraft, departure time, arrival time and price.
    • Booking - represents a booking. Contains user, trip email, flight(s), fare(s), booking reference number, booking date, total price and status.
    • Passenger - represents Passengers associated with a Booking. Contains first name, last name and Booking id.
    • Airport - represents an airport. Contains name, IATA code, locality, region and country.
    • Aircraft - represents aircraft flow for a given flight. Contains aircraft model/type, identification and number of seats. Note: The Aircraft model was originally implemented with the intention of using it to implement seat selection functionality and options for seating and fares configurations. However, this was not implemented due to time constraints and potential for scope creep.

  • Blog app:
    • BlogPost - represents a blog post. Contains title, image, content, date created and content rendered which is required to render content created with a Markdown field. The Markdown field was added to the admin panel to allow for content formatting/styling.






🪀 Features

💩 CRUD Functionality

  • User CRUD functionality is primarily related to Bookings.
    • Create: Users create a Booking by going through the full user flow. A Booking is created once the user completes checkout.
    • Read: Users can view their created Bookings and relevant Booking details when logged in.
    • Update: Users can edit a Booking by changing Passenger information for that Booking.
    • Delete: Users can cancel a Booking which deletes it from the database.
  • Admin CRUD functionality exists for all Models and is done from the Django Admin dashboard.

🔑 Authentication & Authorisation

  • Users can create an account from the Signup page.
  • Users can login from the Login page.
  • Authorisation is required to reach certain pages such as Bookings, Passenger Details and Checkout. Requesting these pages while unauthprised will redirect users to the Login page.
  • If not logged in by the time a user reaches the Passenger Details page, a modal shows on screen with the Signup form. Users can also click the Login link at the bottom of the form instead, and be redirected to the Passenger Details page on successful authentication.
Signup page

Screenshot of Signup page

Login page

Screenshot of Login page

Signup modal

Screenshot of Signup modal

🧭 Navigation

  • Primary navigation is located in the header and is present on all pages.
  • A hamburger menu is present on mobile devices and expands to show the primary navigation links.
Navigation on homepage

Screenshot of homepage

🔎 Search

  • The search form is located on the homepage and allows users to search for flights by entering their origin, destination, trip type, dates and number of passengers.
  • The search form is validated on the front-end and back-end to ensure that the data entered is valid and that the search can be performed.
  • If the search is valid, the user is redirected to the Flights page where they can view the search results.
  • If the search is invalid, an error message is shown.
Homepage with search form

Screenshot of homepage

✈️ Flights

  • The Flights page shows the search results for the user's search query.
  • If there are no results, an info message is shown prompting the user to try an alternate date.
  • If there are results, the results are shown in a card format with the outbound and return flights shown in separate cards.
  • Once flights have been selected, the CTA button is enabled and the user can proceed to the next step in the booking flow. Selected flight numbers are stored in session storage and are used to populate the Order Summary page.
Flights

Screenshot of Flights page

🗓️ Alternate Dates

  • The alternate dates feature allows users to view flight results for alternate dates to their original search query.
  • The feature works by sending an AJAX request (via htmx) to the server with the new date, and the server responds with the new flight data for that date, and new date options for the alternate date selector.
  • The new flight data is then loaded into the page and the click event listeners are re-attached to the new flight cards so that they expand when clicked, to show the fares.
  • It is not possible to select a date that is in the past.
  • It is not possible to select a date that is before the outbound date if the trip type is return.

💸 Fares

  • The fares are shown in a card format with the outbound and return fares shown in separate cards.
  • The user can click on a fare card to expand it and show the fare details.
  • The user can select a fare by clicking the button on the fare card.
  • Selected fare information is stored in session storage and is used to populate the Order Summary page.
Fares

Screenshot of Fares

💼 Baggage Policy

  • Links to view the airline's baggage policy are shown on the fare cards.
  • Clicking the link opens a modal with the baggage policy information.
  • The modal can be closed by pressing ESC or clicking outside of the modal.
Baggage Policy Modal

Screenshot of baggage policy modal

🛫 Edit Flights

  • The user can edit their flight selection by clicking the "Edit" button on the fare card.
  • This reloads the previous flight search results and allows the user to select new flights.
Edit Flights

Screenshot of Edit Flights

👯 Passengers

  • The Passengers form is shown after the user has selected their flights.
  • The form is pre-populated with the number of passengers selected in the search form.
  • If logged in, the form is pre-populated with the user's details.
  • The "Confirm email" field is not pre-populated and must be entered by the user as a security and error prevention measure.
Passengers form

Screenshot of Passengers form

💳 Checkout

  • The Checkout page shows the Order Summary and the Payment Details form.
  • The Order Summary is populated with the flight and fare information stored in session storage.
  • For the purposes of this project, the Payment Details form is a mockup and does not process any payments. The form is lightly validated on the front-end using the Payform library with some minor modification to allow for dummy card data to be entered.
Checkout page

Screenshot of Checkout page

✅ Confirmation

  • The Confirmation page shows the user's booking reference number and a confirmation message.
  • A CTA button is shown which allows the user to view their bookings.
Confirmation page

Screenshot of Confirmation page

📜 Bookings

  • The Bookings page shows a list of the user's bookings.
  • An Edit button is shown for each booking which allows the user to edit the booking's passengers.
  • The Booking detail page shows the booking's details and the passenger details.
Bookings page

Screenshot of Bookings page

❌ Cancel Booking

  • The user can cancel a booking by clicking the "Cancel Booking" button on the Booking detail page.
  • This deletes the booking from the database by sending an AJAX request to the server and redirects the user to the Bookings page. A confirmation dialog is shown to the user to confirm that they want to cancel the booking.
Cancel Booking

Screenshot of Cancel Booking

👥 Edit Passengers

  • The user can edit a booking's passengers by clicking the "Edit Passengers" button on the Booking detail page.
  • This sends an AJAX request (via htmx) to the server and loads the Passengers form with the booking's passenger data pre-populated.
  • The user can then edit the passenger data and submit the form to update the booking's passenger data, or cancel the edit and return to the Booking detail page.

🌐 Blog

  • The Blog is basic in design and is not a focus of this project.
  • The Blog page shows a list of blog posts which were generated using the ChatGPT.
  • The Blog detail page shows the blog post's title, image and content.
  • New blog posts can be added via the Django Admin dashboard.
Blog page

Screenshot of Blog page

🤔 About

  • The About page shows a brief description of the project and the technologies used.
About page

Screenshot of About page





🛣️ Roadmap

Implement seat selection as per original design

Screenshot of seat selection design

Implement 'Extras' screen for seat selection, baggage selection, car hire and insurance as per original design.

Screenshot of extras selection

Build out flights page to show prices in alternate date selector, sort options, cart and edit search

Search results screen

> Add password reset and "Remember me" login option




🪲 Bugs

  1. When styling the flight search result cards, there was some difficulty in adding a transition to animate the expansion of the card when clicked, to show the fares. It was found that it is not possible to transition from display:hidden, nor is it possible to transition from height:0 to height:auto. A workaround was implemented by setting max-height:0 with overflow:hidden then using JavaScript to add max-height:100rem (or any other large value) along with transition:all to animate the card expansion and collapse.
  2. The "alternate date selector" on the flight results page works by sending an AJAX request (via htmx) when an alternate date is clicked, and responding with HTML with the new flight data for the given date. When the new HTML is loaded from the response, the click event listeners need to be re-attached to the new flight card elements so that they expand when clicked, to show the fares. However, when initially trying to implement this re-attachment, an issue arose where the flight cards would not expand every second time an alternate date was selected. Following some troubleshooting, it was found that the click event listeners were compounding, thus negating each other (i.e. as if a user clicked the card twice in rapid succession). Using console.log and Chrome Dev Tools for debugging enabled me to see which events were firing so that the issue could be identified and solved by defining the click handler function outside of the event listner function. Relevant Stack Overflow thread.
  3. The Flights table contains 90,000 rows of data and when implementing the Bookings CRUD functionality, there were severe issues experienced particularly in the admin panel when trying to view/edit Bookings which resulted in indefinite loading times as the Flights data was loaded. Django has a number of built-in solutions for this issue and a solution was implemented by defining search_fields and autocomplete_fields in the ModelAdmin configurations for the Flight and Booking models. Django Documentation Reference
  4. When testing the site on mobile, a bug was identified where the date input field placeholder text would not display. Following some research and troubleshooting, it was found that this is a known issue with Flatpickr and a workaround was found as referenced in this JSfiddle.
Code Snippet Implemented to fix bug #4
.flatpickr-mobile:before {
  content: attr(placeholder);
  color: #9ca3af;
  width: 100%;
}

.flatpickr-mobile:focus[value]:not([value=""]):before {
  display: none;
}

input[type="hidden"][value]:not([value=""]) + .flatpickr-mobile:before {
  display: none;
}

  1. When testing the alternate date selection feature, a bug was identified where the "No flights on selected date" error message would not disappear during the transition between a newly selected alternate date. Additionally, if the fares container was expanded for a flight, it would remain visible during the request when selecting a new date. The loading indicator would display during the request, but would push the error message/fares container down. The intended behaviour was for all content - including flights, fares and error messages - to be hidden during a request, and for the loading indicator to display. Some time was spent troubleshooting the htmx implementation, however it was found that all that needed to be done was add classList.add('hidden') to these elements when an alternate date was clicked, as I had done for the flight cards already in the earlier stages of development. This was a simple fix and a reminder to always ensure code is clean and well documented, as I had already forgotten how I had implemented this functionality on the flight cards by the time I was reaching the latter stages of development.
Screenshot of bug #5

Screenshot of bug

  1. When testing the site on mobile, a bug was identified where when clicking on the search form input fields (origin, destination, dates), the keyboard displayed which was not intented as the user must select from the dropdown menu/date picker. This was solved by adding readonly to the input fields. Future iterations of the site will allow for user keyboard input, however this was not implemented due to time constraints and potential for scope creep.
  2. Fixing bug #6 resulted in a new bug where a user could submit the search form without entering any data. This was solved by adding a simple piece of logic which removes the readonly attribute before validating the form, to check that the input values are valid before submitting.
Code snippet added to fix bug #7
// SEARCH FORM VALIDATION
const validateForm = function (e) {
  e.preventDefault();

  // NEW CODE SNIPPET ADDED TO FUNCTION
  document.querySelectorAll("input").forEach((input) => {
    input.removeAttribute("readonly");
  });

  ...

  // REST OF FUNCTION...
  ...
}

  1. Performance issues were identified with slow page loading speeds noted when accessing a user's Bookings page. Django Debug Toolbar helped to identify inefficient database queries which were causing the performance issues. Queries were optimised by using select_related which brought the total load time from 4800ms to 120ms - a significant improvement.




⚙️ Technologies Used

This section outlines the various technologies used throughout the project and the purpose each serves.

💾 Core Development Technologies

📚 Libraries, Frameworks and Packages

  • Tailwind CSS - used to style elements throughout the site.
  • Flowbite - a Tailwind-based open-source library; used very sparingly for small number of minor components in the site (radio select, dropdown select)
  • htmx - an open-source lightweight library used to fetch and load content dynamically via AJAX requests. Utilised specifically for fetching new Flight data and Passengers edit form.
  • Flatpickr - a JavaScript library which provides the date picker styles and functionality on the Homepage.
  • Payform - a JavaScript library used for formatting the Payment Details form inputs.

🐍 Python/Django Packages

🖥️ Infrastructural Technologies

  • PostgreSQL (via Digital Ocean) - used for database.
  • Heroku - used for hosting the application.
  • AWS S3 - used for storing static files and media files.




🧪 Testing

🤖 Automatic Testing

  • Automated unit tests were written for core back-end functionality of the app to test data validation and integrity, templates used, HTTP status codes, user input etc.
  • 35 tests were written in total.
  • The Coverage package was used to assist in guiding test requirements.
  • 100% coverage was achieved on the core models and views.
  • Future plans to write unit tests for coverage on blog and users apps.
Test coverage report

Coverage report

⚒️ Manual Testing

🛰️ Overview

  • Responsiveness was tested as per below table (go to section: Responsiveness)
  • All HTML files were passed through the W3C validator with no errors
  • All JavaScript files were passed through JSHint with no errors present.
  • The website was tested on major browsers including Chrome, Safari, Firefox and Edge.
  • All user flows were tested in depth including navigating through the booking flow, viewing blog content, entering search queries, clicking CTAs and links, and form submission.
  • All forms were tested to ensure validation was present and that forms could be submitted without error
  • Lighthouse was used to test for Performance, Accessibility, Best Practices and SEO and adjustments were made to improve test results.
  • WAVE was used to test for accessibility issues and adjustments were made to improve test results.

🧪 General Testing

Expand test detail
Test Action Success Criteria
Homepage loads Navigate to website URL Page loads < 3s, no errors
Links Click on each Navigation link, CTA, button, logo, footer link Correct page is loaded/correct action performed, new tab opened if applicable
Form validation Enter data into each input field, ensure only valid data is accepted Form doesn't submit until correct data entered, error message shown
Responsiveness Resize viewport window from 320px upwards with Chrome Dev Tools. Test devices as detailed in Responsiveness Testing Page layout remains intact and adapts to screen size as intended
Lighthouse Perform Lighthouse test on each page for the primary user flow (Booking process) Score of > 89 on Performance, Accessibility, Best Practices
Browser compatibility Test links, layout, appearance, functionality and above Tests on Chrome, Safari, Firefox and Edge. BrowserStack used to test various mobile/large format devices with recent browser versions. Website looks and functions as intended and passes all tests above

🏠 Homepage & Search Testing

Expand test detail
Test Action Success Criteria
Origin/Destination selection - Click Origin/Destination fields. - Drop down menu opens with correct data.
- Text input disabled.
- Dropdown closes on click outside.
- Correct selection added to field.
Trip type selection - Select trip type (return/one-way) - One-way trip hides return date field.
- Return trip shows return date field.
Date selection - Click date field - On click, show date picker.
- Dates in the past disabled.
- Dates after 01-07-2024 disabled.
- Correct selection added to field.
Passenger selection - Click passengers field - Drop down menu displays with up to 8 passengers
Form submission - Fill in form and click submit button - Form submitted
- Flight results page loaded with correct data
Validation - Select same origin and destination
- Select return date before selected outbound date
- Leave fields blank
- Field validation message shown if same origin and destination
- Field validation message shown if return date before outbound date
- Field validation messages shown for blank fields
- Form does not submit until data is valid

✈️ Flight Search Results Testing

Expand test detail
Test Action Success Criteria
Flight results - Review flight results - Correct flight data shown
Alternate dates - Select alternate dates for outbound/return flights - Correct flight data fetched
- Correct dates added to selector
- Error message shown if same/invalid dates selected
Fare selection - Select a fare - Fares container expands on click
- Correct fare data shown
- Fare/flight selection works as intended
Edit flight - Edit a flight selection - When edit button clicked, flight results show and new flight can be selected
Confirm flights - Select flights and confirm - When confirmed, date added to session storage and request sent

💵 Payment & Confirmation Testing

Expand test detail
Test Action Success Criteria
Flight results - Review flight results - Correct flight data shown
Alternate dates - Select alternate dates for outbound/return flights - Correct flight data fetched
- Correct dates added to selector
- Error message shown if same/invalid dates selected
Fare selection - Select a fare - Fares container expands on click
- Correct fare data shown
- Fare/flight selection works as intended
Edit flight - Edit a flight selection - When edit button clicked, flight results show and new flight can be selected
Confirm flights - Select flights and confirm - When confirmed, date added to session storage and request sent

🔒 Authorisation Testing

Expand test detail

| Test | Action | Success Criteria | | --------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --- | | Flight results | - Review flight results | - Correct flight data shown | | Alternate dates | - Select alternate dates for outbound/return flights | - Correct flight data fetched
- Correct dates added to selector
- Error message shown if same/invalid dates selected | | Fare selection | - Select a fare | - Fares container expands on click
- Correct fare data shown
- Fare/flight selection works as intended | | Edit flight | - Edit a flight selection | - When edit button clicked, flight results show and new flight can be selected | | Confirm flights | - Select flights and confirm | - When confirmed, date added to session storage and request sent | |


🚦 Lighthouse Testing

  • All pages were tested using Lighthouse with the primary goals of identifying performance and accessibility issues and ensuring adherance to best practices.
  • The Lighthouse test results for each step of the core user flow are shown below:
Homepage

Homepage Lighthouse test

Flights

Flights Lighthouse test

Passengers

Passengers Lighthouse test

Checkout

Checkout Lighthouse test

Bookings Overview

Bookings Lighthouse test

Booking Detail

Booking Detail Lighthouse test


📱 Responsiveness Testing

  • Testing for responsiveness was conducted using Chrome Dev Tools and ResponsivelyApp.
  • The website was tested extensively on a range of emulated mobile, tablet and large format screen sizes in both portrait and landscape orientations.
Responsiveness test results

Responsiveness testing with ResponsivelyApp

Device iPhone SE iPhone X iPhone 12 Pro iPhone 13 Pro Max iPhone 14 Pro Max iPad iPad Air iPad Pro Macbook Pro
Resolution 375x667 375x812 390x844 414x76 414x896 768x1024 820x1180 1024x1366 1440x900
Render Pass Pass Pass Pass Pass Pass Pass Pass Pass
Layout Pass Pass Pass Pass Pass Pass Pass Pass Pass
Functionality Pass Pass Pass Pass Pass Pass Pass Pass Pass
Links Pass Pass Pass Pass Pass Pass Pass Pass Pass
Images Pass Pass Pass Pass Pass Pass Pass Pass Pass
Portrait/Landscape Pass Pass Pass Pass Pass Pass Pass Pass Pass

✅ Code Validation

  • All HTML pages were checked with the W3C Markup Validation Service with no major errors present. Errors were present for htmx attributes, however these are valid and necessary for the functionality of the site. There were also duplicate IDs present in the HTML, however these are also valid and necessary for the functionality of the site.

  • All JavaScript files were passed through JSHint with no errors present.

  • All custom coded Python files were formatted with a PEP8 complaint formatter - Black.

  • HTML Validation Examples:




🖥️ Deployment

📦 Local Deployment

  1. Clone the repository from GitHub by clicking the "Code" button and copying the URL.
  2. Open your preferred IDE and open a terminal session in the directory you want to clone the repository to.
  3. Type git clone followed by the URL you copied in step 1 and press enter.
  4. Install the required dependencies by typing pip install -r requirements.txt in the terminal.
  5. Note: The project is setup to use environment variables. You will need to set these up in your local environment. See Environment Variables for more information.
  6. Connect your database of choice and run the migrations by typing python manage.py migrate in the terminal.
  7. Create a superuser by typing python manage.py createsuperuser in the terminal and following the prompts.
  8. Optional: Fixtures for Flight, Airport and Aircraft models are included in the project in the fixtures directory. To add pre-populated data to the database, run python manage.py loaddata fixtures/[fixture_name].json.
  9. Run the app by typing python manage.py runserver in the terminal and opening the URL in your browser.

💜 Heroku Deployment

  1. Login to the Heroku dashboard and create a new app.
  2. Connect your GitHub repository to your Heroku app.
  3. In the Settings tab, ensure that the Python Buildpack is added.
  4. Set environment variables in the Config Vars section of the Settings tab.
  5. In the Deploy tab, enable automatic deploys from your GitHub repository.
  6. Click the "Deploy Branch" button to deploy the app.
  7. Once the app has been deployed, click the "Open App" button to view the app.
  8. If using S3, you will need to set up an S3 bucket and add the environment variables to your Heroku app (see tutorial here for reference.)

📐 Environment Variables

  • For local deployment, you will need to create a .env file in the root directory of the project and set the environment variables in this file.
  • For Heroku deployment, you will need to set the environment variables through the Heroku CLI or through the Heroku dashboard under 'Config Vars'.
  • You need to define the following variables:
    • If using a Postgres database:
      • DATABASE_URL - the URL for your Postgres database.
      • NAME - the name of your database.
      • USER - the username for your database.
      • PASSWORD - the password for your database.
      • HOST - the host for your database.
      • PORT - the port for your database.
    • Django settings:
      • SECRET_KEY - the secret key for your Django project.
      • DEBUG - set to True for development, False for production.
    • If using S3:
      • USE_S3 - set to True to use S3, False to use local storage.
      • AWS_ACCESS_KEY_ID - your AWS access key ID.
      • AWS_SECRET_ACCESS_KEY - your AWS secret access key.
      • AWS_STORAGE_BUCKET_NAME - the name of your AWS S3 bucket.




👋 Credits

  • Unsplash - used for sourcing Blog photographic images.
  • ChatGPT - used for generating all Blog text content.
  • Favicon.io - used to create favicon.
  • Payform - used for Payment Details input formatting.
  • Mockaroo - used for creating model data.