# Project Overview and CSS Variables
Let's work on our final project: the movie watchlist!
In this project you will implement a full app that allows users to store movies they have or intend to watch. Each movie will have its own page with information about the movie, cast, and trailer. Also, users will be able give each movie a rating.
The app will have both light and dark mode, which users will be able to change with the click of a button.
At the end of the section we will also look at how you could take the project further by looking at how you could add features such as user comments or public user profiles.
We'll also use this section to introduce a very popular technology frequently used with Flask: WTForms. This is a library used for creating, validating, and rendering HTML forms. You'll learn how to use the library's components as well as create custom components.
# Rundown of the project
Let's start by looking at the project design, and what users can do!
You can access the finished version of the project here (opens new window). You can register with a fake e-mail and password if you'd like to try it, as there is no e-mail confirmation.
The design is simple and uses straight lines and a single accent colour. The font is Public Sans, a free, open-source font from Google Fonts.
# User signup and login
SIGNUP IMAGE
The user signup form has two password fields which must match. The validation matching is done in Flask using WTForms validators.
LOGIN IMAGE
The user login form only requires e-mail and password.
We will be hashing user passwords before storing them in the database.
# The movies list
Each user will have a list of movies that they've added. Every movie has a few properties, all of which (except the unique ID) are editable:
_id
, a UUID that we will generate.title
, a string.director
, a string.year
, a number.cast
, a list of strings (empty by default).series
, a list of strings (empty by default)tags
, a list of strings (empty by default)last_watched
, adatetime
object that the user can set to be the "current date".rating
, a number between 0 and 5.description
, a string.video_link
, a string which is the YouTube embed link.
When movies are created, most of the properties are set to either None
, 0
, or []
. That's because the "new movie" page only takes the title, director, and year:
NEW MOVIE PAGE
But users can then edit movies to add more details:
EDIT MOVIE PAGE
# Movie details, rating, and watch date
Each movie will have its own page, which shows all the movie data in the database:
MOVIE DETAILS PAGE
The rating stars are clickable. Upon clicking them, we will change the rating of the movie in the database.
GIF OF RATING CHANGE
Similarly, the "last watched date" is clickable. Clicking it sets the "last watched date" to today.
GIF OF WATCH DATE CHANGE
# Dark mode
In the navigation bar there is a button that users can click to switch between light mode and dark mode.
GIF OF CHANGING MODE
In each mode, the colours change, so we will be using CSS variables to greatly simplify our CSS styling in both modes.
# The starter CSS and CSS variables
Let's take a look at the CSS code that we are giving you as part of the starter code:
:root {
--text-dark: #000;
--text-light: #fbf2f2;
--text-muted: #595959;
--background-color: #fff;
--accent-colour: #f56565;
--accent-colour-2: #3bb54a;
--tag-colour: #e5e5e5;
--border: 3px solid #000;
}
.button {
--background-color: #e2e8f0;
--background-color-hover: #bdd1eb;
}
.form__field {
--background-color: #e8e5e5;
}
.form__field,
.nav__link {
--border: 3px solid #f56565;
}
html {
/* Sets global font size on small devices */
font-size: 12px;
}
/* When the screen width hits 960px, we increase the global font size to 14px. This changes
the scale of all of our relative units (the rems), keeping everything in proportion */
@media screen and (min-width: 60em) {
html {
font-size: 14px;
}
}
/* When the screen width hits 1200px, we once again increase the global font size, this time to 16px */
@media screen and (min-width: 75em) {
html {
font-size: 18px;
}
}
body {
/* Sets the shared font characteristics, so that that they can be inherited globally */
font-family: "Public Sans", sans-serif;
color: var(--text-dark);
line-height: 1.45;
background-color: var(--background-color);
}
/* Button styles that we'll share across our site */
.button {
/* In order to easily position our buttons, we're making them block level elements */
display: block;
/* Removes any outlines added when the button is in focus */
outline: none;
/* Setting the cursor to pointer indicates to a user that the button is a clickable element */
cursor: pointer;
/* Again, with buttons we have to be explicit about inheriting font properties */
font-size: inherit;
font-family: inherit;
/* Slows the background colour change effect when we hover over the button, making
it take 0.1s with an accelerating colour change */
transition: background 0.1s ease-in;
}
/* Utility class to use on links within text. */
.link {
text-decoration: none;
color: var(--accent-colour);
white-space: nowrap;
}
.link:hover {
text-decoration: underline;
}
We've added some comments in the code to explain why some CSS properties are there, but the main part is at the top:
:root {
--text-dark: #000;
--text-light: #fbf2f2;
--text-muted: #595959;
--background-color: #fff;
--accent-colour: #f56565;
--accent-colour-2: #3bb54a;
--tag-colour: #e5e5e5;
--border: 3px solid #000;
}
.button {
--background-color: #e2e8f0;
--background-color-hover: #bdd1eb;
}
.form__field {
--background-color: #e8e5e5;
}
.form__field,
.nav__link {
--border: 3px solid #f56565;
}
This doesn't actually change any of the targeted elements. Instead, it sets the values of CSS variables.
A CSS variable starts with --
, and its value can then be accessed using var(--variable-name)
(e.g. var(--border)
).
Look at the --border
variable, for example. It is defined under :root
(the html
element in most cases). That means that any element that is inside the html
element will be able to access the --border
variable, and retrieve the value 3px solid #000
. However, if we access the --border
variable inside an element with class .form__field
or .nav__link
, then the value will be 3px solid #f56565
.
Doing this means that we only have to define values in one place in our application, which will make it easier to change them later on if we need to.
# CSS reset
I've also added a CSS reset to the starter code. A CSS reset is a CSS file that changes certain properties in certain elements so that elements behave in a consistent way across browsers.
Also, some specific elements just behave differently from all other elements, and that can sometimes be confusing. A CSS reset usually makes all elements behave the same way. For example, input
elements have font: inherit
so that they don't use the default system font. Instead, they use the same font as the rest of the HTML.
This is the CSS reset:
/*
1. Use a more-intuitive box-sizing model.
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
/*
2. Remove default margin
- We added padding: 0 ourselves to remove unwanted padding (e.g. from lists)
*/
* {
margin: 0;
padding: 0;
}
/*
3. Allow percentage-based heights in the application
*/
html,
body {
height: 100%;
}
/*
Typographic tweaks!
4. Add accessible line-height
5. Improve text rendering
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
6. Improve media defaults
*/
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/*
7. Remove built-in form typography styles
*/
input,
button,
textarea,
select {
font: inherit;
}
/*
8. Avoid text overflows
*/
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
# Starter HTML
In the starter HTML, layout.html
, I've linked the Public Sans font and the stylesheets provided, as well as set up the Jinja blocks for use in child templates:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>{{ title | default('Movie Watchlist') }}</title>
<link href="https://fonts.googleapis.com/css?family=Public+Sans:400,600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/reset.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" />
{% block head_content %} {% endblock %}
</head>
<body>
<main class="main">
{% block main_content %} {% endblock %}
</main>
</body>
</html>
I've created an index.html
template too, which extends layout.html
. It's basically empty at the moment.
# Our starter Python code
In movie_library.__init__.py
, we have a create_app()
function that should look very familiar to you! It connects to MongoDB and stores the MongoClient
in the Flask app. It also sets the app secret key:
import os
from flask import Flask
from dotenv import load_dotenv
from pymongo import MongoClient
from movie_library.routes import pages
load_dotenv()
def create_app():
app = Flask(__name__)
app.config["MONGODB_URI"] = os.environ.get("MONGODB_URI")
app.config["SECRET_KEY"] = os.environ.get(
"SECRET_KEY", "pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw"
)
app.db = MongoClient(app.config["MONGODB_URI"]).get_default_database()
app.register_blueprint(pages)
return app
# routes.py
In routes.py
I've created a very small Blueprint
, since you already know how they work, so that we don't have to waste time setting it up:
from flask import Blueprint, render_template
pages = Blueprint(
"pages", __name__, template_folder="templates", static_folder="static"
)
@pages.route("/")
def index():
return render_template(
"index.html",
title="Movies Watchlist",
)
# requirements.txt
and .flaskenv
I've set up the requirements.txt
with everything that we will need in this project:
flask
gunicorn
python-dotenv
flask-wtf
pymongo
passlib
Also, while developing a large project such as this one I would recommend you use a formatter (I use black
), and a linter (I use flake8
).
In the .flaskenv
file you'll need this so that Flask can find the app and starts it in debug mode:
FLASK_APP=movie_library
FLASK_ENV=development
# Conclusion
That's it! I strongly recommend you follow along with me in this section, so download the starter code and get familiar with it!
Over the next 20-ish lectures we will be adding a lot of code and functionality to our app. Code with me, and enjoy this section. I'll see you in the next lecture!