- This project aims to build a full-stack web application using the python micro-framework Flask, MongoDB database, Tailwind CSS & JavaScript
- The app is a photo sharing app where dog owners can post photos of their dogs, or dog lovers can visit and upvote/favourite photos of dogs uploaded to the site
- This application features a main photo gallery section, an upvote/like feature, user signup and login and customiseable user accounts.
- User experience
- Database Models and Schema
- Design
- Features
- Technologies used.
- Testing
- Deployment
- Credits
Table of contents generated with markdown-toc
-
New Visitor
- I want to see the content of the website without being forced to register first
- I would like to easily register for the site.
- I would like to be able to like and favourite photos on the site and easily understand how this system works
- I would like to be able to comment on photos
- I would like to add my own dogs to the site
-
Repeat users
- I would like to easily login upon opening the site.
- I would like to see my saved favourites and previous uploads
- I would like to be able to edit/delete my comments
- I would like to edit and delete my posts.
- I would like to be able to edit and delete my account
-
All users.
- I would like to get feedback when I have completed an action on the site.
- I would like to be able to contact the website owners
-
Website owner
- I want the website to be fun and enjoyable for the user
- I want the user to be able to sign up or log in easily
- I want users to be able to easily recover their account if they have lost their login details
- I only want users to be able to edit/delete their own content
- I want the ability as admin to delete/edit any content that is inappropriate
- Mobile Wireframes
- Desktop Wireframes
- (Tablet design planned to follow same layout as mobile)
There were some changes from the initial wireframes, some of which were stylistic, some due to learning more about the technologies being used create the app, and some due to reducing complexity to achieve a minimum viable product.
- On mobile, a bottom navigation bar was implemented when users were logged in
- Pagination buttons were replaced with clickable page numbers rather than 'Previous' and 'Next' buttons
- The original aim of the app was to have three categories for the main gallery, similar to a reddit style ranking system
- Hot: Dogs with the most recent upvotes
- New: Most recently uploaded dogs
- Top: Dogs with the most all-time upvotes Due to certain complexities involved in this, particularly taking into account that the app may not have regular uploads, to achieve a minimum viable product I decided to go with just 'Hot' (most liked of all time) and 'New' (newly uploaded dogs) for the app.
- Similarly, in order to achieve a MVP, breed selection was kept to one select item, with an option for mixed-breed included in the options.
- Images were used for some form pages, but not for the login/register page as outlined in the app which were kept clean
- In desktop view, 'My Dogs' and 'Favourites' were originally positioned side by side, however in practice - as a user would likely have much more favourites than uploads, to these were kept one above the other is it provided a more balanced layout.
- The database consists of four collections - User, Breed, Dog, Comment
- User
- This contains user's username, email address, hashed password string and avatar selection
- The user ID acts as a reference field in various other collections
- Breed
- This acts as a reference field for the Dog collection and contains a lists of breeds that users can choose from when uploading their dog
- Dog
- This contains the basic dog information; name, about section, upload date
- Dog breed is referenced as noted above
- Dog owner referenced User ID and is automatically set to the user that uploads the Dog item
- liked_by and faved_by are both one to many reference field lists, referencing IDs of Users that have favourited or liked a dog
- img_url is the users uploaded image of a dog
- img_url_card is the above image with transformations setting height to 350px, width to 525px, low quality on the image and automatically focusing on key part of image using cloudinary API
- img_card_thumb is also optimized to lower quality and limited to a width of 500px (height remains at auto)
- upload_date is an automatically added datetime item
- Comment
- autor references the User.id that created the comment
- dog references the Dog.id of the dog page the comment is attached to
- content is text input of comment and datetime is another automatically added datetime item
-
mongodb was used as the project database.
-
I followed the following steps to set it up.
- signed up to Mongodb and created a shared cluster
- selected default AWS cloud provider
- selected Ireland region
- selected m0 cluster tier
- chose name for cluster
- once cluster was created I clicked 'CONNECT' button
- Selected 'connect your application'
- selected Python / 3.6 or later as my driver
- copied the connection string for use in my application
- set password / cluster name / collection name as enviuronmental variables to connect to my DB within flask
- used Flask-MongoEngine to interact with my DB within the app
- The app's primary color was based on the pawprint logo colour found on the mobile phone in the main landing page image (#cf5551)
- This colour was darkened slightly for use as the primary colour in the app to ensure there was proper contrast on all text/buttons
- I then used the Adobe color wheel to find a complimentary colour that also offered good contrast for white backgrounds and text
- Different shades of the primary and secondary colours were created with Tailwind Shades and added via the
tailwind.config.js
- For borders I used a mid-light gray (Tailwind color gray-400) matching the original design in the wireframes
- For the bottom navbar and footer I used a dark charcoal gray (Tailwind color gray-700)
I used three fonts throughout the website. A display font on the welcome page, and the traditional pairing of a serif heading font with sans serif body text font.
- Landing Page Title
- For the landing page I chose Lobster, a fun and recognizable display font that paired nicely with the main sans font underneath
- Headings
- Headings throughout the site use Crete Round, a contemporary serif font
- Body font
- All other text and paragraphs are using Lato a modern and popular sans-serif font
- If user's are not signed in, then they are greeted with a fun landing page
- There are clear links to the main gallery and login & register pages
- Landing page redirects to the main gallery if users are already signed in
- The footer is viewable on scroll, but initially hidden off screen to focus the user on the main landing page design and image
- Gallery page shows six uploaded dogs per page
- Pagination buttons are below pictures to easily navigate between pages
- Options to view highest rated or newly uploaded dogs
- Default view is to show the most popular dogs
- Clear distinction between 'Hot' and 'New' depending on which page the user is on
- There is a different wallpaper depending on whether yo are on Hot or New
- Each dog card on the gallery page contains links to:
- Like the dog
- Favourite the dog
- See the dog's main page with larger picture
- Link to the person who uploaded the dog (if you are not the uploader)
- Links to delete or edit the dog, if you are the uploader
- There is a large button at the bottom of the gallery to upload a new dog
- There are two different navigations, depending on whether you are on mobile or tablet+
- On mobile there is a bottom navigation bar, making it easy to access the main links with your thumb (visible once the user has signed in)
- The top mobile navigation simply consists of a 'My Profile' button (or Login/Register buttons if a user has not yet signed in)
- On desktop, the links that are present in the mobile bottom navigation are instead added alongside the 'My Profile' button in the top navigation bar
- If user is not logged in:
- Form has fillable Name/Email/Message fields
- If user is logged in
- Form has read-only, pre-filled username/email fields for current user
- Has fillable message form
- All messages sent from contact form send messages to my own email
- Both the Register and Login pages have a simple, clean form asking for username / password
- Register form asks for email and password confirmation
- Both pages link to eachother if user is already registered / not regisatered yet
- Both pages link to contact page
- Login page links to a password reset page, if user has forgotten their login password
- Request page features a single email field for users to request a password reset link
- Feedback is given to check your email regardless of whether it is a registered email or not for security reasons (not identifying whether an email is regisatered or not)
- If it is a registered email, a link with a JWT token is sent to the user's email
- This link leads to a Password Change form with two password fields to type and confirm the users new password
- Displays this user's chosen avatar
- Displays any dogs this user has uploaded
- Displays any dogs this user has favourited
- If this user is the current user, it presents links to:
- Change your avatar
- Here you can choose from 16 dog themed user avatars
- Edit your account
- Here you can change your username or email
- Delete your account
- Presents a screen to confirm your password in order to delete your account, along with any of your uploaded dogs/comments
- Change your avatar
- Displays a larger thumbnail image of dog, without cropping to card aspect ratio
- This image features a link to see original, user upload full-size image
- Display's dog info: name, breed, upload date, owner and about section
- If current user is dog's owner, then displays buttons to edit or delete dog
- Edit dog will load a form with any information pre-filled
- Uploading a new profile picture will replace the previous picture in the Hot Dogz cloudinary database
- Delete dog will ask user's to confirm before deletion
- Otherwise displays text prompting current user to leave a comment
- Displays avatar of comment author and name, both of which act as a link to their user page
- Displays comment content and date comment was added
- If current user is author, it displays edit and delete comment buttons
- Edit comment will navigate to a text entry, pre-filled witih the comment user is editing
- Delete comment will display the comment text as a blockquote and ask user to confirm delete
- There is an admin user account which has permissions to delete or edit any user, dog or comment on the site
- A preview of the admin account on the gallery home page will show edit/delete options for all uploaded dogs, which are normally hidden unless dog owner is the current user:
- Links for deleting/editing comments and users are also shown in this way for admins.
- I have created an error pages using the flask app_error handlers
- Each page contains a reason for the error and a navigation link to return to the previous page, along with a humourous image of a sad bulldog in a dress. Errors are:
- 404 - not found an error
- 403 - no permission error
- 500 - server error.
- Creating a new view called 'Top' which replaces the current implementation of 'Hot', and instead having 'Hot' display recently upvoted / trending dog photos
- Filtering dogs by breed (would be most useful if the site was popular and there was a lot of posts)
- Possibly future improvement would be to separate the front end and the back end by having Flask act as an API and use a JavaScript framework for the front end. This would have reactivity benefits, such as the page not having to refresh every time a user likes/favourites a photo.
- The application was built on the Flask framework.
- I followed Corey Schafer's guide on YouTube in using Flask Blueprints to split my application up itno the following modules:
- main
- users
- dogs
- errors
- This makes it easier to find routes and elements if they need to be updated or changed.
- The templates folder is also split into a similar structure.
- base.html
- /main
- /user
- /dog
- /errors
- App configuration settings are contained within
config.py
- Inside the
__init__.py
file I've created the app as a Flask application factory. - Then in the
app.py
file, the application factory is imported and the function is the invoked ie.app = create_app()
which creates the application.
-
- Flask micro framework was used to build the web app using python code.
-
- Flask login was used to manage logged in users.
-
- Werkzeug Security Helpers were used to hash the user passwords before storing in the database
-
- For sending emails relating to the contact and password reset forms
- For the Heroku deployment, I used the Heroku Sendgrid extension instead, as Flask-Mail was giving me the error
SMTP AUTH extension not supported by server
, although it worked locally.
- For the Heroku deployment, I used the Heroku Sendgrid extension instead, as Flask-Mail was giving me the error
- For sending emails relating to the contact and password reset forms
-
- For interacting with the MongoDB database
-
- For creating forms and adding validation
-
- To help deploy the app to Heroku
- TailwindCSS was used extensively as the primary method of styling the application
- I installed Tailwind by initialising an NPM package within my static folder with
npm init
- I then ran the following NPM installs within the terminal:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest postcss-cli
to install the necessary dependencies - I ran
npx tailwindcss init -p
to create both my tailwind and postcss config files - I created two .css files - app.css & compiled.css
- Within app.css I placed the tailwind directives to inject Tailwind's styles into the CSS file:
@tailwind base;
@tailwind components;
@tailwind utilities;
- I then included a PostCSS command wihtin my package.json to compile the Tailwind libraries into a usable CSS file:
"build:css": "postcss css/app.css -o css/output.css"
- Any time that I made custom CSS changes or additions within my app.css or tailwind.config.js files, I would run this command to recompile my CSS file and then complete a hard refresh within my browser to update the changes
- Before deploying the site to Heroku, I ensured that
purge
was set totrue
within my tailwind.config.js file. This ensures that only the necessary classes are compiled in the final CSS file used by my web page, minimising the amount of CSS that the browser has to load as much as possible.
- In adding user images to the website, I utilised the Cloudinary Upload API
- Within my Cloudinary account, I set up a hot_dogz folder.
- Within my Dog model I added a method to save uploaded user images into this folder under the format
hot_dogz/{user}/{pk}
- All of of a user's dogs would be saved within one folder, named after the user's username
- Each dog's photo would be named after it's primary key
- If a user delete's a dog or delete's their profile, then the dog image is deleted from the cloudinary database using the built-in
uploader.destroy()
method.
-
Within each Dog collection, I added two additional image URLs which utlised the Cloudinary transformation API. These make on-the-fly changes in how the image is rendered on the web page.
- One URL for displaying image thumbnails on the dog's profile page
- One URL for displaying at a fixed aspect ratio within the dog card component
-
For the
img_url_thumb
i added the transformations:w_500,c_scale,q_auto:low
- Width: 500px
- Crop: scale
- Auto render in low quality for smaller file size
- These transformations dramatically reduce file size. Here is an example file size difference between clicking on the original full-size image vs what is rendered on a dog's profile page (from 2.95MB to 21.85kb)
-
For the
img_url_card
I wanted images to be a fixed size of 350px x 525px, so I used the transformationsc_fill,g_auto,h_350,w_525,q_auto:low
- Crop is set to fill
g_auto
= Gravity set to 'auto'. The 'gravity' transformation determines which part of an image to focus on and decides where a crop should be made- Setting it to 'auto' leaves Cloudinary's AI decide where to crop the image, which generally focuses on a dog's face. An example can be seen here:
Without gravity: With gravity set to 'auto':
- Font Awesome - For icons used throughout the site
- GIMP - GNU Image Manipulation Program - For image editing
- favicon.io - For creating favicon .ico
- Google Fonts - for importing chosen fonts
- Balsamiq - For creating my wireframes
- QuickDBD - For creating my DB schema diagram
- Am I Responsive? - For creating the mockup image at start of README
- VSCode - My primary code editor of choice for the project
- PyCharm - Secondary code editor, used particularly for additional PEP 8 compliance functionality
- Python3
- Github account
- MongoDB account
- Heroku account
To create a clone, follow the following steps.
- Log in to GitHub and go to the repository.
- Click on the button with the text “Code”.
- Click “Open with GitHub Desktop” and follow the prompts in the GitHub Desktop Application or follow the instructions from GitHub to see how to clone the repository in other ways.
-
Install all the requirements:
- Go to the workspace of your local copy.
- create a virtual environment with
python3 -m venv venv
- Activate your virtual environment with
source venv/bin/activate
- Install requirements from requirements.txt file with
pip install -r requirements.txt
-
Create your database in MongoDB.
- Signup Or Login For MongoDB
- Create a cluster as well as a database.
- Create the following collections in the Database:
- breed
- comment
- dog
- user
-
Create a file in the root directory called ".flaskenv". This will contain all of your envornment variables. Your .flaskenv file should look similar to the following:
FLASK_APP=run.py
FLASK_DEBUG=[0 for off, 1 if you want to run flask debug mode locally]
SECRET_KEY=[random string]
MONGO_URI=mongodb+srv://[mongoDBusername]:[mongoDB password]@[clustername].wijab.mongodb.net/[database name]?retryWrites=true&w=majority
MONGO_DBNAME=[mongoDB database name]
CLOUD_NAME=[cloudinary username]
CLOUD_API_KEY=[cloudinary API key]
CLOUD_API_SECRET=[cloudinary API secret key]
CLOUDINARY_URL=[cloudinary connection URL]
MAIL_SERVER=[your mail smtp string, i.e. 'smtp.googlemail.com' if using gmail]
MAIL_PORT=[587 if using TLS, 465 if using SSL]
MAIL_USE_TLS=[1 if true, 0 if false (if false then set MAIL_USE_SSL=1 instead)]
MAIL_USERNAME=[email username, i.e. #####@gmail.com]
MAIL_PASSWORD=[login password for email]
- Make sure that .flaskenv is included in your .gitignore file. It should be included already in cloned file
- To read your environment variables from your .flaskenv file, you must ensure that you have installed Python-dotenv within your virtual environment:
pip install python-dotenv
. This should have alreayd happened when installing requirements earlier
To deploy our application on Heroku, we are required to have a requirements.txt file as well as a Procfile. These files will allow Heroku understand what dependencies are required to run the application, as well as tell Heroku which file to run, to launch the application.
- Within your root folder, type in the terminal
touch Procfile
to create the Procfile - In your IDE, insert the text
web: gunicorn run:app
in your Procfile and save- gunicorn should have been installed via pip earlier
-
Open Heroku.
-
Login or signup for Heroku.
-
Once logged in create a new app and select the desired region.
-
Deployment method "GitHub" (if this section is accidentally missed, you can use the tab selection within your dashboard "DEPLOY")
-
Select "connect to GitHub" and follow the on screen instructions. Once connected to your Github:
- Search for your repository using the form provided.
-
Once you have connected your GitHub repository:
- Navigate to the "Settings" tab:
- Scroll to the section "Config Vars" here you will have to tell Heroku what these variables are:
- Input all data found in .flaskenv file into the config var section
- Scroll to the section "Config Vars" here you will have to tell Heroku what these variables are:
- Navigate back to the "Deploy" tab:
- Scroll to the "Manual Deploy" tab:
- Select the branch you wish to deploy (master is default)
- Click the "Deploy Branch" button. (This may take some time as Heroku uploads the app to their servers.)
- Scroll to the "Manual Deploy" tab:
- Navigate to the "Settings" tab:
-
Once the build is complete, a "View App" button will appear just below the build progress box. You can click this to see immediately if the build was successful. If the app doesn't load first time, try refresh once prior to investigating further.
-
Common issues include outdated requirements.txt and/or missing Procfile, if errors occur, check these are both correct before investigating further
- My navbar designed was inspired by this post on TailwindComponents by sebageounity
- Form design was inspired by this post on TailwindComponents by darkcris1
- Most of my Flask code and knowledge was inspired by Miguel Grinberg's Flask Mega-Tutorial
- Flask knowledge was also supplemented by Corey Schafer's Python Flask Tutorial on YouTube, particularly the use of Blueprints
- Dog avatars sourced from freepik
- Pug photos were sourced from Burst by artist Matthew Henry
- Other dog wallpapers were sourced from Shutterstock (via a Premium subscription for 1 month)
- Breed list was taken from this list of Top 100 dog breeds in the UK