Welcome to CheesyStore, an online cheesemonger based in Ireland. Our website offers a handpicked selection of the finest cheeses from Ireland and around the world. CheesyStore goes beyond just selling cheese. Alongside the shop there is a blog featuring pairing guides, the stories behind our products, and the latest news from the dairy world. These insights, along with special offers, are shared across various social media platforms to keep our community engaged and informed.
CheesyStore is a B2C e-commerce store and blog. The site is targeted towards potential customers both in Ireland and internationally. The payment system uses Stripe.
Live Website here: Cheesy Store
Link to Main Project Board: Project Board
This web application is a submission for Portfolio Project 5, with a focus on e-commerce. The project required a concept that could process payments using Stripe and be marketable on social media platforms. The chosen concept is an online cheesemonger, which offers a variety of cheese products. The website is designed to cater to a wide audience and is structured for easy categorization of products.
To enhance marketing efforts and attract customers, the website includes a blog. The blog allows the site owners to publish posts related to cheese products, history, and culture. This feature aims to engage visitors and provide additional value beyond just e-commerce.
The current version of the website is a Minimum Viable Product (MVP), developed within the project's deadline constraints. Future expansions are planned to include features such as recipes, pairing guides, and an integrated chatbot using the ChatGPT API. These additions are intended to further enhance the user experience and the website's offerings.
User Stories are tested in: TESTING.md
Link to User Stories + Tasks: User Stories Board
This project does not have a specific color scheme. The header and footer feature a black background with white text. To maintain a clean appearance, especially due to the numerous images associated with products and blog posts, the design predominantly employs a monochrome palette. Most pages on the website display a background image, with the exception of the store and checkout pages. To avoid clutter on these pages, a white overlay has been applied. On pages where the overlay has not been applied, the content of the page rests on white card divs.
CheesyStore contains a background image that is present on most of the pages on the site. It is from Pexels.com and it is called "Slice Cheese, Biscuits and Bowl of Fruits". I chose it because the centre of the image is blank which would have been nice for a call to action. A link can be found here.
For CheesyStore, I selected the Rubik font due to its versatility. Being a sans-serif typeface, it's excellent for headings and titles. For body text or more extensive sections of text, I opted for the Lora font. Its readability paired well with Rubik so it was an ideal choice.
Wireframes were created at the start of the project to establish the website's structure. The initial framework has largely stayed consistent throughout the development process. These wireframes were used as a reference for the design, ensuring that it did not stray significantly from the intended direction. They were also designed with responsiveness in mind, knowing that Bootstrap could be used to adapt the layout for smaller screens.
Although there were some deviations from the original design, such as not including an "About Us" section, I made efforts to adhere to the initial wireframes as closely as possible. The wireframes proved to be valuable, providing a solid foundation for the design. They helped prevent excessive deviation during the styling process, which often led to cluttered HTML and CSS.
In the repository for CheesyStore, I initiated the project by creating sixteen User Story issues. Subsequently, I generated corresponding Tasks for each User Story. Three different project boards were created, and the User Stories were imported into each.
The main project board comprises three columns: to do, in progress, and completed. As development advanced, I transitioned User Stories from one column to another, aiding in tracking the project's progress.
A second project board was created, incorporating both User Stories and Tasks. This board was designed to provide an overview of the tasks associated with each User Story, with each User Story and its corresponding Task placed in a specific column.
A third project board was created to prioritize User Stories using the MoSCoW Prioritization method. This board featured columns for Must-Have, Should-Have, Could-Have, and Won't-Have, facilitating the prioritization of development tasks.
Link to Main Project Board: Project Board
Link to User Stories + Tasks: User Stories Board
Link to MoSCoW Prioritization Board: MoSCoW Board
User Stories are tested in: TESTING.md
CheesyStore has four app folders with models. The User model is handled by Django AllAuth.
This app contains five models. These models are for the products, the categories for the products and the rating of the products.
The Product model contains the information to store details of the product for sale. The origin, category, cheesetype and rating fields are linked with foreign keys to the others models in this folder in a one-to-many relationship. The rating model is linked to the Product model but in a many-to-one relationship.
The main Category model is for the main category to place the product into:
The subcategory Origin model is for where the cheese comes from:
The second subcategory CheeseType is for the type of cheese it is:
The Rating model holds the User rating for the specific product.
This app contains two models. These models are the blog posts and the comments on the web applications blog. There is a many-to-one relationship between post and user, comment and post, and comment and user.
The Post model is for the blog posts themselves to be written by a superuser or through django-admin:
The Comment model is the comments on the blog posts. It is linked to the Post model and User model through foreign keys:
This app contains two models. These models are for the Order and Order Line Items - or the products themselves in the order. There is a one-to-many relationship from UserProfile to Order, and Order to OrderLineItem. There is a many-to-one relationship between Product and OrderLineItem.
The Order model contains the information for each specific order including user, address, contact details and order number:
The OrderLineItem model contains the products and the quantity of each in each order:
This app contains the information for each User profile. The User model itself is handled by Django AllAuth but it is extended here to aid the creation of profiles for each user:
The UserProfile model contains information to display for each registered user. There is a one-to-one relationship between UserProfile and User.
The Contact model contains information to display to the admin. It is a standalone model. Every time someone fills out the contact form, an email is sent and the message saved in the database.
I created this Database Diagram Model with DrawSQL.
Superusers can add new products to the store via the "Product Management" link in the Navbar. This feature is only for administrators, allowing them to update the shop's inventory. The "Product Management" link is visible only to admins.
Registered users have the ability to post comments on blog entries. Administrators can create new blog posts through the Django admin panel.
Administrators, once logged in, will notice a small "delete" button beneath each product listing on the "Products" page and the "Product Detail" page. Clicking this button triggers a form with text beneath the button to confirm the deletion of the product.
Similarly, registered users will see a "delete" button below their comments. Clicking this button triggers a form with text beneath the button to confirm the deletion of the comment.
Django All Auth is used for backend authentication:
On larger screens, the navigation bar contains tabs aligned to the right and aligned to the left. On the right, there is an icon for the store which serves as link to the homepage. It is followed by links to three dropdown icons for Products, Categories and More. By clicking Product we find links All Products, Cheeses and Other Products. By clicking Categories we find links the products in the store categorized by Cheese Type and Origin. By clicking More we find links to the FAQ, Contact and Blog Page.
On the left of the navigation bar, two dropdown icons and a link. The two dropdown links are for Search and My Account. By clicking Search, a dropdown appears with a Form to search the products in the store. By clicking My Account, links to login, register, sign-out and my profile appear. A further Product Management link appears if the user is a Superuser. The final link is a link to the Bag page contains products selected to purchase.
On smaller screens, the Navigation bar responds by turning the Icon into another Dropdown Menu. Remaining on the mobile Navigation bar are Search and Bag icons which have the same functionality as on Desktop. The CheesyStore Icon Dropdown menu contains all the other links from the Desktop navigation bar including a Home and My Account link.
The Homepage is structured into three sections. The top section features a "Welcome" message alongside a brief description of the Store, accompanied by a "Shop Now" button designed as a prompt for visitors to explore the store further. The middle section displays three thumbnails, each linking to one of the three most recent blog posts. The inclusion of a new blog post dynamically refreshes this section to showcase the latest content. On devices with smaller screens, the display adjusts to highlight just a single post. The bottom section is divided into two parts: the first part highlights the most recent addition to the store's product range, and the second part provides a subscription form for the mailing list. When viewed on smaller screens, these parts stack vertically.
The products page layout remains consistent across all product categories, with products organized in rows. The number of products displayed per row varies with screen size: six products are shown per row on the largest screens, while only one product is displayed on the smallest screens. Each product card features an image, name, price, origin, cheese type, and rating. For administrators, additional "Edit" and "Delete" buttons are available for each product for easy management. The content is divided into multiple pages through pagination.
The product details page is designed with two-columns. On one side, there's a large image of the featured product. The opposite column displays the same product information found on the main store page, including specifics like name, price, origin, cheese type, and rating, along with a short product description. For users who are logged in, there's an option to rate the product with up to five stars. Below the rating, a quantity selector allows users to adjust how many units they wish to purchase, using plus and minus buttons for adjustment. The page concludes with two action buttons: one to continue shopping and another to add the selected product quantity to the shopping bag. On smaller screen sizes, the large image of the product will not display.
The Blog section mirrors the store's design, organizing blog posts into rows of cards. Each card displays a featured image and an excerpt from the blog post, as well as the time and date it was submitted. To facilitate easier navigation, the blog pages are also paginated. On smaller screen sizes, the blog posts will display as a single column.
The layout for viewing individual blog posts is designed similarly to the product details page. The first part is divided into two columns: one for the main text of the blog post and another for the featured image. On smaller screens, the featured image is designed to be hidden for viewing space. The second part of the layout is dedicated to user interactions, specifically comments. It's split into two sections: one displaying existing comments and the other featuring a form for submitting new comments. Beneath each comment, "Edit" and "Delete" buttons are provided, visible only to the comment's author. On smaller screens, these two sections stack vertically.
The bag page features a table listing all the items added by the user. Each product has a separate row within the table, with columns to display the product's image, title, price, quantity, and subtotal. The quantity column includes a form for each product, allowing users to either remove the product or adjust its quantity. The final row of the table presents the grand total of the items in the bag, accompanied by two links: one to "Keep Shopping" and another to "Continue to Checkout." For better readability on smaller screens, the table layout shifts to a single-column, where each section presents the product's image, details, and the quantity adjustment form.
The checkout page is divided into two main sections. On the left side, there is a summary of the order, providing an overview of the items selected for purchase. On the right side, users are presented with a form to enter their personal and shipping details, as well as a separate section designated for entering payment information i.e card details. For users who are logged in, the form fields for personal and shipping information are automatically populated with their stored details, streamlining the checkout process. These two sections stack vertically on smaller screen sizes.
The Frequently Asked Questions (FAQ) Page is organized with a single, centrally aligned column that groups each question with its relevant answer. This layout facilitates easy navigation and readability, allowing users to quickly find the information they need.
The "Contact Us" page is split into two distinct sections. The first section features a form where users can fill out their name, email, and the message they wish to send to the administrators for various purposes. The second section includes the subscription form that is also found on the homepage, allowing users to subscribe to newsletters or updates directly from the "Contact Us" page. This dual-section setup serves both communication and engagement purposes on the same page.
The success page displays an order summary for the user, providing details about their purchase. This summary includes the order number, the date of the order, and information regarding the shipping details. This layout ensures users have a clear confirmation of their transaction, including when and where their order will be shipped.
The Product Management page features a comprehensive form designed for entering the details of new products, including an option to upload an image. The form incorporates dropdown menus, allowing for the selection of categories under which the new products will be placed. This setup facilitates organized product addition, ensuring each item is correctly categorized for easy browsing and management.
The profile page is divided into two main sections to enhance user experience. The first section lists all orders associated with the user. Each listed order includes a link to the relevant checkout success page. The second section of the profile page presents a form for updating the Default Delivery Information. This form includes fields for users to input or amend their standard shipping details.
The Sign In page features a straightforward design, with a form that's centrally aligned for ease of use. This form contains fields for entering a Login and Password. Pages for registration, password recovery, and displaying errors follow a similar design ethos, ensuring a consistent and user-friendly experience.
In the footer a link to the store's Privacy Policy is aligned to the right. In the center, social media links are displayed, offering a way for users to connect with the store. Also rightly aligned is a link to the Contact Page. On smaller screen sizes, the design simplifies to display only the social media links.
I want to use ChatGPT API to create a chatbot for users of my store to use to help make purchases and navigate the store. I initially made a User Story that remains unfinished: As a potential customer, I want to interact with a chatbot on the cheese-selling website so that I can receive personalized recommendations, get answers to my questions about the products, and have assistance throughout the checkout process, making my shopping experience smoother and more enjoyable.
In the future, I will add more categorizations to the cheese. Part of the reason I decided on a cheesemonger was due to the many ways you could categories cheese. I will remove Goats cheese as a cheese type and created a dairy type category. I could create a texture model.
I want to add a subscription service whereby someone can subscribe monthly for a cheese board. The images for the cheeseboards are already in the media folder. I could implement Stripe to take monthly payments. Stripe has code to implement for this purpose. This was a user story from the start: As a user, I want to subscribe to a monthly cheese subscription service so that I can discover new cheeses and have a consistent supply of high-quality cheese without having to reorder manually each time.
I want to introduce a loyalty scheme. It was a User Story from the beginning: As a user, I want to participate in a loyalty program that rewards me for my purchases so that I can enjoy discounts, get early access to new products, and access exclusive content. This would reward users and incentivize them to return. I got into further detail in TESTING.md User Story testing section.
I would like to spend more time improving the sites design. It could be improved and it is something I will work on after submission. For example, when a user rates an item, the rating could automatically update. At the moment, the pages needs to be refreshed before it updates.
Another example is for Editing a comment. Similar to how a paragraph appears on the screen asking to confirm deletion, I would like a form to appear to edit the comment rather than having to edit the comment on an external page.
The user profile section could be updated. I want users who have placed an order to leave or a review or testimonial. I considered implementing it before submission but left it aside as what needed to be submitted was a minimum viable product and I considered a testimonial page to be not necessary for the deadline.
Cheesy Store's Business Model is Business to Consumer (B2C). Products are sold directly from Cheesy Store to consumers who are the end-users.
A customer of Cheesy Store would be someone who is most likely an adult who is interested in cheese or cheese culture. The blog can potentially attract potential customers who may be interested in it's content. From there products can be marketed and the customer can be redirected to the store.
I chose a mixture of short-tail and long-tail keywords. I struggled to only pick ten. There are many permutations regarding keywords so I chose a mixture of all sustainable, organic, gourmet and artisan.
I used Google search to check Search results for other online cheese shops keywords. I also utilized WorkTracker and other word tracking services. Some screenshots are below. I learned that Sheridan's have the Irish cheese market cornered and run a monopoly for Irish cheese.
Cheese | Cheesemonger | Cheese Shop |
---|---|---|
Irish Cheese | Irish Cheesemonger | Irish Cheese Shop |
Sustainable Irish Cheese | Sustainable Irish Cheesemonger | Sustainable Irish Cheese Shop |
Organic Irish Cheese | Organic Irish Cheesemonger | Organic Irish Cheese Shop |
Gourmet Irish Cheese | Gourmet Irish Cheesemonger | Gourmet Irish Cheese Shop |
Artisan Cheese | Artisan Irish Cheesemonger | Artisan Irish Cheese Shop |
Buy Artisan Cheese | Online Artisan Irish Cheesemonger | Online Artisan Irish Cheese Shop |
Award-Winning Irish Cheese | ||
Small-batch Irish cheese | ||
Rare Irish cheese |
A blog can attract users to a website with interesting information. The blog on this website will attract people with an interest in cheese. The admins who make the posts can enter in a comma-separated string with keywords for the blog post. The keywords serve as tags which are rendered beneath the blog post. They are also rendered in the meta tag of base.html so these keywords are rendered in the metatag of the relevant blog posts. I created a blogtags.py to separate the string for the tags.
To enhance search engine optimization (SEO) and ensure search engines can effectively understand and navigate the site's structure, a sitemap file was created. This file, generated using xml-sitemaps.com, includes a list of important page URLs.
Additionally, a robots.txt file was established to guide search engines on which areas of the website they are restricted from accessing. This measure helps improve the site's SEO by focusing search engine crawlers on the most relevant and valuable content, thereby enhancing the quality of the site in search engine rankings.
For this site, a Facebook business page has been created for social media marketing. The Facebook page includes a 'Contact Us' button which takes the user to the Contact Page on Cheesy Store. The Facebook page link is included in the footer so appear extended from base.html to every webpage.
Users and visitors to the website can sign up to the newsletter. They do not need an account to do so. A sign-up box is present on the homepage and the contact page. This allows the business to stay in touch with customers. The newsletter can contain anything from special offers to new arrivals. Mailchimp was used for this purpose.
Full testing: TESTING.md
To deploy this page to Heroku from its GitHub repository, the following steps were taken:
pip3 install dj_databse_url
pip3 install psycopg2-binary
pip3 freeze --local > requirements.txt
DATABASES = {
'default': dj_database_url.parse(os.environ.get('DATABASE_URL'))
}
if 'DATABASE_URL' in os.environ:
DATABASES = {
'default': dj_database_url.parse(os.environ.get('DATABASE_URL'))
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Create requirements.txt file by typing pip3 freeze --local > requirements.txt
Create a file named "Procfile" in the main directory and add the following: web: gunicorn project-name.wsgi:application
Add Heroku to the ALLOWED_HOSTS list in settings.py in the format ['app_name.heroku.com', 'localhost']
Push these changes to Github.
Add the following Config Vars in Heroku:
Variable name | Value/where to find value |
---|---|
AWS_ACCESS_KEY_ID | AWS CSV file |
AWS_S3_REGION_NAME | eu-west-1 |
AWS_SECRET_ACCESS_KEY | AWS CSV file |
AWS_STORAGE_BUCKET_NAME | cheesystore |
DATABASE_URL | Postgres generated (as per step above) |
EMAIL_HOST_PASS | Password from email client |
EMAIL_HOST_USER | Site's email address |
PORT | 8000 |
SECRET_KEY | Random key generated as above |
STRIPE_PUBLIC_KEY | Stripe Dashboard > Developers tab > API Keys > Publishable key |
STRIPE_SECRET_KEY | Stripe Dashboard > Developers tab > API Keys > Secret key |
STRIPE_WH_SECRET | Stripe Dashboard > Developers tab > Webhooks > site endpoint > Signing secret |
USE_AWS | True (when AWS set up - instructions below) |
The site is now live and operational.
[
{
"AllowedHeaders": [
"Authorization"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
"Resource": [
"YOUR-ARN-NO-HERE",
"YOUR-ARN-NO-HERE/*"
]
pip3 install boto3
pip3 install django-storages
pip3 freeze --local > requirements.txt
and add storages to your installed apps.if 'USE_AWS' in os.environ:
AWS_STORAGE_BUCKET_NAME = 'insert-your-bucket-name-here'
AWS_S3_REGION_NAME = 'insert-your-region-here'
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
Then add the line
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
to tell django where our static files will be coming from in production.Create a file called custom storages and import both our settings from django.con as well as the s3boto3 storage class from django storages.
Create the following classes:
class StaticStorage(S3Boto3Storage):
location = settings.STATICFILES_LOCATION
class MediaStorage(S3Boto3Storage):
location = settings.MEDIAFILES_LOCATION
STATICFILES_STORAGE = 'custom_storages.StaticStorage'
STATICFILES_LOCATION = 'static'
DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage'
MEDIAFILES_LOCATION = 'media'
STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{STATICFILES_LOCATION}/'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIAFILES_LOCATION}/'
and then add the following at the top of the if statement:
AWS_S3_OBJECT_PARAMETERS = {
'Expires': 'Thu, 31 Dec 2099 20:00:00 GMT',
'CacheControl': 'max-age=94608000',
}
Go to S3, go to your bucket and click 'Create folder'. Name the folder 'media' and click 'Save'.
Inside the folder, click 'Upload', 'Add files', and then select all the images that you are using for your site.
Then under 'Permissions' select the option 'Grant public-read access' and click upload.
Your static files and media files should be automatically linked from django to your S3 bucket.
To clone this repository follow the below steps: