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