This app creates shopping lists for dinner parties from a recipe database.
It provides a structured flow with step-by-step questions that are straightforward to answer, does all necessary calculations in the background, and produces a tailor-made list depending on the ingredients needed, while also taking existing stock into account.
The result is more user-friendly and less prone to errors than using Google Sheets' built-in filtering and sorting for the same purpose.
The database contains recipes I routinely use, so this app does not only simulate a real-life process, it actually is usable in real life.
It was developed as my first Python project, as a requirement for my coursework for Code Institute (Portfolio Project 3).
Developer: Dr. Sylvia Blaho
See the development progress and further plans on GitHub Projects.
- Table of Contents
- User Experience (UX)
- Design
- Features
- Start screen
- "Stop program" confirmation
- "Start planning" confirmation
- Dish selection screen
- Dish selection confirmation
- Selected dish and ingredients
- Modified list of dishes
- Automatically end planning loop after last dish
- Continue without checking the pantry
- Start checking the pantry
- Pantry check
- Pantry check done
- No items needed
- Shopping list
- End screen
- "Start again" message
- Future features
- Accessibility
- Technologies used
- Deployment
- Testing
- Credits
Table of contents generated with readme-toc
As an avid cook and dinner party host, I maintain several recipe databases in Google Sheets for different occasions, seasons and dietary preferences. These contain a range of dishes, ingredients and substitutions.
As it is not uncommon that I host a series of dinner parties within the same week, it is paramount to be able to pool all the ingredients needed for all these parties together, so that they can be purchased in a single grocery run or delivery.
Google Sheets is very well suited for storing data in a structured way, but it is much less capable of producing a shopping list tailored for a specific occasion. While it is technically possible to create such a list using conditionals, filtering and sorting, it is a cumbersome process prone to manual error.
The Dinner Party Planner app aims to solve this problem. It provides a structured flow with step-by-step questions that are straightforward to answer, does all necessary calculations in the background, and produces a tailor-made list depending on the ingredients needed, while also taking existing stock into account.
The same set of goals was determined for all users of the app regardless of whether they are first-time or returning users.
- [UX1] I want to have a user-friendly and easy to use shopping list generator.
- [UX2] I want to see what recipes are available at each stage of planning
- [UX3] I want to choose one or more recipes to shop for
- [UX4] I want to see the ingredients for the dish I selected
- [UX5] I want to get a shopping list of all ingredients correctly added
- [UX6] I want to adjust the shopping list based on the ingredients I already have
- [UX7] I want clear instructions on the next step at each stage of the process
- [UX8] I want to only see relevant information on the screen at each step of the process
- [UX9] I want to be able to visually distinguish different kinds of information on the screen
- [UX10] I want to know the result of each step/choice in the process
- [CR1] I want to create my first Python application
- [CR2] I want to create an application that fills a real-life need
- [CR3] I want to make use of Object Oriented Programming
- [CR4] I want to reduce code redundancy as much as possible
- [CR5] I want the app to handle all possible user input
- [CR6] I want the user to always be sure how to interact with the program
- [CR7] I want the app to be as visually appealing as possible under the given constraints of the template
- [CR8] I want to write code with future extensibility and scalability in mind
From the outset, I had three main goals in mind: ease of use, proper error handling, and scalability.
The diagram below shows the Minimum Viable Product flow for the app. As discussed with my mentor, this satisfies the criteria of an MVP by being usable as is, and also a suitable project to finish in 2 weeks after just starting to learn Python.
The MVP program flow can be summarized as follows:
- if the user answers
Y
to the initial question: start planning loop - if the user answers
N
to the initial question: end with message - when the user selects a dish:
- remove it from the list of dishes
- display its name and ingredients
- add its ingredients to the shopping list
- if the shopping list already contains an ingredient that is added: combine two items into one, sum the ingredient quantity
- if the user wants to continue adding an ingredient: rerun planning loop
- if the user does not want to add more ingredients:
- end planning loop
- print shopping list, end program
- if the user selects all dishes:
- end planning loop
- print shopping list, end program
After the MVP was completed well ahead of the expected timeline, and there was more time left until the submission deadline than originally anticipated, I decided to add a new feature that was outside the scope of the MVP: the check_pantry()
function and its accompanying logic.
This loops through each ingredient on the shopping list, asks the user how much of it they already have, and adjusts the quantities accordingly. When the resulting quantity is 0 or less, the item is deleted from the shopping list.
Even though this feature seemed like an easy add-on, it has nearly doubled the steps in the program flow.
The current version of the program flow is as follows (click here to view the full-size version on Miro):
The current program flow can be summarized as follows:
- if the user answers
Y
to the initial question: start planning loop - if the user answers
N
to the initial question: end with message - when the user selects a dish:
- remove it from the list of dishes
- display its name and ingredients
- add its ingredients to the shopping list
- if the shopping list already contains an ingredient that is added: combine two items into one, sum the ingredient quantity
- if the user wants to continue adding an ingredient: rerun planning loop
- if the user does not want to add more ingredients:
- end planning loop
- ask if they want to check their pantry
- if the user selects all dishes:
- end planning loop
- ask if they want to check their pantry
- if the user does not want to check the pantry:
- print shopping list, end program
- if the user wants to check the pantry
- ask how much of each shopping list ingredient they have
- subtract answer from quantity on shopping list
- if resulting quantity is 0 or less, remove item from the list
- if there are shopping list items left, print shopping list
- end program with message
This flow ensures an intuitive progression through the process, and eliminates the need for complicated navigation.
The app is designed to work with as little typing as possible.
Yes/no questions only require a single character as an answer. They accept both capital and lowercase answers.
The list of dishes is presented as a numbered list, so that users only need to enter the dish number, rather than type the whole name of the dish.
The pantry checker function only requires the user to enter a number for each ingredient checked.
The list of dishes changes dynamically, so only dishes that have not yet been added to the shopping list are shown to the user at each point.
The pantry checker flow only asks input for dishes that have been added to the shopping list.
All lists shown to the user are alphabetized, which aids visual processing.
The shopping list is presented twice during the flow of the program: at the end of planning, and during the pantry checking process.
Each of these is very different from each other as well as from how the data is stored, tailored to the specific purpose in the flow.
Both are
- transformed into a readable string
- alphabetized
- measurement names are shown instead of abbreviations
- singular/plural is handled based on the quantity
When a user enters an invalid input, the input is repeated in the error message. This improves user interaction, and also avoids misunderstanding that could result from a user entering input before the prompt appears.
Error messages also show the reason why user input is rejected (not a valid number, out of range, not "Y"or "N"), and remind the user what type of input is accepted for the question on the screen.
When revising and refactoring code, I devoted special attention to code design, beyond merely getting the functionalities to work. I worked to reduce redundancy and improve readability by utilizing Object Oriented Programming (OOP), reusing functions/methods, and splitting up code into separate files where warranted.
The first version of the app consisted of independent functions being called in sequence. While this worked as intended, it lacked organization and scalability, so I decided to refactor it by creating a module with 2 classes, DishList
and ShoppingList
, along with their respective methods and related (but independent) functions:
- the
DishList
class creates a list of dishes as an object. It has the following methods:print_enum()
: printsDishList
as an enumerated listselect_dish()
: lets the user select a dish to be added to the shopping list
- the
ShoppingList
class creates a list of lists, consisting of an ingredient & measurement unit and a quantity. It has the following methods:get_ingredients()
: gets the ingredients of the selected dish from the database, adds them to the shopping list and prints them outunify_ingredients()
: checks the shopping list for items with the same ingredient and unifies these
- the
parse_string()
function replaces the abbreviation of measurement units with their full name, and also returns some other information crucial for text transformation (see the docstring for details) - the
print_formatted()
function transforms the database string to be printed in a user-friendly format
These classes can be viewed in planner.py
.
Developing the pantry checker functionality inspired a lot of restructuring, refactoring, splitting up functions and rethinking the logic. On the one hand, this significantly expanded the scope of the project compared to the MVP, but it had the added benefit of resulting in much cleaner and better code, on top of the actual functionality (which is very relevant to real-life usage).
- handle floats without upper bound
- shown measurement names instead of abbreviations
- remove ingredients with a quantity less than 0
- handle singular/plural quantities in the output message
A relatively easy piece of refactoring involved creating a general-purpose Y/N question validator function. This takes a single parameter, the prompt text to be shown in the input
block, and only accepts "Y" or "N" (and their lowercase versions) as valid inputs.
Because I am using extensive color effects in this project, the content of the prompt can be quite complicated when it includes the colorama
declarations.
Handling it as a string resulted in a lot of unintended errors and bad human readability. This is why I chose the prompt
parameter of this function to be defined as a single-item list instead of a string, which eliminated these errors.
The range validator function was much more labor intensive to generalize, as this also involved handling both integers and floats, ranges without an upper bound, and extensive customization of the output message.
The range validation function in its current form takes the following parameters:
prompt
- same function and usage as with the Y/N validatornum_type
- whether the input must be an integer or a floatlower
- the lower bound of the allowed range (inclusive)upper
- the upper bound of the allowed range (inclusive) - optional parameter, default is infinity
At the suggestion of my mentor and @nobe4, I split up my run.py
file further, and created 2 dedicated files:
utilities.py
containing general-purpose functions (the input validators andclear
)gsheet.py
containing all functionalities related to interfacing with Google Sheets.
Having worked in Technical Writing for nearly a decade, and with IT security teams for over half of that time, I have seen that proper and sufficient documentation not only helps development and collaboration, but also does a lot to eliminate the human error factor from security vulnerabilities. Furthermore, I have also learned that the "proper" and "sufficient" level of code documentation very much depends on the expertise of those working on said code.
Since this is my very first Python project after having started to learn the language just a few days before development started, I have consciously erred on the side of caution when documenting code. Each function has docstrings with parameter types and descriptions. In addition, individual lines are also commented with what exactly they do and why. This has proven to be very beneficial when refactoring code and modifying functionalities during the project.
- dataset from Google Sheets
- list of dishes
- shopping list
- human-readable version of shopping list
- human-readable version of ingredients
- dataset from Google Sheets
- list of dishes
- shopping list
- ingredients of a dish
- list of dishes
- shopping list quantities
- shopping list items
- items from the shopping list
The matrix of dishes, ingredients and quantities are stored in this Google Sheet.
A new test Google account was created for this purpose for security reasons, as I did not consider it good practice to use my real-life credentials for developing my first app of this kind, where the potential for human error is large.
However, the contents of the database are actually recipes I routinely use, so this app does not only simulate a real-life process, it actually is usable in real life.
The database was designed with extensibility in mind, and already contains data structures for features outdie the MVP:
The app interfaces with the database in a way that does not rely on the order of elements. This means that the Google worksheet remains usable, and can be modified or sorted without affecting the interaction with the app.
The crucial properties of the database in terms of compatibility with the app are only the following:
- dishes are in the second row
- ingredients are in the first column
- each ingredient cell contains
(
and)
- quantity cells only contain floats
Since using a terminal-based app can be daunting for people who are not used to it, I took extra care to structure and time the appearance of the information presented to the user.
I cleared the screen after each step, so that the user is not distracted by information no longer relevant to them.
I inserted pauses after confirmation messages, so that the user has enough time to read them.
I added emojis to confirmation messages to break up the monotony and aid visual recognition.
I used ASCII art to signify the beginning and end of the program.
The main colors chosen (magenta and green) harmonize with the background image of the site.
In addition, red is used for validation error messages.
However, the use of color in this project goes beyond aesthetic purposes: it also serves to aid the user experience, and is deliberately used to distinguish different functional elements from each other:
style | function |
---|---|
colored background | user input needed |
red background | user entered invalid data |
green text | information that the user needs to proceed |
magenta ASCII text | start and end of program |
In addition, important information within messages is highlighted with a contrasting color.
As this project is focused on Python rather than HTML/CSS, designing/altering the site itself was an optional extra.
Nevertheless, I chose to lightly alter the provided HTML/CSS code to make the deployed page more unique and appealing to users (as a terminal window on a plain white background is alienating to many people).
I chose a background image of a colorful dish of sweet potatoes, purple onions and thyme being prepared, to illustrate the joy and labor that goes into throwing a dinner party.
I changed the background color of the Run Program button to the purple color of the onions from the background image (with the help of ImageColorPicker). This color was chosen to harmonize with the image but still stand out from the rest of the elements on the page.
The color contrast with the white text was checked for accessibility/legibility (see Color contrasts for more details).
I have also made the button and the text on it larger and increased the font weight. To balance out these changes, I also increased the button width and its margin.
Finally, I added a hover cursor effect.
I horizontally centered all elements on the page and added some top margin for a more pleasing look.
I added a favicon showing a vector drawing of two wine glasses clinking, to symbolize the social nature of dinner parties. The color of the graphic is a darker shade of the purple color chosen for the button.
I added some SEO meta tags to the HTML file, so that the site can be found more easily.
I followed the American Pizza Order System project by Iasmina Pal in implementing the changes above.
I made some additional changes based on the American Pizza Order System project that I have decided to reroll: although the modified layout.html
file passed validation, the deployed page did not.
Click to see the details of the reroll
In addition to some styling in the head
element, the American Pizza Order System project
added the following code to @body
in layout.html
:
<body>
<main id="main-container">
<h1>AMERICAN PIZZA ORDER SYSTEM</h1>
@{body}
</main>
</body>
I had adopted this code, and also added a logo to the header and my name and a link to the GitHub repository.
I added the name of the app as an h1
element before the Run button in the template. For its background, I used the purple color of the onions from the background image (with the help of ImageColorPicker).
The heading text color is white, so that it provides sufficient contrast with the background (see Color contrasts for more details).
I put the image used for the favicon as a logo on each side of the header. Its design mirrors that of the box containing the header (box, border radius and shadow), but the colors are inverted.
I added my name and the link to the application's repository under the terminal window, using the colors already defined above.
Validating the resulting html
file in itself passed without errors.
However, running HTML validation for the deployed site, produced an error of there being multiple <body>
tags.
I have deduced that the reason for this is that the Code Institute template adds a <body>
tag as well, resulting in a duplicate.
Accordingly, I have rerolled the changes to layout.html
that involved adding html
elements, and kept the styling to modifications in the head
.
After this, both the modified layout.html
file and the the deployed site passed the W3C validator.
I changed the font used from Arial to Verdana. This font is considered the most legible of the popular web-safe fonts, especially for small screen sizes.
The start screen displays the name of the app in a large font styled with ASCII art, followed by the developer name and repository link, the instructions for use, and finally, the initial question asking the user if they want to start planning.
If the user answers "N" to the initial question, the corresponding confirmation message appears, before clearing the screen and showing the end screen.
If the user answers "Y" to the initial question, the corresponding confirmation message appears, before clearing the screen and starting the planning loop.
This shows the available dishes in an alphabetized list. The user can choose a dish by selecting the corresponding number.
Once the user selected a dish, a confirmation message appears showing the name of the dish.
The next screen shows the ingredients of the selected dish, followed by the question asking the user if they want to select another dish.
If the user answers "Y" to the previous question, the modified list of dishes is shown with the corresponding confirmation message. The new list does not contain dishes that have previously been selected.
The planning flow automatically ends when we run out of dishes to select.
In this case, the following message is shown:
If the user chooses not to check their pantry for ingredients, a confirmation message appears, followed by the shopping list.
If the user chooses to start the pantry tracking flow, the following confirmation message appears.
The program then goes through all the ingredients on the shopping list, and asks the user how much they have.
After all ingredients on the shopping list have been checked, the following confirmation message appears:
If the user has all the required ingredients in sufficient quantities, the following message appears:
This is followed by the end screen.
If there are items that the user needs to buy, the shopping list appears after checking the pantry.
After a short pause, the shopping list is followed by the end screen, to let the user know that the program has ended.
Because the "run program" button is outside the terminal, the end screen also includes instructions on how to start the program again.
There are several extra features and extensions planned for this project that were outside the MVP and were unrealistic to complete in the allotted time of 2 weeks.
They are viewable in GitHub Issues, including extensive mock code for possible implementation.
The WebAIM contrast checker was used to ensure that the text and background color of the heading provides sufficient contrast for legibility.
The colors used in the project are as follows:
color name | HEX code | Comment |
---|---|---|
white | #FFFFFF | |
onion-purple | #7A2F40 | |
thyme-green | #5A5A26 | Not used in the final version, but still visible in the screenshot here. |
The paired colors have the following contrasts:
color 1 | color 2 | contrast | WCAG AAA |
---|---|---|---|
onion-purple | white | 9.09:1 | ✅ |
thyme-green | white | 7.17:1 | ✅ |
- Python: to create the app
- Markdown: to create the documentation
- HTML/CSS: to modify the deployment template supplied by Code Institute
os
: used in the function that clears the terminaltime
: use thesleep
method to pause the running of the app, so that the user has time to process informationgspread
: get data from Google Sheetsgoogle.oauth2.service_account
: authenticate with Google Sheetsitertools
: get combinations from a listcolorama
: add color to the terminal text, both for design and UX purposes (see the section Color for more detail)pyfiglet
: display text using ASCII art
- Cloudconvert – Convert images to
webp
format - Favicon.io – create the favicon
- Git – version control
- GitHub – store the source files
- GitHub Desktop – GitHub UI
- GitHub Issues – feature management, bug tracking
- GitHub Projects – project management
- GitHub TOC generator – automatically generate a Markdown TOC
- GitHub web editor
- GitPod – Integrated Development Environment (only used for testing setup/requirements)
- Image color picker – color picker from image
- MacDown – Markdown editor
- Miro - create the flowchart
- Preview – cropping and annotating images
- PyCharm - code editor used for development
- PythonTutor – debugging Code
- Shields.io – add badges to README
- Slack – mentor communication
- Spck - editor & Git client for Android
- WebAIM – color contrast checking
The project was developed with Pycharm, using GitHub for version control.
The README was written with MacDown, Spck and GitHub web editor.
GitHub Projects and Issues were used to track project development, new features and bugs.
Significant changes to the codebase were developed on separate branches, which were deleted when the addition/change was completed and tested.
Create a worksheet in Google Sheets following this template.
Make sure to keep the following format:
- dishes are in the second row
- ingredients are in the first column
- each ingredient cell contains
(
and)
- quantity cells only contain floats
Follow the process described in the Love Sandwiches walkthrough project, helpfully replicated here.
You can fork the repository by following these steps:
- Log in to GitHub (if you don't have a GitHub account yet, you can create one for free).
- Navigate to the project website https://github.com/blahosyl/dinner-party.
- Click on Fork in the upper right part of the screen.
- On the next page you have the possibility to change the repository name. To do this, simply write your desired name in the text field in the center part of the screen. You can also leave the name as it is.
- Click Fork in the bottom right part of the screen.
Tip
If you do rename the repository, make sure to keep the GitHub naming conventions in mind.
- Create a list of requirements by going to the terminal and typing
pip3 freeze > requirements.txt
. This popuplates yourrequirements.txt
file with the list of required files.
Push your changes to GitHub. - Under Settings > Config Vars in Heroku, add a new var with the key
CREDS
and the value equal to the contents of yourcreds.json
file. - Under Settings > Config Vars in Heroku, add a new var with the key
PORT
with the keyPORT
and the value8000
. - Under Settings > Buildpacks in Heroku, add Python to Heroku Buildpacks.
- Under Settings > Buildpacks in Heroku, add NodeJS to Heroku Buildpacks.
- Under Deploy > Deployment method in Heroku, select GitHub and connect Heroku to your GitHub account.
Type in your repository name, then click Search. When your repository appears, click Connect next to it. - Under Deploy > Manual deploy in Heroku, select Deploy branch to deploy manually.
Once the process is finished, the following message will appear:
Your app was successfully deployed
Click View under the message, and a new tab will appear with your deployed app. - (optional) Under Deploy > Automatic deploy in Heroku, select Enable Automatic Deploys if you want your app to be rebuilt each time you push to the
main
branch of your GitHub repository.
See the document TESTING.md
for details.
The following resources were used to learn/double check general, atomic functionalities/syntax:
gspread
user guide- Copy list so it can be changed without affecting the original list
- Remove item from list
- Docstring conventions
- Get combinations of items from a list
itertools
package documentation- Add items to list
- Remove specified item from a list
- Check if a string is empty
- Convert
input
toint
withTry/Except
try
/except
- Errors and exceptions
- Input validation: integer in range (did not work)
- Insert a substring before/after a certain character in a string
- Delete a character from a string
- Fix
TERM environment variable not set
error - Clear the terminal
- Naming conventions
- Naming global vs. local variables
- Conventions for putting modules into separate files
- Classes of the same module can be grouped in the same file
- Print floats in general notation - only print decimal point and decimal place digits if they are "not empty"
- String operations
- Cut off last character of string
- Regular expressions for letters (not used)
partition
method examplepartition
method on W3Schoolscolorama
documentation- Escape special characters in ASCII art
- String literals with
r
- Web-safe fonts
- Verdana properties
- Not capitalizing "daiquiri"
- Not capitalizing "bloody mary"
pyfiglet
usage- Sort list of lists
- Problems with removing list items in a loop and some solutions (not used)
- Fix list comprehension error with indices (not used)
isnumeric
: only allow numeric values- Infinity
- Defining functions with optional arguments
The following sources contributed code or suggestions to specific functions within the project:
Rory Patrick Sheridan, my mentor, gave suggestions, helped me solve or spotted bugs described in these Issues (see the Issue descriptions and comments for details).
Modifying the layout.html
file in the Code Institute template to change some CSS styling was done following but also revising the method used in the American Pizza Order System project by
Iasmina Pal (see the section Rerolled design elements for details).
@nobe4 and Zerina Johansson helped me solve the issue of printing out the user input in a try/except
loop.
@nobe4 also gave me valuable advice on organizing code, and helped me solve the bug in displaying the correct user input in a try/except
block.
I am very grateful to Peter Litauszki who helped me set up GitHub and Spck on an Android tablet, so I could work on the README while on the go.
Finally, I would like to thank an enthusiastic alpha tester who wished to remain anonymous.
All text content was written by me.
Background image by me, converted to webp
with CloudConvert.
Image for the favicon and logo from Vecteezy, converted to png
format with Preview, converted to ico
format with Favicon.io, converted to webp
with CloudConvert.
- Creating your first README with Kera Cudmore by Code Institute
- Creating your first README by Kera Cudmore
- Bully Book Club by Kera Cudmore
- Bodelschwingher Hof by Ana Runje
- Travel World by Pedro Cristo
- Sourdough Bakes by Siobhan Gorman
- Horizon Photo by Rory Patrick Sheridan
- BackeStock by Amy Richardson
- American Pizza Order System by Iasmina Pal
- The README of my first Code Institute project
- The README of my second Code Institute project