Live site: https://flyux.carlmurray.design
- 📄 Project Background
- 🔁 Process
- 🪀 Features
- 🛣️ Roadmap
- 🪲 Bugs
- ⚙️ Technologies Used
- 🧪 Testing
- 🖥️ Deployment
- 👋 Credits
- 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
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.
- 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.
- 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.
- 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).
- 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.
- 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.
- As a user, I want to checkout quickly and securely on the site, so that I can finalise my booking.
- 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.
- As a user, I want to review the booking details before finalising the booking, so that I can ensure everything is correct.
- 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.
- 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.
- 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.
- As an admin, I want to log in to an admin panel securely, so that I can access the site's administrative features.
- 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.
- 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.
- As a user, I want to create an account so that I can keep track of and edit my existing bookings.
- As a user I want to explore blog posts to get travel inspiration for my next trip.
The data models for the project are shown below:
- Users app:
User
- custom user model which extends the DjangoAbstractUser
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.
- User CRUD functionality is primarily related to
Booking
s.- 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
Booking
s and relevantBooking
details when logged in. - Update: Users can edit a
Booking
by changingPassenger
information for thatBooking
. - Delete: Users can cancel a
Booking
which deletes it from the database.
- Create: Users create a Booking by going through the full user flow. A
- Admin CRUD functionality exists for all Models and is done from the Django Admin dashboard.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
Implement 'Extras' screen for seat selection, baggage selection, car hire and insurance as per original design.
Build out flights page to show prices in alternate date selector, sort options, cart and edit search
- 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 fromheight:0
toheight:auto
. A workaround was implemented by settingmax-height:0
withoverflow:hidden
then using JavaScript to addmax-height:100rem
(or any other large value) along withtransition:all
to animate the card expansion and collapse. - 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. - The
Flight
s table contains 90,000 rows of data and when implementing theBooking
s CRUD functionality, there were severe issues experienced particularly in the admin panel when trying to view/editBooking
s which resulted in indefinite loading times as theFlight
s data was loaded. Django has a number of built-in solutions for this issue and a solution was implemented by definingsearch_fields
andautocomplete_fields
in theModelAdmin
configurations for theFlight
andBooking
models. Django Documentation Reference - 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;
}
- 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 addclassList.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.
- 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. - 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...
...
}
- 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.
This section outlines the various technologies used throughout the project and the purpose each serves.
- Django used as a full-stack framwork for developing the app.
- JavaScript used for client-side interaction and validation.
- HTML/CSS + Django Template Language used for building out site pages.
- 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 andPassenger
s 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.
- Gunicorn - provides HTTP server.
- psycopg2 - provides PostgreSQL connection.
- Pillow - used for image processing (Model ImageField).
- Whitenoise - used for serving static files.
- Coverage - used for testing and analysis.
- Django Markdown Field - adds a markdown-compatible text field to admin panel (for BlogPost model).
- Black - A PEP8 compliant code formatter.
- Django Debug Toolbar - used for debugging.
- Django Storages and Boto3 - used for storing static files and media files on AWS S3.
- PostgreSQL (via Digital Ocean) - used for database.
- Heroku - used for hosting the application.
- AWS S3 - used for storing static files and media files.
- 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
andusers
apps.
- 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.
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 |
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 |
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 |
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 |
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 | |
- 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:
- 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.
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 |
-
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:
- Clone the repository from GitHub by clicking the "Code" button and copying the URL.
- Open your preferred IDE and open a terminal session in the directory you want to clone the repository to.
- Type
git clone
followed by the URL you copied in step 1 and press enter. - Install the required dependencies by typing
pip install -r requirements.txt
in the terminal. - 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.
- Connect your database of choice and run the migrations by typing
python manage.py migrate
in the terminal. - Create a superuser by typing
python manage.py createsuperuser
in the terminal and following the prompts. - 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, runpython manage.py loaddata fixtures/[fixture_name].json
. - Run the app by typing
python manage.py runserver
in the terminal and opening the URL in your browser.
- Login to the Heroku dashboard and create a new app.
- Connect your GitHub repository to your Heroku app.
- In the Settings tab, ensure that the Python Buildpack is added.
- Set environment variables in the Config Vars section of the Settings tab.
- In the Deploy tab, enable automatic deploys from your GitHub repository.
- Click the "Deploy Branch" button to deploy the app.
- Once the app has been deployed, click the "Open App" button to view the app.
- 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.)
- 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 toTrue
for development,False
for production.
- If using S3:
USE_S3
- set toTrue
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.
- If using a Postgres database:
- 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.