Project 4: Movie Watchlist: Adding user signups to the movie watchlist (+106, -4)

forms.py (+33, -0)

From: curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/forms.py

To: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/forms.py

            
            index e8d61eb..a54f8b8 100644
--- a/curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/forms.py
+++ b/curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/forms.py
@@ -1,6 +1,7 @@
 from flask_wtf import FlaskForm
 from wtforms import (
     IntegerField,
+    PasswordField,
     StringField,
     SubmitField,
     TextAreaField,
@@ -9,10 +10,42 @@ from wtforms import (
 
 from wtforms.validators import (
     InputRequired,
+    Email,
+    EqualTo,
+    Length,
     NumberRange,
 )
 
 
+class RegisterForm(FlaskForm):
+    email = StringField("Email", validators=[InputRequired(), Email()])
+
+    password = PasswordField(
+        "Password",
+        validators=[
+            InputRequired(),
+            Length(
+                min=4,
+                max=20,
+                message="Your password must be between 4 and 20 characters long.",
+            ),
+        ],
+    )
+
+    confirm_password = PasswordField(
+        "Confirm Password",
+        validators=[
+            InputRequired(),
+            EqualTo(
+                "password",
+                message="This password did not match the one in the password field.",
+            ),
+        ],
+    )
+
+    submit = SubmitField("Register")
+
+
 class MovieForm(FlaskForm):
     title = StringField("Title", validators=[InputRequired()])
     director = StringField("Director", validators=[InputRequired()])
        

models.py (+8, -0)

From: curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/models.py

To: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/models.py

            
            index 2c19798..2234bca 100644
--- a/curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/models.py
+++ b/curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/models.py
@@ -15,3 +15,11 @@ class Movie:
     tags: list[str] = field(default_factory=list)
     description: str = None
     video_link: str = None
+
+
+@dataclass
+class User:
+    _id: str
+    email: str
+    password: str
+    movies: list[str] = field(default_factory=list)
        

routes.py (+29, -2)

From: curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/routes.py

To: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/routes.py

            
            index 8ebf9ec..10458d2 100644
--- a/curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/routes.py
+++ b/curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/routes.py
@@ -5,14 +5,16 @@ from dataclasses import asdict
 from flask import (
     Blueprint,
     current_app,
+    flash,
     redirect,
     render_template,
     session,
     url_for,
     request,
 )
-from movie_library.forms import MovieForm, ExtendedMovieForm
-from movie_library.models import Movie
+from movie_library.forms import RegisterForm, MovieForm, ExtendedMovieForm
+from movie_library.models import User, Movie
+from passlib.hash import pbkdf2_sha256
 
 
 pages = Blueprint(
@@ -32,6 +34,31 @@ def index():
     )
 
 
+@pages.route("/register", methods=["POST", "GET"])
+def register():
+    if session.get("email"):
+        return redirect(url_for(".index"))
+
+    form = RegisterForm()
+
+    if form.validate_on_submit():
+        user = User(
+            _id=uuid.uuid4().hex,
+            email=form.email.data,
+            password=pbkdf2_sha256.hash(form.password.data),
+        )
+
+        current_app.db.user.insert_one(asdict(user))
+
+        flash("User registered successfully", "success")
+
+        return redirect(url_for(".index"))
+
+    return render_template(
+        "register.html", title="Movies Watchlist - Register", form=form
+    )
+
+
 @pages.route("/add", methods=["GET", "POST"])
 def add_movie():
     form = MovieForm()
        

nav.html (+2, -2)

From: curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/templates/macros/nav.html

To: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/templates/macros/nav.html

            
            index 72b17c5..116ad0a 100644
--- a/curriculum/section14/lectures/15_user_signup_in_flask_app/start/movie_library/templates/macros/nav.html
+++ b/curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/templates/macros/nav.html
@@ -17,8 +17,8 @@
                     <span class="nav__item">Log in</span>
                 </a>
                 <a
-                    href="#"
-                    class="nav__link"
+                    href="{{ url_for('pages.register') }}"
+                    class="nav__link {{ 'nav__link--active' if request.path == url_for('pages.register') }}"
                 >
                     <span class="nav__item">Register</span>
                 </a>
        

register.html (+34, -0)

From: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/templates/register.html

To: curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/templates/register.html

            
            new file mode 100644
index 0000000..55c61ad
--- /dev/null
+++ b/curriculum/section14/lectures/15_user_signup_in_flask_app/end/movie_library/templates/register.html
@@ -0,0 +1,34 @@
+{% from "macros/fields.html" import render_text_field %}
+
+{% extends "layout.html" %} 
+
+{%- block head_content %}
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/forms.css') }}" />
+{% endblock %} 
+
+{% block main_content %}
+
+    <form name="register" method="post" novalidate class="form">
+        {% with messages = get_flashed_messages(with_categories=true) %}
+            {%- for category, message in messages %}
+                <span class="form__flash form__flash--{{category}}"> {{ message }}</span>
+            {% endfor %}
+        {% endwith %}
+        <div class="form__container">
+            {{ form.hidden_tag() }}
+
+            {{ render_text_field(form.email) }}
+            {{ render_text_field(form.password) }}
+            {{ render_text_field(form.confirm_password) }}
+    
+            <span class="form__small">
+                Already have an account? <a href="#" class="form__link">Log in here</a>.
+            </span>
+    
+            <div>
+                {{ form.submit(class_="button button--form") }}
+            </div>
+        </div>
+    </form>
+
+{% endblock %}