@leroysheep / MovieWebApp / commits / 3f6697ad5e

started the crud and html templates

Lee Roy Stevenson committed May 14, 2025 at 10:50 UTC 3f6697ad5e827483763316b06d2cd096f3045bc9
14 files changed +301 -32
.gitignore new
+2
@@ -0,0 +1,2 @@
1 +.venv
2 +.env
\ No newline at end of file
app.py new
+84
@@ -0,0 +1,84 @@
1 +
2 +
3 +from flask import Flask, render_template, request
4 +from datamanager.sqlite_data_manager import SQliteDataManager
5 +from data_models import User, Movie, UserMovie
6 +
7 +app = Flask(__name__)
8 +data_manager = SQliteDataManager("sqlite:///movie_app.db")
9 +
10 +@app.route('/')
11 +def home():
12 + return render_template("home.html")
13 +
14 +
15 +@app.route('/users')
16 +def list_users():
17 + users = data_manager.users
18 +
19 + return render_template("users.html",users=users)
20 +
21 +@app.route('/users/<user_id>', methods=["GET", "POST"])
22 +def user_movies(user_id):
23 + if request.method == "GET":
24 + chosen_user = data_manager.get_user(user_id)
25 + user_movies_list = data_manager.get_user_movies(user_id)
26 + return render_template("user_movies.html", user_movies=user_movies_list,
27 + user=chosen_user)
28 + elif request.method == "POST":
29 + chosen_user = data_manager.get_user(user_id)
30 + movie_title = request.form["movie_title"]
31 + with data_manager.SessionFactory() as session:
32 + movie = session.query(Movie).filter_by(name=movie_title).first()
33 +
34 + data_manager.set_user_movies(user_id, movie.id, movie.rating, movie.user_rating)
35 + success = True
36 + user_movies_list = data_manager.get_user_movies(user_id)
37 + return render_template("user_movies.html", user_movies=user_movies_list,
38 + user=chosen_user, success=success)
39 +
40 +@app.route('/users/new', methods=["GET", "POST"])
41 +def new_user():
42 + if request.method == "GET":
43 + return render_template("new_user.html")
44 + elif request.method == "POST":
45 + name = request.form["name"]
46 + user = User(name=name)
47 + with data_manager.SessionFactory() as session:
48 + try:
49 + session.add(user)
50 + session.commit()
51 + success = True
52 + except Exception():
53 + session.rollback()
54 + success = False
55 + return render_template("new_user.html", success=success)
56 +
57 +@app.route('/movies/new', methods=["GET", "POST"])
58 +def new_movie():
59 + if request.method == "GET":
60 + return render_template("new_movie.html")
61 + elif request.method == "POST":
62 + title = request.form["name"]
63 + movie = data_manager.set_movie(title)
64 + if movie:
65 + success = True
66 + else:
67 + success = False
68 + return render_template("new_movie.html", movie=movie, success=success)
69 +
70 +@app.route('/movies', methods=["GET", "POST"])
71 +def list_movies():
72 + if request.method == "GET":
73 + movies = data_manager.movies
74 + return render_template("movies.html",movies=movies)
75 + elif request.method == "POST":
76 + movie_id = request.form["movie_id"]
77 + data_manager.delete_movie(movie_id)
78 + movies = data_manager.movies
79 + return render_template("movies.html",movies=movies)
80 +
81 +if __name__ == "__main__":
82 +
83 + app.run(debug=True, host="127.0.0.1",port=5000)
84 +
data_models.py
+5 -1
@@ -26,7 +26,11 @@ class UserMovie(Base):
26 user_rating = Column('user_rating', Float, default=0.0 )# Add the rating column here
27
28 def __repr__(self):
29 - return f"<UserMovie(user_id={self.user_id}, movie_id={self.movie_id}, rating={self.rating})>"
29 + result = ""
30 + for user in session.query(User).all():
31 + result += (f"<UserMovie(user_id={self.user_id}, movie_id={self.movie_id}, "
32 + f"rating={self.rating})>\n")
33 + return result
34
35 # Define the User model
36 class User(Base):
datamanager/__init__.py
datamanager/data_manager_interface.py renamed
datamanager/database_test.py renamed
+2 -3
@@ -1,8 +1,7 @@
1 import pytest
2 -from sqlalchemy.orm import sessionmaker
2
4 -from sqlite_data_manager import SQliteDataManager
5 -from data_models import Base, User, Movie
3 +from datamanager.sqlite_data_manager import SQliteDataManager
4 +from data_models import User, Movie
5
6 TEST_DB_URL = "sqlite:///:memory:"
7
datamanager/sqlite_data_manager.py renamed
+43 -28
@@ -4,7 +4,8 @@ from sqlalchemy.orm import sessionmaker, joinedload, Session
4 from contextlib import contextmanager
5 from typing import List, Dict, Any
6 from data_models import Base, User, Movie, UserMovie
7 -from data_manager_interface import DataManagerInterface
7 +from movie_api import OMDBClient
8 +from datamanager.data_manager_interface import DataManagerInterface
9
10
11 # Define the database connection string.
@@ -43,8 +44,20 @@ class SQliteDataManager(DataManagerInterface):
44 Getter for users.
45 Returns: a list of User objects.
46 """
46 - with self.get_db() as db:
47 - return db.query(User).all()
47 + with self.SessionFactory() as session:
48 + users = session.query(User).all()
49 + return users
50 +
51 +
52 + def get_user(self, user_id: int) -> User:
53 + """
54 + Get a user by ID.
55 + :param user_id:
56 + :return: User
57 + """
58 + with self.SessionFactory() as session:
59 + user = session.query(User).filter_by(id=user_id).first()
60 + return user
61
62 def add_user(self, user: User) -> None:
63 """
@@ -60,7 +73,7 @@ class SQliteDataManager(DataManagerInterface):
73 Getter for movies.
74 Returns: a list of Movie objects
75 """
63 - with self.get_db() as db:
76 + with self.SessionFactory() as db:
77 return db.query(Movie).options(joinedload(Movie.users)).all()
78
79 def set_user_movies(self, user_id: int, movie_id: int, rating: float, user_rating: float = 0.0)\
@@ -99,15 +112,25 @@ class SQliteDataManager(DataManagerInterface):
112 print(f"Added movie {movie_id} to user {user_id} with rating {rating}")
113
114 db.commit() # Commit within the function
115 +
116 + elif user and not movie:
117 + try:
118 + self.set_movie(movie_title=movie.name)
119 + self.set_user_movies(user_id=user_id, movie_id=movie_id, rating=rating,
120 + user_rating=user_rating)
121 + except ValueError as e:
122 + print(f"Failed to add movie: {e}")
123 +
124 else:
125 print("User or movie not found.")
126
127 def get_user_movies(self, user_id: int) -> List[Dict[str, Any]]:
128 """
129 Get movies for a specific user with their ratings.
108 - Returns: A list of dictionaries, where each dictionary contains movie details and the user's rating.
130 + Returns: A list of dictionaries, where each dictionary contains movie details
131 + (name, director, year, poster) and the user's rating.
132 """
110 - with self.get_db() as db:
133 + with self.SessionFactory() as db:
134 user = db.query(User).options(joinedload(User.movies)).filter_by(id=user_id).first()
135 if user:
136 movies_with_ratings = []
@@ -127,13 +150,18 @@ class SQliteDataManager(DataManagerInterface):
150 return []
151
152
130 - def set_movie(self, movie: Movie) -> None:
153 + def set_movie(self, movie_title: str) -> Movie:
154 """
155 Add a new movie to the database.
156 """
134 - with self.get_db() as db:
157 + new_movie = OMDBClient().get_movie(title=movie_title)
158 + if new_movie is None:
159 + raise ValueError("Movie not found")
160 + movie = Movie(**new_movie)
161 + with self.SessionFactory() as db:
162 db.add(movie)
163 db.commit()
164 + return movie
165
166 def update_movie(self, movie_id: int, update_data: dict) -> Movie | None:
167 session = self.SessionFactory()
@@ -158,8 +186,8 @@ class SQliteDataManager(DataManagerInterface):
186 Returns:
187 True if the movie was successfully deleted, False otherwise.
188 """
161 - engine = self.engine
162 - with Session(engine) as db:
189 +
190 + with self.SessionFactory() as db:
191 movie = db.query(Movie).options(joinedload(Movie.users)).filter_by(id=movie_id).first()
192 if movie:
193 # Remove the movie from all associated users' movie lists
@@ -171,7 +199,7 @@ class SQliteDataManager(DataManagerInterface):
199 return False
200
201 def main():
174 - data_manager = SQliteDataManager(TEST_DB_URL)
202 + data_manager = SQliteDataManager("sqlite:///movie_app.db")
203
204 with data_manager.SessionFactory() as session:
205 # Create some users
@@ -183,12 +211,10 @@ def main():
211 session.commit()
212
213 # Create some movies
186 - movie1 = Movie(name="The Matrix", director="Wachowskis", year=1999, poster="matrix.jpg",
187 - rating=8.7)
188 - movie2 = Movie(name="Inception", director="Nolan", year=2010, poster="inception.jpg",
189 - rating=8.8)
190 - data_manager.set_movie(movie1)
191 - data_manager.set_movie(movie2)
214 + movie1 = Movie(name="The Matrix")
215 + movie2 = Movie(name="Inception")
216 + movie1 = data_manager.set_movie(movie1.name)
217 + movie2 = data_manager.set_movie(movie2.name)
218 session.add_all([movie1, movie2])
219 session.commit()
220
@@ -223,17 +249,6 @@ def main():
249 print("All movies after deletion:", session.query(Movie).all())
250
251
226 -
227 -
228 -
229 -
230 -
231 -
232 -
233 -
234 -
235 -
236 -
252 if __name__ == "__main__":
253 main()
254
movie_api.py new
+24
@@ -0,0 +1,24 @@
1 +import dotenv
2 +import os
3 +import requests
4 +
5 +
6 +dotenv.load_dotenv()
7 +
8 +API_KEY = os.getenv("OMDB_API_KEY")
9 +
10 +BASE_URL = "http://www.omdbapi.com/?apikey=" + API_KEY
11 +
12 +class OMDBClient:
13 + def __init__(self):
14 + self.BASE_URL = BASE_URL
15 +
16 + def get_movie(self, title: str) -> dict | None:
17 + url = self.BASE_URL + "&t=" + title
18 + response = requests.get(url)
19 + if response.status_code == 200:
20 + new_movie = {"name": response.json()["Title"], "director": response.json()["Director"],
21 + "year": response.json()["Year"], "poster": response.json()["Poster"],
22 + "rating": response.json()["imdbRating"]}
23 + return new_movie
24 + return None
\ No newline at end of file
templates/home.html new
+14
@@ -0,0 +1,14 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>Movie - WebApp</title>
6 +</head>
7 +<body>
8 + <h1>Welcome to the Movie Web App</h1>
9 + <ul>
10 + <li><a href="/users" methods="GET">Users</a></li>
11 + <li><a href="/movies" methods="GET">Movies</a></li>
12 + </ul>
13 +</body>
14 +</html>
\ No newline at end of file
templates/movies.html new
+25
@@ -0,0 +1,25 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>Movies List</title>
6 +</head>
7 +<body>
8 +<h1>Movies List</h1>
9 +
10 + {% for movie in movies %}
11 + <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
12 + <ul>
13 + <li><a href="/movies/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li>
14 + <form action="/movies" method="post">
15 + <button type="submit" name = "movie_id" value ="{{ movie.id }}">
16 + Delete</button></form>
17 + </ul>
18 + {% endfor %}
19 + <a href="/">Home</a>
20 + <a href="/users">Users</a>
21 + <a href="/movies/new">Add Movie</a>
22 + <a href="/users/new">Add User</a>
23 +
24 +</body>
25 +</html>
\ No newline at end of file
templates/new_movie.html new
+24
@@ -0,0 +1,24 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>Add new Movie</title>
6 +</head>
7 +<body>
8 + <h1>Add new Movie</h1>
9 + {% if success == True %}
10 + <p>Movie added successfully!</p>
11 + {% elif success == False %}
12 + <p>Failed to add movie. Please try again.</p>
13 + {% elif success == None %}
14 + {% endif %}
15 + <form action="/movies/new" method="POST">
16 + <label for="name">Name:</label>
17 + <input type="text" id="name" name="name" required>
18 + <button type="submit">Add Movie</button>
19 + </form>
20 + <a href="/">Home</a>
21 + <a href="/users">Users</a>
22 + <a href="/movies">Movies</a>
23 +</body>
24 +</html>
\ No newline at end of file
templates/new_user.html new
+24
@@ -0,0 +1,24 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>Add new User</title>
6 +</head>
7 +<body>
8 + <h1>Add new User</h1>
9 + {% if success == True %}
10 + <p>User added successfully!</p>
11 + {% elif success == False %}
12 + <p>Failed to add user. Please try again.</p>
13 + {% elif success == None %}
14 + {% endif %}
15 + <form action="/users/new" method="POST">
16 + <label for="name">Name:</label>
17 + <input type="text" id="name" name="name" required>
18 + <button type="submit">Add User</button>
19 + </form>
20 + <a href="/">Home</a>
21 + <a href="/users">Users</a>
22 + <a href="/movies">Movies</a>
23 +</body>
24 +</html>
\ No newline at end of file
templates/user_movies.html new
+35
@@ -0,0 +1,35 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>{{user.name}} - Movie List</title>
6 +</head>
7 +<body>
8 + <h1>{{user.name}} - Movie List</h1>
9 +
10 + <header>
11 + {% if success == True %}
12 + <p>Movie added successfully!</p>
13 + {% elif success == False %}
14 + <p>Failed to add movie. Please try again.</p>
15 + {% elif success == None %}
16 + {% endif %}
17 + <form action="/users/{{user.id}}" method="POST">
18 + <label for="name">Name:</label>
19 + <input type="text" id="name" name="name" required>
20 + <button type="submit">Add Movie</button>
21 + </form>
22 + </header>
23 + {% for movie in user_movies %}
24 + <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
25 + <ul><li>{{ movie.name }} ({{ movie.year }})</li>
26 + <li>Director: {{ movie.director }}</li>
27 + <li>Rating: <input type="number" id="rating" name="rating" required> {{ movie.rating }} <form action="/users/{{user.id}}/{{movie.id}}"
28 + method="UPDATE"><button type="submit">Update
29 + </button></form></li>
30 + <hr>
31 + </ul>
32 + {% endfor %}
33 +
34 +</body>
35 +</html>
\ No newline at end of file
templates/users.html new
+19
@@ -0,0 +1,19 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="UTF-8">
5 + <title>Users - MovieWeb App</title>
6 +</head>
7 +<body>
8 +<h1>Users</h1>
9 +<ul><a href="/users/new" methods="GET">Add User</a></ul>
10 +<ul><a href="/" methods="GET">Home</a></ul>
11 +<ul>
12 +{% for user in users %}
13 +
14 +<li><a href="/users/{{user.id}}" methods="GET">{{user.name}}</a></li>
15 +{% endfor %}
16 +</ul>
17 +
18 +</body>
19 +</html>
\ No newline at end of file