Recipe Book is an online app designed to build a community of 'foodies' and home cooks who share recipes and join in conversations. All users have access to browse and view published recipes but users must be registered to interact with content, join conversations and submit their own recipes. Registered users can easily submit their recipes to the site via a recipe submission form and can return to edit their recipes or delete them from the site. Each recipe is displayed on the site for other users to access after the author has requested publishing and it has been accepted by admin. Recipes can also be saved to the 'My Recipe Book' section where registered users can access saved recipes more easily and also access recipes they have written.
The deployed website can be found here
- User Experience (UX)
- Agile Development
- Design
- Features
- Technologies Used
- Local Development and Deployment
- Testing
- Credits
- As a user I can use a simple navigation menu so that easily find content.
- As a user I can view the navigation menu on any screen sizes so that navigating the site remains easy on all my devices.
- As a User I can easily find social media links so that I can find and interact with a community of app users.
- As a Site User I can create recipes so that I can share them on the site.
- As a Site User I can publish and unpublish my recipes so that I can choose whether to share the recipe or not.
- As a Site User I can update/edit my recipes so that I can revise the recipe.
- As a Site User I can delete my created recipe so that I can remove the recipe from the site.
- As a Site Admin I can unpublish recipes so that manage content on the site.
- As a site user I can view a paginated list of recipes so that I can select one to read.
- As a Site User I can click on a recipe so that I can read the full recipe.
- As a Site User I can save recipes so that I can make a collection the recipes I like.
- As a Site User / Admin I can view the number of likes on each recipe so that I can see which is the most popular.
- As a Site User I can like or unlike another user's recipe so that I can interact with the content.
- As a Site User I can leave comments on a recipe so that I can be involved in the conversation.
- As a Site Admin I can approve or reject submitted user recipes so that I can manage the content of the site.
- As a user I can sign up for an account so that I can save, share and interact with site content.
- As a user I can easily sign in to my account so that I can easily use the site features on return visits.
- As a user I can easily log out of my account so that I can keep my account secure.
This project was developed using Agile methodology which allowed me to iteratively and incrementally build my app, with flexibility to make changes to my design throughout the entire development process.
GitHub Issues and Projects were used to manage the development process. Each part of the app is divided into Epics_ which are broken down into User Stories with Tasks. An Epic represents a large body of work, such as a feature. The board view of the Project feature was used to display and manage my progress in the form of a 'kanban board'. The user stories were added to the 'Todo' column to be prioritised for development, moved to the 'In Progress' column to indicate development of the feature had begun and finally moved to the 'Done' column when the feature had been implemented and the acceptance criteria had been met.
User stories were prioritised using the MoSCoW prioritisation technique. Each user story was given one of the following labels:
- Must have - to indicate the user story is guaranteed to be delivered.
- Should have - to indicate the user story would add significant value but is not vital.
- Could have - to indicate the user story would have a small impact if left out.
- Won't have - to indicate the user story is not a priority in the current iteration.
GitHub milestones were also used to group related user stories together.
The Recipe Book app is designed with a simple structure to ensure the app is easy to use and navigate. Each page has a consistent layout to allow users to easily find the information they need. The app has a responsive design to ensure it can be clearly viewed on a wide range of devices. The navigation menu is available on all pages of the app to provide users with a consistent method to navigate the site. Bootstrap rows and columns have been used to provide a clean and uniform structure to the content of each page.
The colour scheme was chosen to complement the colours in the images without causing distraction and provide contrast for good readability of the information. The colour palette was created using Coolors.
Google Fonts was used to add the following fonts:
- 'Roboto' was used to provide a simple, clean and easy to read appearance.
- 'Reenie Beanie' was used as a decorative font for the logo.
The images and logo were chosen to reflect the content site, and provide a simple appearance without causing distraction.
Icons were used as visual indicators for social media links, recipe likes, recipe saves, recipe preparation time and recipe servings.
All icons were sourced from Font Awesome.
The navigation menu is featured on all pages to provide a consistent means of navigating the site. The menu provides links to 'Home' page, 'Browse' page, 'My Recipe Book' page, 'Create Recipe' page, a login link when the user is unauthenticated and a logout link when the user is authenticated. It is fully responsive, collapsing into a navbar toggle button which presents the navigation menu as a dropdown menu. A navbar brand and image features on the left of the navbar, providing an additional link to the 'Home' page.
The 'Home' page features a hero section with a decorative image and a call to action which encourages unauthenticated users to sign up to the website or encourages authenticated users to browse recipes on the site. This page also features a section displaying the top three most liked recipes in a responsive column format as cards that are links to the full recipe details.
The 'Browse' page features a paginated list of all currently published recipes displayed as cards. Clicking anywhere on a recipe card will display the full recipe details. This page is displayed in a fully responsive column format. If there are no published recipes available for display, text reading 'There are no recipes yet' is displayed.
The 'Saved Recipes' page can be accessed by authenticated users only and displays a paginated list of all currently published recipes that the user has saved as cards. Clicking anywhere on a recipe card will display the full recipe details. This page is displayed in a fully responsive column format. If the user has no saved recipes, text reading 'You have not saved any recipes yet' is displayed. This page reuses the 'Browse' template. Unauthenticated users are redirected to the login page.
The 'My Recipes' page can be accessed by authenticated users only and displays a paginated list of all recipes written by the user as cards. Clicking anywhere on a recipe card will display the full recipe details. This page is displayed in a fully responsive column format. If the user has not written any recipes, text reading 'You have not written any recipes yet' is displayed. This page reuses the 'Browse' template. Unauthenticated users are redirected to the login page.
A recipe is displayed in full when the user clicks on a recipe card. The recipe image, title, author, number of likes, preparation time and number of servings are displayed in two columns on larger screens and one column on smaller screens. When viewed by authenticated user who is the author of the recipe, functionalities to edit and delete the recipe are available. An edit button will direct the user to create/edit recipe page which is populated with the current recipe details, where they can edit their recipe or cancel and return to the full recipe details page. A delete button will direct the user to a page asking the user to confirm they wish to delete the recipe, the recipe will be deleted from the database if the user confirms deletion or return to recipe details page if the users cancels deletion. When the recipe is viewed by an authenticated user who is not the author, functionalities to like and save the recipe are available. When the user likes the recipe the database is updated, along with the total number of likes on the page and the like button to confirm the action. When the recipe is saved the database is updated along with the save button to confirm the action.
If a recipe description was written by the recipe author it will be displayed.
The ingredients and method sections are displayed in two columns on larger screens and one column on smaller screens. The ingredients are displayed in a table format with a column for ingredient name and a column for ingredient amount, and the method is displayed as a numbered list.
Below the ingredients and method any tags entered by the author are displayed which are a link to the browse page where other recipes that contain the same tag are displayed. This feature allows users to more easily find similar recipes.
The commenting feature is also featured on the full recipe details page. Authenticated user are presented with a text input which can be used to submit a comment. Comments must be approved by admin before they will be displayed on the site. Upon submitting a comment a message is displayed inform the user that their comment has be successfully submitted and is awaiting approval. Approved comments for a recipe are displayed on the full recipe details page to all user of the site along with a total count of comments for the recipe.
A form in which authenticated user enter recipe details. The form consists of the following fields:
- a text input field for recipe title
- a text area for recipe description
- a choicefield for cooking time
- an integer field for number of servings
- a file upload field for an image, a default image is used if user does not upload an image
- a text input field for tags
- a multiwidget comprised of two text input fields for ingredient name and amount
- a text area for method steps
- a check box input for publication request
Django formsets and a jquery plugin called django-dynamic-formset are used for ingredient and method inputs giving the user the ability to add additional form inputs to add as many individual ingredients and method steps as needed.
The form features a submit button to submit a recipe and a cancel button to return to the 'browse' page without submitting the form. Upon successful form submission the user is returned to the 'My Recipes' page.
When editing a recipe the form fields are prepopulated with the existing recipe details ready to be edited by the user. Django's UserPassesTestMixin was used to limit access to logged-in users that pass a test to check that they are the author of the recipe. This ensures that only the recipe author can edit any recipe.
The app uses the Django Allauth package to handle user authentication and enable authenticated users to utilise the CRUD functionalities. The package provides a set of views and templates to handle user registration, login and logout. Defensive programming has been used throughout the site to prevent users accessing pages when they don't have the relevant permissions. To access the admin panel the user requires 'superuser' or 'staff status' permission status. Django's LoginRequired mixin is used to limit access to anonymous users and redirect them to the login page when they try to view content for which they require an account to access. Content is protected from unauthorised changes with the use of user_passes_test decorator, for example recipes can only be edited by the author of the recipe. This decorator causes unauthorised users to be redirected to a 403 error page informing the user they are not permitted to perform the action.
Django's admin panel can be accessed by 'superusers' and users with the permission of 'staff status'. The admin panel is used to manage site content by setting the approval status of recipes and comments. A list of all tags entered can also be viewed form the admin panel and can be deleted , edited and added to if needed. Each data model is registered with the admin using the register decorator so they are easily accessed and managed.
Custom error pages were created for the 400, 403, 404, and 500 errors. These pages give the user context of the error that has occurred. The navigation menu is still available to the user for continued app use.
The Recipe Book app uses a relational database to store and manage data. The relational database management system software used for this project is PostgreSQL which is hosted on the cloud service ElephantSQL.
Title - a CharField with a maximum of 50 characters and a help text reading 'Required, max length 50 characters.' which is displayed under the form input field.
Slug - an autopopulated field that uses django-autoslug, a django library that provides an improved slug field which can automatically populate itself from another field, preserve uniqueness of the value and use custom slugify() functions for better i18n.
Author - a ForeignKey linking the recipe to the user model of the user who created it.
Recipe_image - a CloudinaryField which contains the URL to the image that is stored on the Cloudinary server.
Tags - the tags field uses a django application called django-taggit which provides a tag model and taggeditem model. It uses the class TaggableManager() to manage the relationships between the recipe and the associated tags.
Description - a TextField with a maximum of 300 characters and a help text reading 'Optional, max length 300 characters.' which is displayed under the form input field. This field is not required.
Cooking_time - a CharField with a maximum of 50 charaters and a help text reading 'Select how long your recipe takes to prepare.' which is displayed under the form input field. The input is selected from the choices dropdown.
Serves - an IntegerField with a help text reading 'Enter how many people your recipes serves.' which is displayed under the form input field.
Ingredients - a JSONField. The ingredient input values are converted into a JSON string when the form is submitted.
Method - a JSONField. The method input values are converted into a JSON string when the form is submitted.
Created_on - a DateTimeField that autopopulates with the current date and time when a recipe is created.
Updated_on - a DateTimeField that autopopulates and updates with the current date and time when a recipe is updates.
Publish_request - a BooleanField which which is set to True when the user checks the 'Make Public' check box, defaults to false when box is left unchecked. A help text reading 'Check this box to submit your recipe for online publication.' is displayed under the form input.
Approval_status - a CharField with a maximum of 50 characters. Defaults to 'Unpublished' if the user does not check the 'Make Public' check box or defaults to 'Pending Approval' when the user oes check the 'Make Public' check box. The field is updated by admin to approve or reject the recipe.
Likes - a ManyToManyField linking user models to recipes that user has liked.
The comment model was taken from Code Institute's 'Codestar Blog'.
Recipe - a ForeignKey linking the comment to the recipe it is associated with.
Name - a CharField with a maximum of 150 characters. This field is populated with the user's username.
Body - a TextField to store the comment submitted.
Created_on - a DateTimeField that autopopulates with the current date and time when a comment is submitted.
Approved - a BooleanField that defaults to false and is updated to true by admin when the comment is approved.
Recipe - a ForeignKey referencing the recipe that has been saved.
User - a ForeignKey referencing the user that has saved the recipe.
Saved_on - a DateTimeField that autopopulates with the current date and time when the recipe was saved.
The Entity Relationship Diagram below shows the structure of the database and the relationships between the tables.
The app uses the Cloudinary cloud service to store static files such as images, CSS, and JavaScript files. To store the recipe images uploaded by user when creating a recipe, the Cloudinary field uses the Cloudinary API to upload the images to the Cloudinary server and store the image URL in the database.
- Add functionality for authenticated users to give recipes a star rating, this would give further quick and easy user interaction.
- Add search functionality to search for recipes by keyword. This would make it easier for user to find content they are interested in and improve user experience.
- Add a profile page feature for users to introduce themselves to the community and give authenticated users a place to share information about themselves.
- Django 3.2
- Bootstrap 5
- jQuery 3.7.1
- Font Awesome 5.15.4
- Google Fonts
- django-crispy-forms
- cripsy-bootstrap5
- django-allauth
- django-dynamic-formset
- django-autoslug
- django-taggit
- coverage
- Git
- GitHub
- CodeAnywhere
- Heroku
- ElephantSQL
- Cloudinary
- Balsamiq
- Lucidchart
- Coolors
- CloudConvert
- Tiny PNG
- Am I Responsive
- favicon.io
- The W3C Markup Validation Service
- The W3C CSS Validation Service
- Code Institute Python Linter
- JSHint
- Chrome DevTools
- Coverage
- Log in to GitHub.
- Go to the repository for this project (https://github.com/VictoriaParkes/recipe-book).
- In the top-right corner of the page, click "Fork".
- Under "Owner", select an owner for the repository from the dropdown menu.
- Optionally, in the "Description" field, type a description of your fork.
- To copy the main branch only, select the "Copy the main branch only" check box. If you do not select this option, all branches will be copied into the new fork.
- Click "Create fork"
- Log-in to GitHub.com, navigate to your fork of the repository.
- Above the list of files, click Code.
- Copy the URL for the repository.
- To clone the repository using HTTPS, under "Clone with HTTPS", click the "Copy" icon.
- To clone the repository using an SSH key, including a certificate issued by your organization's SSH certificate authority, click SSH, then click the "Copy" icon.
- To clone a repository using GitHub CLI, click Use GitHub CLI, then click the "Copy" icon.
- Open Git Bash
- Change the current working directory to the location where you want the cloned directory.
- Type git clone, and then paste the URL you copied earlier.
- Press Enter. Your local clone will be created.
For more details about forking and cloning a repository, please refer to GitHub documentation.
Use the pip install -r requirements.txt
command to install all of the Python modules and packages listed in your requirements.txt file.
- In your project workspace, create a file called env.py and make sure this file is included in the .gitignore file.
- Add the following code:
import os
os.environ["DATABASE_URL"]='<copiedURL>'
os.environ['SECRET_KEY'] = '<ADD YOUR SECRET KEY HERE>'
os.environ['CLOUDINARY_URL'] = '<API ENVIRONEMENT VARIABLE>'
- Replace
<ADD YOUR SECRET KEY HERE>
in the SECRET_KEY environment variable with your own secret key. - Save the file.
- Create an account and log in with ElephantSQL.com.
- From the dashboard click “Create New Instance”.
- Set up your plan
- Give your plan a Name
- Select a plan tier
- You can leave the Tags field blank
- Select “Select Region”
- Select a data center near you
- Then click “Review”
- Check your details are correct and then click “Create instance”
- Return to the ElephantSQL dashboard and click on the database instance name for this project
- In the URL section, click the copy icon to copy the database URL
- In your env.py file replace
<copiedURL>
in the DATABASE_URL environment variable with the copied URL. - Save the file.
- Create an account and log in with Cloudinary.com.
- In the dashboard copy your API Environment variable.
- In your env.py file replace
<API ENVIRONEMENT VARIABLE>
in the CLOUDINARY_URL environment variable with the copied API Environment variable. - Save the file.
- The requirements.txt file in the project was updated to include details on the project dependencies. Steps to do this are :
- Enter the following command at the terminal prompt : "pip3 freeze > requirements.txt"
- Commit changes to requirements.txt and push to GitHub.
- In
setting.py
, add Heroku Hostname to ALLOWED_HOSTS.
ALLOWED_HOSTS = ["PROJECT_NAME.herokuapp.com", "YOUR_HOSTNAME"]
- Make surea file named Procfile exists on the top level directory which contans the following code:
web: gunicorn PROJECT_NAME.wsgi
-
Commit changes and push to GitHub.
-
Log in to Heroku, create an account if necessary.
-
From the Heroku dashboard, click "Create new app". For a new account a button will be displayed on screen, if you already have one or more apps created a link to this function is located in the "New" dropdown menu at the top right of the screen.
-
On the Create New App page, enter a unique name for the application and select region. Then click Create app.
-
Select the "settings" tab and click the "Reveal Config Vars" button.
-
Enter the following values into the specified fields and click "Add":
KEY VALUE CLOUDINARY_URL paste your API Environment variable copied from the Cloudinary dashboard DATABASE_URL paste the URL copied from ElephantSQL dashboard SECRET_KEY paste your secret key -
Select the "Deploy" tab.
-
Select GitHub as the Deployment Method and click "Connect to GitHub".
-
Enter the name of your GitHub repository in the search bar and click "Search".
-
Click the "Connect" button to link your GitHub repository with your Heroku app.
-
Scroll down the page and choose to either Automatically Deploy each time changes are pushed to GitHub, or Manually deploy.
-
The application can be run from the Application Configuration page by clicking on the Open App button.
Chrome DevTools was frequently utilised in the development of the website to manipulate and test features as they were added to the project, to test responsiveness and for debugging purposes.
See Functionality Testing Document
The website was tested for functionality on different browsers (Chrome, Firefox and Edge) and found to be fully functional on them all.
The app was tested on the following devices, using Chrome DevTools:
- iPhone SE
- iPhone 12 Pro
- Pixel 5
- Samsung Galaxy S8+
- Samsung Galaxy S20 Ultra
- iPad Air
- iPad Mini
- Surface Pro 7
- Surface Duo
- Galaxy Fold
- Samsung Galaxy A51
- Nest Hub
- Nest Hub Max
- iPad
- iPadPro
The W3C Markup Validation Service was used to validate the HTML files.
The W3C CSS Validation Service was used to validate the CSS in the stylesheet style.css
.
The Code Institute Python Linter was used to validate and format the python files neatly. All errors were fixed and no errors were found in the final tests.
JSHint was used to validate the file script.js. All errors were fixed and no errors were found in the final tests.
Lighthouse audit reports were generated through Chrome DevTools to test the performance, accessibility, best practices and SEO of the website during the development of the website.
Lighthouse audit reports are as follows:
The automated tests were written using Django's built-in testing framework which use Python's unittest module. By default Django uses SQLite for the app database, this was used for development purposes and performing automated tests. The test files can be found in the app directory.
The coverage report was generated using the Coverage tool.
User Story | Acceptance Criteria | Status |
---|---|---|
As a site user I can view a paginated list of recipes so that I can select one to read. |
|
COMPLETE |
As a Site User I can click on a recipe so that I can read the full recipe. |
|
COMPLETE |
As a Site User / Admin I can view the number of likes on each recipe so that I can see which is the most popular. |
|
COMPLETE |
As a Site User I can save recipes so that I can make a collection the recipes I like. |
|
COMPLETE |
As a Site User I can like or unlike another user's recipe so that I can interact with the content. |
|
COMPLETE |
As a Site User I can leave comments on a recipe so that I can be involved in the conversation. |
|
COMPLETE |
As a Site User I can create recipes so that I can share them on the site. |
|
COMPLETE |
As a Site User I can publish and unpublish my recipes so that I can choose whether to share the recipe or not. |
|
COMPLETE |
As a Site User I can update/edit my recipes so that I can revise the recipe. |
|
COMPLETE |
As a Site User I can delete my created recipe so that I can remove the recipe from the site. |
|
COMPLETE |
As a Site Admin I can unpublish recipes so that manage content on the site. |
|
COMPLETE |
As a user I can use a simple navigation menu so that easily find content. |
|
COMPLETE |
As a user I can view the navigation menu on any screen sizes so that navigating the site remains easy on all my devices. |
|
COMPLETE |
As a User I can easily find social media links so that I can find and interact with a community of app users. |
|
COMPLETE |
As a user I can sign up for an account so that I can save, share and interact with site content. |
|
COMPLETE |
As a user I can easily sign in to my account so that I can easily use the site features on return visits. |
|
COMPLETE |
As a user I can easily log out of my account so that I can keep my account secure. |
|
COMPLETE |
As a Site Admin I can approve or reject submitted user recipes so that I can manage the content of the site. |
|
COMPLETE |
- When viewing recipe details as anonymous user, TypeError received ('AnonymousUser' object not iterable). Added 'if' statement to view to check if user is authenticated before querying the 'Saves' database for matching user.
- User images were not being submitted, added class
enctype="multipart/form-data"
to form to allow files to be uploaded. - Refactored my recipe book pages to make paginating and redirecting to 'my recipes' after creating, editing and deleting recipes easier.
- Removing all ingredients or method formset from recipe form submitted empty list for model instance, added clause to save recipe but not submit for publication if ingredients or method not entered.
- Visiting the admin panel using the admin url without a trailing slash resulted in the return of a 404 error. This was caused by the application attempting to match the given url to the recipe details url pattern which uses the slug of the selected recipe. The error was fixed by using
recipe/<slug:slug>
in the recipe detail url pattern instead of<slug:slug>
.
Django Docs, W3Schools and Bootstrap Docs were frequently referred to in the development of this website:
- Code from Code Institute's CodeStar blog walkthrough project was used and modified to create the commenting feature.
- Forms were styled using django-crispy-forms docs.
- The multiwidget and multivalue fields in the recipe form were created using Python – Django: MultiValueField and MultiWidget and Handling multiple input values for single Django form field.
- The dynamic functionality of the formsets in to recipe form were created using Adding forms dynamically to a Django formset and django-dynamic-formset.
- Extra context and querysets were added to views using When to use get, get_queryset, get_context_data in Django?.
- Delete success message added using Stack Overflow.
- Automated tests to assert queryset equality were created using Testing Equality of Django QuerySets: A Guide.
- The custom error pages were created using advice from my mentor Brian Macharia.
- The user images failing to upload bug was fixed using W3Schools.
- The admin panel 404 error was fixed using Stack Overflow.
The recipes featured on the app were sourced from BBC Good Food.
The images featured on the app were sourced from rawpixel:
I would like to thank Brian Macharia, my Code Institute mentor, for his helpful feedback and advice.