NB: Some updates to readme done post-course. Submitted project can be forked from the main branch.
TechieMeme is a website meant to lighten up people's day who like technology or related things. Like Code, science, maths or hardware as an example. Inspiration for this site came from the Code Institute Slack channel community-tech-humour, which I love. On this site, the user can view, like and comment on the memes that they like or upload their memes and edit, or delete them if necessary. This website was created as a learning exercise for my Code Institute fourth portfolio project.
The live app can be found here.
Admin panel can be found here.
- To create a website that is easy to navigate and use.
- To create a visually appealing website.
- To create a website that is responsive on all devices.
- To create an interactive website.
- To create a website that is fun to use.
- To create a website that is easy to use.
- As a user, I want to be able to view the site on any device I choose.
- As a user, I want to be able to easily navigate the site.
- As a user, I want to be able to easily find the content I am looking for.
- As a user, I want to be able to easily register for an account.
- As a user, I want to be able to easily log in and out of my account.
- As a user, I want to be able to easily recover my password if I forget it.
- As a user, I want to be able to easily view other user's profiles.
- As a user, I want to be able to easily search for memes.
- As an authenticated user, I want to be able to easily upload my own memes.
- As an authenticated user, I want to be able to easily edit my own memes.
- As an authenticated user, I want to be able to easily delete my own memes.
- As an authenticated user, I want to be able to easily like memes.
- As an authenticated user, I want to be able to easily comment on memes.
- As an authenticated user, I want to be able to report memes.
- As an authenticated user, I want to be able to easily contact the developer.
- As a user, I want to be able to easily view my profile.
- As an authenticated user, I want to be able to easily edit my profile.
- As an authenticated user, I want to be able to easily delete my profile.
- As a site owner, I want to be able to log into the admin panel.
- As a site owner, I want to be able to easily manage memes in case of inappropriate content.
- As a site owner, I want to be able to easily manage users in case of inappropriate content.
- As a site owner, I want to be able to provide a fun and easy-to-use website.
With the design for this website, the aim was to create a fun and easy-to-use website. The main colours were chosen from black to emoji icon yellow. Had to add some CSS text-shadow trickery to improve the contrast on the light background. Also to improve accessibility for people with visual impairments some of the meme text was changed to darker yellow.
The colour palette was chosen and made on Coolors.
- Google Fonts was used for the font.
Font Lexend was chosen for the site. This font was chosen because it is easy to read and looks good on all devices. By using this font the site is more accessible for people with visual impairments whilst. Two font weights are used, 400 and 700. 400 is used for the main text and 700 is used for the logo and some headings where bold text is required. Fall back font is Sans-serif.
Font Weight 400 | Font Weight 700 |
---|---|
![]() |
![]() |
Didn't stray too far from the original wireframe. Only a few small things. Text on the footer, and banner and added carousel to the main page instead of pagination as it is on all the other pages.
- The user model is the default Django user model.
key | Field Type | Validation |
---|---|---|
id | IntegerField | |
password | CharField | |
last_login | DateTimeField | |
is_superuser | BooleanField | |
username | CharField | max_length=150, unique=True |
first_name | CharField | max_length=150, blank=True |
last_name | CharField | max_length=150, blank=True |
EmailField | max_length=254, unique=True | |
is_staff | BooleanField | |
is_active | BooleanField | |
date_joined | DateTimeField |
- UserProfile model is connected to the User model with OneToOneField. This model is used to store extra user information.
key | Field Type | Validation |
---|---|---|
user | OneToOneField | User, on_delete=models.CASCADE |
username | CharField | max_length=50, null=True, blank=True |
first_name | CharField | max_length=50, null=True, blank=True |
last_name | CharField | max_length=50, null=True, blank=True |
location | CharField | max_length=100, null=True, blank=True |
EmailField | max_length=100, null=True, blank=True | |
bio | TextField | max_length=500, null=True, blank=True |
user_image | ResizedImageField | upload_to='users/', null=True, force_format='WEBP', quality=85, blank=True, default='users/default_user.webp' |
social_github | URLField | max_length=200, null=True, blank=True |
social_linkedin | URLField | max_length=200, null=True, blank=True |
social_facebook | URLField | max_length=200, null=True, blank=True |
social_youtube | URLField | max_length=200, null=True, blank=True |
social_website | URLField | max_length=200, null=True, blank=True |
created | DateTimeField | auto_now_add=True |
id | UUIDField | primary_key=True, default=uuid.uuid4, editable=False |
- The meme model is used to store all the memes uploaded by users.
key | Field Type | Validation |
---|---|---|
uploader | ForeignKey | UserProfile, on_delete=models.CASCADE |
title | CharField | max_length=100, null=True |
meme_img | ResizedImageField | upload_to='memes/', null=True, force_format='WEBP', quality=85, blank=True, default='memes/default.webp |
tags | ManyToManyField | Tag, blank=True |
smiley_face | ManyToManyField | UserProfile, related_name='smiley_face' |
sad_face | ManyToManyField | UserProfile, related_name='sad_face', blank=True |
created | DateTimeField | auto_now_add=True |
id | UUIDField | primary_key=True, default=uuid.uuid4, editable=False |
- The Comment model is used to store all the comments on the memes.
key | Field Type | Validation |
---|---|---|
meme | ForeignKey | Meme, on_delete=models.CASCADE, related_name='comment' |
user | ForeignKey | UserProfile, on_delete=models.CASCADE, null=True, blank=True |
comment | TextField | max_length=500 |
created | DateTimeField | auto_now_add=True |
id | UUIDField | primary_key=True, default=uuid.uuid4, editable=False |
- Tag model is used to store all the tags for the memes.
key | Field Type | Validation |
---|---|---|
name | CharField | max_length=20, null=True, blank=True |
created | DateTimeField | auto_now_add=True |
id | UUIDField | primary_key=True, default=uuid.uuid4, editable=False |
- The contactForm model is used to store all the contact forms sent to developers by users.
key | Field Type | Validation |
---|---|---|
name | CharField | max_length=100, null=True |
EmailField | max_length=250, null=True | |
subject | CharField | max_length=150, null=True |
message | TextField | max_length=500, null=True |
created | DateTimeField | auto_now_add=True |
id | UUIDField | primary_key=True, default=uuid.uuid4, editable=False |
Link to my GitHub Agile Project
As it is my first time using Agile Development, I decided to use the Kanban and MoSCoW prioritization method. I used GitHub Projects to create the board.
Did find it a bit difficult to use at first but after a while, I got the hang of it. As a solo developer who is time-constrained, I found it a bit time-consuming to create all the Epics and User stories. However, I can see the benefits of using this method in a team environment. As of now, I will continue to use this method for my future projects and I hope to learn more about it. My first-ever hackathon is coming soon where I will be working in a team and I will be surely using the Agile method.
I created 4 columns, Epics, To-Do, In Progress and Done. I also created 9 labels:
- For MoSCoW prioritization: Must Have, Should Have, Could Have, Won't Have.
- And 5 helper labels: Dev-task, Epic, User-story, In-progress, Done.
- Created 9 Epics divided into 24 User stories. Epics and user stories are connected with the # link on the title and in the description.
Example | Image |
---|---|
Epic | ![]() |
User story | ![]() |
- My Kanban board:
then | now |
---|---|
![]() |
![]() |
This project was created using the following languages and frameworks:
- Django as the Python web framework.
- Python as the backend programming language.
- HTML as the markup language and templating language.
- CSS as the style sheet language.
- Bootstrap 5 as the CSS framework.
- JavaScript to create carousel on index.html.
- jQuery to simplify DOM manipulation.
Django installs a few packages by default and some packages get installed with other packages. Will list out the ones that I installed.
Packages | Description (copied from the web) |
---|---|
boto3 | Boto3 is the Amazon Web Services (AWS) Software Development Kit (SDK) for Python, which allows Python developers to write software that makes use of services like Amazon S3 and Amazon EC2. You can find the latest, most up-to-date, documentation at our doc site, including a list of services that are supported. |
crispy-bootstrap5 | django-crispy-bootstrap5 provides you with a crispy-forms template pack to use with django-crispy-forms in your Django projects. |
Django | Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. |
django-allauth | Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. |
django-crispy-forms | django-crispy-forms provides you with a |
django-resized | Django-resized is the best approach to resize and convert images. It is a Django app that allows easy image resizing using the easy-thumbnails app as a backend. |
django-storages | Django Storages is a collection of custom storage backends for Django. |
Pillow | PIL is the Python Imaging Library. |
psycopg2 | Psycopg is the most popular PostgreSQL database adapter for the Python programming language. Its main features are the complete implementation of the Python DB API 2.0 specification and thread safety (several threads can share the same connection). It was designed for heavily multi-threaded applications that create and destroy lots of cursors and make a large number of concurrent “INSERT”s or “UPDATE”s. |
waitress | Waitress is meant to be a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones that live in the Python standard library. It runs on CPython on Unix and Windows under Python 2.7+ and Python 3.5+. It is also known to run on PyPy 1.6.0 on UNIX. It supports HTTP/1.0 and HTTP/1.1. |
whitenoise | With a couple of lines of config WhiteNoise allows your web app to serve its own static files, making it a self-contained unit that can be deployed anywhere without relying on nginx, Amazon S3 or any other external service. (It can still optionally do all that if you want, though.) |
- Amazon AWS S3 was used to store static and media files.
- Amazon AWS IAM was used to create a user for the project.
- Amazon RDS was used to create a PostgreSQL database.
- Font Awesome was used for all icons.
- Balsamiq was used to create the wireframes.
- Coolors was used to create the colour palette.
- Favicon.io was used to create the favicon.
- Birme was used to convert static images to .webp format.
- ami.responsivedesign.is was used to create the mockup image.
- Lucid was used when creating database ERD.
- Visual Studio Code. Did all of my coding and synchronizing with GitHub on VS Code.
- Git for version control.
- GitHub for hosting repositories.
- Heroku where the website is deployed.
- Grammarly was used to double-check spelling mistakes.
Existing Features
- All pages are responsive on all devices.
- All pages feature a header with the site logo and a navigation bar with links to other pages, login/sign-up links and a search bar.
- All pages feature a footer with links to contact the developer (It sends admin an e-mail).
- All user actions are confirmed with Django messages.
- All user-uploaded content will be automatically converted to .webp format to save space and bandwidth using Django-resized.
- The home page is the first page the user sees when they visit the site.
- The Page will have a welcome message and a button to take the user to the upload meme page or sign up if the user is not authenticated.
- The user can see the latest memes uploaded by other users on the Bootstrap carousel.
- The Meme page is where the user can see all the memes uploaded by other users.
- Page will have 4 meme cards per page and the rest will be paginated.
- The User can click on the meme card to see the single meme page.
- The Delete meme confirmation page is where the user can confirm that they want to delete the meme.
- The Single meme page is where the user can see the single meme.
- User can see the meme title, meme image, meme description, meme category, meme author, meme date uploaded, meme likes, meme comments.
- If the user is the uploader of the meme they will see the edit and delete buttons.
- Authenticated user can comment on the meme and delete their own comments.
- Report meme page is where the user can report the meme.
- Page has a form with the title of the meme link clicked on, name of the user, email of the user and the reason for reporting the meme.
- The Page also has a back button to take the user back to the single meme page in case they don't want to report the meme.
- This form will send an email to the admin with the details of the report and more details about the authenticated user in case of a malicious report.
- The Users page is where the user can see all the users.
- Page will have 4 user cards per page and the rest will be paginated.
- The User can click on the user card to see the single-user page.
- The Single user page is where the user can see the single user.
- User can see the user username, user profile image, user bio, user date joined, last login, user memes, user email, and user social links if provided.
- If the user is the owner of the profile they will see the edit and delete buttons.
NB: The page is zoomed out to show the whole page.
- Edit profile page is where the user can edit their profile.
- Page has a pre-populated form with the profile image, first name, last name, location, user email, user bio and user social links.
- The Page also has a back button to take the user back to the single user page in case they don't want to make any changes.
NB: This page is only available to the user who owns the profile.
- Delete profile confirmation page is where the user can confirm that they want to delete their profile.
- The Page has a message to confirm that the user wants to delete their profile.
- The Page also has a back button to take the user back to the single-user page in case they don't want to delete their profile.
NB: The page is zoomed out to show the whole page. This page is the same for both upload and edit memes with the edit meme instance pre-populated with current details.
- The Upload meme page is where the user can upload their own memes.
- Page has a form with the meme image, meme title and meme tags.
- The Page also has a back button to take the user back to the memes page in case they don't want to upload/edit a meme.
- The Login page is where the user can log into their account.
- Page has a form with the username and password fields.
- Page has a link to the sign-up page in case the user doesn't have an account and social sign-up links to Google and GitHub.
- Page has a "Forgot password?" link to take the user to the password reset page in case they can't remember their current password.
- The Password reset page is where the user can reset their password.
- Page has a form with the email field.
- Password reset only works if the user signed up with a valid email address.
- The Logout page is where the user can log out of their account.
- The Page has a message to confirm that the user wants to log out.
- The Sign-up page is where the user can sign up for an account.
- Page has a form with the username, email, password1 and password2 fields.
- The Page has a link to the login page in case the user already has an account.
- When the user registers for an account and uses email, they will receive a welcome email and confirmation email with a link to confirm their email address.
- When the user won't upload a profile image, a default image will be used.
Sign-up Page | Welcome Email | Confirm Email |
---|---|---|
![]() |
![]() |
![]() |
Redirect to edit profile page | Default profile image |
---|---|
![]() |
![]() |
- Contact developer (link on the footer) page is where the user can contact the developer.
- Page has a form with the user name, email, subject and message fields.
- This page is only available to authenticated users.
- This form will send an email to the developer with the details of the message and more details about the authenticated user.
- The Page also has a back button to take the user back to the home page in case they don't want to contact the developer.
- The 404 page is where the user will be redirected if they try to access a page that doesn't exist.
- The 500 page is where the user will be redirected if there is an internal server error.
- Give some users admin rights to manage memes and users.
- Add an option for a user to add their own custom tags to the meme.
- Add a private messaging feature between users.
- Improve search functionality (search by user name. Currently you can search only users who have uploaded a meme).
- Improve privacy controls and hide some details from other users.
All pages were tested with Google Chrome Lighthouse. Testing was performed in private browsing mode and on the live website on Heroku.
Page | Image |
---|---|
Home Page | ![]() |
Memes Page | ![]() |
Single Meme Page | ![]() |
User Page | ![]() |
Single User Page | ![]() |
Upload Meme Page | ![]() |
Login Page | ![]() |
Logout Page | ![]() |
Contact Developer Page | ![]() |
Report Meme Page | ![]() |
- No errors were found when passing through the official W3C validator.
- All pages were passed through the official W3C validator.
- Validating was done by a live website on Heroku. Some errors were found but they are all related to Django templates.
- All JavaScript files were passed through the official JSHint validator.
File | Image |
---|---|
script.js (Bootstrap carousel on index.html or "Home" page) | ![]() |
- All Python files were passed through the Code Institute PEP8 validator.
File | Result |
---|---|
settings.py | ![]() |
urls.py | All clear, no errors found |
File | Result |
---|---|
admin.py | All clear, no errors found |
apps.py | All clear, no errors found |
forms.py | All clear, no errors found |
models.py | All clear, no errors found |
urls.py | All clear, no errors found |
views.py | All clear, no errors found |
File | Result |
---|---|
admin.py | All clear, no errors found |
apps.py | All clear, no errors found |
forms.py | All clear, no errors found |
models.py | All clear, no errors found |
urls.py | All clear, no errors found |
views.py | All clear, no errors found |
Manual Testing
This website was developed on a Dell Windows 10 desktop computer using Visual Studio Code. Testing was done on the following devices and browsers:
Device | Browser | Result |
---|---|---|
Dell Windows 10 desktop computer | Google Chrome | Works as expected |
Dell Windows 10 desktop computer | Microsoft Edge | Works as expected |
Dell Windows 10 desktop computer | Mozilla Firefox | Works as expected |
Dell Windows 10 desktop computer | Opera | Works as expected |
HP Windows 11 laptop 13" Screen | Google Chrome | Works as expected |
HP Windows 11 laptop 13" Screen | Microsoft Edge | Works as expected |
HP Windows 11 laptop 13" Screen | Mozilla Firefox | Works as expected |
Apple iPhone 12 | Safari | Works as expected |
Apple iPhone 12 | Google Chrome | Works as expected |
Apple iPhone 12 | Mozilla Firefox | Works as expected |
Apple iPhone 13 | Safari | Works as expected |
Apple iPad 10th generation | Safari | Works as expected |
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
Action | Expected Result | Actual Result | Image |
---|---|---|---|
User clicks on the "upload meme" button. | User is redirected to the login page. | Working as expected. | ![]() |
- The Navigation bar and footer are the same as on the home page.
Action | Expected Result | Actual Result | Image |
---|---|---|---|
User clicks on the "Contact Developer" link. | User redirected to the login page. | Working as expected. | ![]() |
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
- The Navigation bar and footer are the same as on the home page.
Action | Expected Result | Actual Result | Image |
---|---|---|---|
User clicks on the "Login" button. | User is redirected to the login page. | Working as expected. | ![]() |
- The Navigation bar and footer are the same as on the home page.
As is now common with my Code Institute projects, most of this was one interesting problem to solve. A few of the more interesting ones are listed below.
- How to convert all images to a more web-friendly format.
- This was solved by using the Django-resized package. This package will convert all images to .webp format and resize them. This will save space and bandwidth.
- Creating the functionality to comment on the memes took much more time than expected. It actually turned out not to be this difficult but I was overthinking it.
- This was solved by creating a new model for comments and then creating a form for that model. Then I created a view for the form and added it to the single meme page.
- [This error was fixed minutes before submission Solution: Didn't realize I needed to change all credentials to match deployed site.] IntegrityError at /accounts/social/signup/
UNIQUE constraint failed: account_emailaddress.email
- This error is caused by the user trying to sign up with the same email address that they used to sign up with Google or GitHub.
- Carousel misalignment on mobile devices.
- This is caused by the carousel needing to move a different amount of pixels on different devices. Will be solved in the future just ran out of time for this iteration.
- Search result pagination.
- When the user enters a short search term, the pagination will show but as as soon as you turn the page it reloads the memes page. This is caused by the search term being in the title or description of the meme. Will be solved in the future just ran out of time for this iteration.
- Go on to Heroku website and log in if you already have an account or sign up if you don't.
- Click on the "New" button on the top right of the home page and select "Create new App" from the drop-down menu.
- In the "App name" field enter the name of your app. This name has to be unique.
- Heroku displays a green tick if your app name is available.
- In the "Choose a region" field choose either the United States or Europe based on your location.
- Click the "Create app" button.
- Next page, top centre of the screen, select the "Settings" tab.
- In the "Config Vars" section, click on the "Reveal config Vars" button.
- In this section you need to enter your environment variables. Usually stored in the env.py file locally. In my case, I have 10 variables:
- SECRET_KEY - Django secret key.
- AWS_ACCESS_KEY_ID - Amazon AWS access key.
- AWS_SECRET_ACCESS_KEY - Amazon AWS secret access key.
- AWS_STORAGE_BUCKET_NAME - Amazon AWS bucket name.
- DATABASE_USER - Amazon RDS database user.
- EMAIL_HOST_PASS - Email password.
- EMAIL_HOST_USER - Email address.
- DATABASE_HOST - Amazon RDS database host.
- DATABASE_NAME - Amazon RDS database name.
- DATABASE_PASS - Amazon RDS database password.
- Copy and paste these variables into the KEY field and their values into the VALUE field.
- Go back to the top of the screen and select the "Deploy" tab.
- In the "Deployment method" section select "GitHub".
- In "Connect to GitHub" click on the "Search" button. Find the project repository in the list and click on the "Connect" button.
- Scroll to the bottom of that page. Click on the "Deploy Branch" button to deploy.
- You should also see an option to enable automatic deployment. If you enable this, every time you push to GitHub, Heroku will automatically deploy the app.
- You will see build log scrolling at the bottom of the screen after that. When successfully finished building the app, you should see the link to your app.
NB: You will need to add your Heroku app link to the ALLOWED_HOSTS in the settings.py file. You also need to make sure that DEBUG is set to False, requirements.txt and Procfile are up to date and pushed to GitHub.
- To clone this project.
- On my GitHub profile page, top centre of the screen click on "repositories".
- Find and click on the "TechieMeme-PP4" repository.
- In the repository page that opens, click on the 'Code' button.
- Menu that opens make sure you are in the "local" tab, copy the link in "HTTPS".
- paste that link into the relevant section in your ide to clone the repository.
- CodeAnywhere.
-
- Click on the "New Workspace" and paste that link to the "Repository URL" field.
- vsCode.
-
- Select "File" and "New Window". In the middle of the page select "Clone Git Repository...",
-
- Paste that link into the search box at the top of the screen and hit enter.
-
- Select the local destination for repository files.
- To fork this repository.
- Open my GitHub repository.
- Click on the 'Fork' button on the top right of the screen.
- On the 'Create a new fork' page you are given the option to rename that repository and then click on the green 'Create fork' button at the bottom of the form.
After reading through numerous articles about copyright laws on memes, the general consensus seems to be that as long as I'm not trying to profit off these memes we should be fine. This website is not for profit and was created for educational purposes only.
Here is one of the first responses from my research: The National Law Review
- The Background image on the home page was taken from Pixabay.
Would like to say thanks to all for the support throughout the project.
- Dennis Ivy He has the best Django tutorials on YouTube and the way he explains everything is very easy to understand. I owe a lot to him.
- Code Institute for all the support and knowledge.
- Slack community tech-humour channel where I got the inspiration for this project and some feedback. My cohort channel for all the support and feedback.
- My mentor Dick Vlaanderen who's continuously very supportive of me and very knowledgeable.
- I would also like to thank or say sorry to my family. I'm not too sure they have seen me much these past weeks. According to Wakatime, I have spent nearly 55 hours behind this computer. That's on top of 40 hour work week.
.