- The Umbra Report is a blog dedicated to strange and unusual stories.
- Users are able to contribute stories with a supporting image while interacting with the posts of others through comments and likes.
- The site offers an opportunity for community around creative writing or perhaps even true stories.
The live app is hosted on ther Heroku platform. More details on how the project was deployed can be found below here.
The project's code and file repository is hosted by Github. More details on cloning or forking the repository can be found here.
David C. O'Gara
- The Umbra Report
- Table of Contents
- UX
This project is to be made using Agile develpment pricipals to plan and complete the work to a minimum viable product. This involves seperating the work in to a project back log of user stories in which an iteration of the project (MVP) must be achieved by project deadline.
Following Agile development practice this project has been divided into user stories to give a clear understanding of the project needs, goals and features and to split the work in to small achievable sets of tasks.
Here is a list of user stories for this project seperated in to their relative Epics. Each story links to its place on Github where you can view:
- User Story
- Acceptance criteria
- MSCW prioritisation label
- Linked iteration
- Linked project board.
Epic: User Registration and Authentication
- As a Site User, I want to register an account on the blog website so that I can create and manage my own posts.
- As a Site User, I want to log in to the website securely using my credentials so that I can participate in the blog’s features.
- As a Site user, I want the option to reset my password in case I forget it, ensuring secure access to my account.
Epic: Blog Post CRUD Management
- As a Site User, I want to create a new blog post with a title, content, and optional images, so that I can share my thoughts and experiences.
- As a Site User, I want the ability to update my existing blog posts to update or improve the content over time.
- As a Site User, I want to be able to delete my own blog posts if I decide to remove them from the website.
- As a Site User or Admin, I can create draft posts so that I can finish writing the content later.
- As a Site Admin I can manage all blog posts through the admin panel to maintain control over the site’s content.
Epic: User Interaction and Engagement
- As a Site User I can view a paginated list of posts so that I can select which post I’m interested in viewing.
- As a Site User, I can click on a post so that I can view the complete content.
- As a Site User or Admin, I can view the comments on each individual post so that I can read the conversation.
- As a Site User, I want to be able to comment on blog posts to engage with the author and other readers.
- As a Site Admin I can approve or disapprove comments so that I can filter out objectionable comments.
- As a Site User, I want to be able to like blog posts to engage with the author and other readers.
- As a Site User, I want to receive notifications when someone likes or comments on my blog posts to stay informed about the engagement with my content.
Epic: Search and Navigation
- As a Site User, I want to search for specific blog posts or topics to quickly find the information I'm looking for.
- As a Site User, I want a user-friendly navigation menu that helps me explore different sections of the blog easily.
Epic: Responsive Design and Accessibility
- As a Site User, I want the blog website to be responsive, ensuring a seamless experience on various devices, including desktops, tablets, and smartphones.
- As a Site User with accessibility needs, I want the website to be accessible, with features like alt text for images and keyboard navigation, to ensure a positive experience for all users.
Epic: Additional information
- As a Site User, I can click on the About link so that I can read about the site.
- As a Site Admin, I can create or update the about page content so that it is available on the site.
- As a Site User, I can click on a username link so that I can read about the user and see their posts.
Due to the small scope of this project, having a project backlog and seperate iterations didn't seem neccessary as their would only be one project to client hand-over. It was instead decided to keep all the user stories in the one project iteration list. A project backlog milestone was created however on github to follow agile practice.
Update: User Stories with the 'won't-have' label that didn't fit into the scope of this project were moved back into the project backlog at the end of the iteration period.
You can view it here.
As this is an accedemic porfolio project there is only one interation in which this project will be developed and tested. That being the project start date to it's hand-in date by which all its must-have deliverables are to be achieved.
The aim of The Umbra Report is to provide a space for users to share unusual stories, whether real or imagined as well as engage with the stories of others through comments, likes and uploading of related images.
The target audience for this project are enthusiasts of horror stories, urban legends, 'creepypasta', ghost hunting and crytozoology. The site aims to be suitable for a wide-audience so meterial that is deemed inappropriate, harmful or 'not safe for work' will be filtered about by the site admin.
Wireframes were created to plan the basic layout of the App.
VIEW SECTION
The diagram displayed here shows the relationship between the database models used in the project.
- The Report custom model links to the User through an author field. This is the site user that has created the report (blog post).
- The Report model also relates to the User model through the likes field. This is a many-to-many field as this allows a user to like multiple reports and a report to have multiple likes.
- The User model is Django's built in user authentication model and is linked to buy the other models.
- The Comment model links to the Report model as post (the subject of the comment) and the User model as author.
- The About model links to the User model as author.
The project uses the PostgreSQL database installed and managed through the ElephantSQL service and the Psycopg2 database adapter for use with Python and Django. This is chosen as the project requires a relational database for interactivity between models.
The custom Report model is the model that acts as the site's blog post.
DB Key | Data Type | Purpose | Additional Information |
---|---|---|---|
id(Primary Key) | IntegerField | Unique numerical identifier. | Automatically generated. |
title | CharField | This is the title of each blog post. | Required. Has to be unique. Max character length is 250 |
slug | AutoSlugField | Slug is a string that can only include characters, numbers, dashes, and underscores. This is auto generated by the content of the title field via the Django library Autoslug. | Slug is editable. |
author | ForeignKey | The is the author of the report linked to and represented by the User model. | On deletetion of linked User model post will also be deleted. Realted name is 'blog_posts'. |
category | CharField | This allows a user to select a category for their report from a dropdown list. | CATEGORY_CHOICES = [("1", "True Story"),("2", "Urban Legend"),("3", "Creative Writing"),("4", "Creepypasta"),] |
source | URLField | This in option to include a url if the report references work from elsewhere. | blank = True |
featured_image | CloudinaryField | This field allows the user to upload an optional image as supporting content for the report and is done so by linking to the hosting service Cloudinary. | By default this field is set to 'placeholder' (as a string). |
image_title | CharField | Optional field to input a title for the featured_image. | blank=True |
image_author | CharField | Optional field to input an author for the featured_image. | blank=True |
image_source | Charfield | Optional field to include URL if image referenced from elsewhere. | blank=True |
content | TextField | This is the reports main text content. | |
description | CharField | This field is for a description, tagline or excerpt of the main text to be seen as a preview in the blog's index page. | This can be left blank by the user, in which case the site Admin can fill with appropriate information while reviewing. Max character length is 500. |
status | IntegerField | This field uses a binary system (1 or 0) to represent whether the report is in draft or published status. | Set to 'published' by default. |
created_on | DateTimeField | This field states the date and time the instance of report was created. | Automatically generated once when instance is created through 'auto_now_add'. |
updated_on | DateTimeField | This field states the date and time the instance of report was last updated on. | Automatically generated each time the instance is updated via 'auto_now'. |
approved | BooleanField | This field is a simple checkbox to mark whether the administrator has apporoved the content for publication. | Set to False (unapproved) by default. |
likes | ManyToManyField | This field collects a list of Users who have given the page a 'like'. | Allowed to be blank by default. |
Report model contains two model functions.
-
str(self): Returns string of Title and Author as '{self.title} by {self.author}' for admin display purposes.
-
likes_number(self): Returns an integer of number of users who have liked the report.
-
Create - Registered users (see User model) are able to create a new report as author via a form on the site.
-
Read - Users both registered and unregistered can browse and read published reports.
-
Update - Registered and authenticated users are able to update/edit a report providing they are the author or administrator.
-
Delete - Registered and authenticated users are able to delete a report providing they are the author or administrator.
The Comment model represents a comment written on a report. This is based on the Comment model used in the Code Institute "I Think, Therefore I Blog" walkthrough project.
DB Key | Data Type | Purpose | Additional Information |
---|---|---|---|
post | ForeignKey | Links to the Report model to specify which report is the subject of the comment. | Upon deletion of the referenced report instance the comment will also be deleted from the database. |
content | TextField | This is the main text content of the comment. | |
approved | BooleanField | This is a checkbox to state whether the admin has deemed to content of the comment as suitable for publication. | Set to false (unapproved) by default. |
created_on | DateTimeField | This field states the date and time the instance of comment was created. | Automatically generated once when instance is created through 'auto_now_add'. |
updated_on | DateTimeField | This field states the date and time the instance of comment was last updated on. | Automatically generated each time the instance is updated via 'auto_now'. |
Comment model contains one model function.
-
str(self): Returns string of comment content and author as "Comment: {self.content} by {self.author}".
-
Create - Registered users (see User model) are able to create a new comment as author via a form on the site.
-
Read - Users both registered and unregistered can browse and read published comments.
-
Update - Registered and authenticated users are able to update/edit a comment providing they are the author or administrator.
-
Delete - Registered and authenticated users are able to delete a comment providing they are the author or administrator.
The About model represents a paragraph of text about the site. This is largely based on the About model used in the Code Institute "I Think, Therefore I Blog" walkthrough project.
DB Key | Data Type | Purpose | Additional Information |
---|---|---|---|
title | CharField | The title of the about article. | Max character length of 200. |
updated_on | DateTimeField | This field states the date and time the instance of comment was last updated on. | Automatically generated each time the instance is updated via 'auto_now'. |
content | TextField | The main text portion of the about article. |
About model contains one model function.
- str(self): Returns string of title as "Title".
The User model is Django's buit in user authentication model. More information on this model can be found in the Django documentation here. Users can register for an account on the site through forms provided by Django Allauth.
A few notes on relevant fields.
- is_staff is a boolean field that states whether a registered user can access the admin panel. This is needed to approve posts and comments, update the about page and have an overall control of the content.
- is_superuser is a boolean field that grants a particular user all available privelidges, including staff status. The site creator registers as superuser during production.
VIEW SECTION
The Umbra Report title was taken from music I was listening to at the time of developing ideas for the project. This was 'The Umbra Report', an album by 'Cities Last Broadcast'. I felt the title fit the theme of 'reports from the shadows' that I was going for. Bandcamp
The app's theme is one of stories and urban legends, inspired by sites such as readit where various threads allow users to contribute stories of varied kinds and comment on each other's posts.
This theme was also part inspired by the game Control by Remedy Entertainment. The game's theme features an organisation that collects and investigates strange phenomena, objects and reports. I felt this was a fun theme on which to work this project around.
This game seemed the perfect place to take inspiration for the visual design of the app also. From the mysterious background image pictured above to the UI design bellow. The red highlights, white text and use of symbols throughout was something I wanted to try in the visual design for this site.
For fun, a small reference to the animated series Yamishibai (闇芝居, Yami Shibai, lit. Dark Play) is included at the end of each report with the question 'oshimai?' (おしまい?, eng. The End?). This is something the narrator asks at the end of each ghost story.
- #ffffff Main body text is white allowing the text to stand out in contrast to the darker shades of the background colour.
- #800000 Dark red is used to highlight headlines, hyperlinks and important information such as warnings.
- #008080 Cyan is used as both a body background colour when the background image fails and as hover colour for anchor tags.
- #000000 Black is used as a background shade to contrast the white, red, grey and cyan used throughout.
- #808080 Grey is used as a background to some buttons.
- UPDATE - Dark red(#800000) was changed to a lighter red (#BA0000) later in production. Though this didn't show up on any lighthouse or wave audits, it was found to be a contrast issue by a low decimal number on dev tool's contrast information.
- Oswald via google-fonts
- Courier Prime via google-fonts
Oswald and Courier Prime were chosen to provide an appealing contrast between headings in Oswald and main body text in Courier. Courier in particular was chosen as it resembles a typewriter written document and suited the blog's themes of observation and reports.
A logo for the site, featured on the About page, was created. This represents the site's themes of observation and collaboration.
A background image was created for the site's main body. This represents the site's theme of the unknown and the unusual through its abstract and distorted appearance.
A user-image was created. This is a textured edit of a standard blank user image used to represent an anonymous reporter.
Icons are used throughout the site and are sourced from fontawesome. See [credits] for a full list.
- A CSS transform is applied to buttons as a hover state that increases their size to 1.1 scale.
- Bootstrap is used thoughout this project to allow for a quick development of a site that is responsive and stylised with a greatly reduced amount of code.
Custom CSS was added where necessary to create a more personalised look to the site on top of Boostrap's default styles. The file can be viewed here.
VIEW FEATURES
The Navigation bar was created from Bootstrap's offcanvas navbar found here.
This was updated to include Admin links when a user with staff status is signed in:
A button on the index page allows the user to access a form for creating a Report (blog post).
When not logged in the button instead takes the user to the login page.
The form can then be filled out and submitted.
As a Site User or Admin, I can create draft posts so that I can finish writing the content later.
Users can select whether they want to publish their report or save it as a draft to finish later. Draft reports can then be accessed via the user page.
The index page shows a list of reports (blog posts) and these are separated into pages if there are more than 6.
As a Site User, I can click on a post so that I can view the complete content.
Clicking on a report title takes the user to a full dteail view of the post where they can read the whole content.
Users who want to edit a report they have writen can do so when logged in. A link to edit is on the list view in the index page and an edit button can be found on the full report detail page also.
Users can also choose to delete any report they have written providing they are logged in. A link to delete the report can be found on the list view in the index page and a button to delete can be found on the full report detail page also. Choosing to delete brings up a model to ask for confirmation.
As a Site User, I want to be able to like blog posts to engage with the author and other readers.
Users, when logged in can like a post by accessing the full post page. A button can be found beneath the main content. Once liked a user can unlike a post with the same button.
A form to add a comment on any post can be found at the bottom of the full report detail page. If a user is logged in the can choose to fill out this form to add a comment.
A list of comments can be found at the bottom of the full report detail page. User can only see approved comments unless the are the auther of a yet to be approved comment.
Once a comment is created a user can choose to edit that comment using the edit button bellow the comment. A user must be logged in and the author of the comment to be able to edit. Clicking the edit button changes the original comment form to an edit comment form, populated with the comments content.
A user can choose to delete their comment providing the are the author and are logged in. This is done by selecting the delete button beneath the comment and confirming with the modal.
A new user can register an account by selecting sign up and entering the required details. This is handled by django allauth.
A user, once registered can then log in or out by selecting the links in the navigation bar. This too is handled by django allauth.
Should a user forget their password, Django-allauth offers a password reset option on the login page. This requires the user have an email so email was made a requirement on signup.
As a Site User, I can click on the About link so that I can read about the site.
An about page was created to give users an idea of what the site's themes are and how to participate.
Users can click on their own or anothers user name to access a page that lists links to that user's written reports.
As a Site Admin, I can create or update the about page content so that it is available on the site.](#21)
In creating the Djando app I created a super user account for myself (see detail) which allows me to access the admin panel. This means I have the ability to edit and approve all posts and/or comments as well as update the about page.
Being a project with limited time a couple of user stories were given the label 'won't-have' and are marked here as possible future features.
- As a Site User, I want to receive notifications when someone likes or comments on my blog posts to stay informed about the engagement with my content.
- As a Site User, I want to search for specific blog posts or topics to quickly find the information I'm looking for.
- As a Site User, I want to filter blog posts by categories to quickly find the type of information I'm interested in.
Please refer to this seperate testing document for a full rundown of tests and audits:
- HTML
- CSS
- Javascript
- Python
- Django
- Bootstrap
- Heroku (deployment)
- Cloudinary (image hosting)
- Django-Allauth (user authentication)
- VScode (IDE)
- Photoshop (for Wireframes)
- ERD made with Lucidchart
- GitBash
- Full requirements.txt:
REQUIREMENTS LIST
asgiref==3.7.2
bleach==6.1.0
certifi==2023.11.17
cffi==1.16.0
charset-normalizer==3.3.2
cloudinary==1.36.0
crispy-bootstrap5==0.7
cryptography==41.0.7
defusedxml==0.7.1
dj-database-url==0.5.0
dj3-cloudinary-storage==0.0.6
Django==4.2.9
django-allauth==0.57.0
django-autoslug==1.9.9
django-crispy-forms==2.1
django-summernote==0.8.20.0
gunicorn==20.1.0
idna==3.6
oauthlib==3.2.2
psycopg2==2.9.9
pycparser==2.21
PyJWT==2.8.0
python3-openid==3.2.0
requests==2.31.0
requests-oauthlib==1.3.1
setuptools==69.0.3
six==1.16.0
sqlparse==0.4.4
tzdata==2023.4
urllib3==1.26.18
webencodings==0.5.1
whitenoise==5.3.0
This project requires some steps in preparation...
ElephantSQL is the PostgreSQL database hosting service used in this project.
DETAILS
- Visit ElephantSQL and set up a free account if you don't already have one (you can use Google or Github to create an account easily).
- Login
- Navigate to Dashboard
- To create a new database instance click the '+Create New Instance' button.
- Select a name for your instance (usually the project name).
- Select the free plan called 'Tiny Turtle'.
- (You can leave Tags empty).
- Click the 'Select Region' button.
- Select any available data centre.
- Click the review button.
- If all the details look good then click the 'Create Instance' button.
- Click on your new instance.
- Navigate to STATS menu.
- Check the version is 12 or higher.
- (any less and you may have to create another instance with a different chosen data centre).
- Navigate to DETAILS menu.
- Copy the URL
- Paste into env.py file in this format:
os.environ.setdefault("DATABASE_URL", "<The copied instance URL>")
- pip3 install packages needed to connect to your database:
pip3 install dj-database-url~=0.5 psycopg2~=2.9
- Add to reqiurements.txt:
pip3 freeze --local > requirements.txt
- import the packages into the setting.py file:
import os import dj_database_url if os.path.isfile('env.py'): import env
- Comment out the existing sqlite3 databas connection:
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
- Connect the DATABAS_URL from env.py to settings.py in this format:
DATABASES = {'default': dj_database_url.parse(os.environ.get("DATABASE_URL"))}
- Your project should now be connected to the database, to create the database tables use this command:
python3 manage.py migrate
Use this command and enter details to create your superuser account:
python3 manage.py createsuperuser
Cloudinary is a hosting platform used for images in this project.
DETAILS
- Install The necessary Cloudinary Packages:
pip3 install cloudinary~=1.36.0 dj3-cloudinary-storage~=0.0.6 urllib3~=1.26.15
- Don't forget to add to requirements file:
pip3 freeze --local > requirements.txt
- Visit Cloudinary and set up a free account if you don't already have one (you can use Google or Github to create an account easily).
- Login
- Navigate to Programmable Media - Dashboard
- Copy the API environment variable: CLOUDINARY_URL and fit it into your env.py file in this format:
os.environ.setdefault("CLOUDINARY_URL", "<The URL copied from Cloudinary Dashboard>")
- (remove 'CLOUDINARY_URL=' from the copy-pasted string)
- Open settings.py and add cloudinary and cloudinary_storage to INSTALLED_APPS.
- (The cloudinary_storage app should by place directly under django.contrib.staticfiles)
DETAILS
To keep the original branch unaltered you can fork a repository on github.
- Navigate to the repository you want to fork.
- Click the 'Fork' button at the top right.
- Select the 'Owner' from the dropdown menu.
- Enter an optional description.
- Choose whether or not to copy main branch only.
- Click Create fork.
Source: Github Docs
On Github you can clone your repository to create a local and sync between the two locations.
- Navigate to the main page of the repository.
- Click thr '<>Code' button..
- Copy the repository URL.
- Open Git Bash or a terminal in your IDE.
- Choose the directory you want to place the repository.
- Type 'git clone', and then paste in the copied repo URL.
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
- Press Enter to create your local clone.
Source: Github Docs
DETAILS
To get started with local development in GitPod or your preferred IDE, follow these steps:
- Clone the repository.
- Install required packages (if project is already worked on)
pip3 install -r requirements.txt
-
Create an 'env.py file in the app's root directory for environment variables (if using CI template this should already exist).
-
In the 'env.py file, add a secret key in this format:
import os
os.environ.setdefault(
"SECRET_KEY",
"<your chosen key goes here>"
)
- Add to settings.py in this format:
import os
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY")
- Start the server by running the following command:
python3 manage.py runserver
- Now you can access the application by opening the provided URL in your browser (add your browser url to allowed hosts).
ALLOWED_HOSTS = ['<browser URL>']
DETAILS
Prepare the Django Project:
- Ensure your Django project is properly configured and runs smoothly locally.
- Make sure you have a requirements.txt file listing all Python dependencies required for your project.
- Ensure your project's settings are set up to work in a production environment (e.g., DEBUG = False, static files configuration, database settings).
Create a Procfile:
- Heroku uses a Procfile to determine how to run your application.
- Create a file named Procfile (no file extension) in your project's root directory.
- Inside the Procfile, specify the command needed to run your Django application.
For example:
web: gunicorn your_project_name.wsgi
Create a runtime.txt File:
- Heroku needs to know which Python version your application requires. Create a runtime.txt file in your project's root directory.
- Inside runtime.txt, specify the Python version.
For example:
python-3.9.5
Install Gunicorn:
- Gunicorn is a WSGI HTTP server for Python web applications, and it's commonly used for deploying Django applications.
- Install Gunicorn and add it to your requirements.txt file:
pip install gunicorn
pip freeze > requirements.txt
Set Up Database:
See ElephantSQL
Ad Heroku to allowed hosts in settins.py:
ALLOWED_HOSTS = ['<browser URL>', '.herokuapp.com']
Collect Static Files:
If your project uses static files, collect them using Django's collectstatic command:
heroku run python manage.py collectstatic
Commit your changes to Git:
git add . git commit -m "Initial commit"
Create a new app on Heroku:
-
Head over to heroku and log in. Choose 'create new app'.
-
Choose a name for your app and your location. Hit 'create app'.
-
Select the 'Settings' tab.
-
In Config Vars, reveal config vars.
-
Enter the config variables used in your env.py file.
-
For example:
-
CLOUDINARY_URL
-
DATABASE_URL
-
EMAIL_USER - (gmail address)
-
EMAIL_PASS- (gmail password)
-
SECRET_KEY (The Django secret key is a crucial security setting used to provide cryptographic signing.)
It is used for:
- Security: The secret key is used to generate hashes for password reset tokens, user session tokens, and other cryptographic signatures. It helps protect against various security threats such as session hijacking, data tampering, and CSRF (Cross-Site Request Forgery) attacks.
- Session Management: Django uses the secret key to sign and verify session cookies. This ensures that the session data stored in cookies cannot be tampered with by malicious users.
- CSRF Protection: Django uses the secret key to create tokens for preventing CSRF attacks. When a form is rendered, Django includes a hidden CSRF token in the form. When the form is submitted, Django verifies that the CSRF token matches the one generated for the user's session, thus preventing CSRF attacks.
- Cryptographic Signatures: The secret key is used to sign various pieces of data within Django, such as cookies, messages, and tokens. This allows Django to verify the integrity and authenticity of these pieces of data.
- Given its critical role in security and various Django functionalities, it's essential to keep the secret key secret and never share it publicly or with unauthorized individuals. If the secret key is compromised, it could potentially lead to security vulnerabilities in your Django application.
-
-
Head over to the Deploy tab.
-
Select Github (you will need to authorize this).
-
Choose your repository.
-
Manually deploy the main branch of this GitHub repo.
-
You can now view your app.
- The Code Institute walkthrough project 'I Think, Therefore I Blog' was used to inform the setup and general structure of this project.
- A couple of blog posts were written with ChatGPT, these are under the author 'TestUser'.
Icons are sourced from fontawesome.
The favicon image was converted using favicon.io.
Site logo and background created by the site author.
Majority of Report images uploaded to site are also the author's work however as the site is available for users to upload images, any copyrighted images found will have to be dealt with accordingly when identified.
- Thanks to my Mentor, Malia, for her advice on ensuring a solid end product.
- Thanks to those users who signed up and got involved in testing/using the site.