@leroysheep / MovieWebApp / commits / 30dcb11174

added some stile and tested ai api

Lee Roy Stevenson committed May 20, 2025 at 05:43 UTC 30dcb111747c939d4413b97a27e11923224ee31f
17 files changed +565 -133
app.py
+82 -21
@@ -1,22 +1,40 @@
1 -from django.contrib.messages import success
1 from flask import Flask, render_template, request
2 +
3 +from ai_request import AIRequest
4 from datamanager.sqlite_data_manager import SQliteDataManager
4 -from data_models import User, Movie, UserMovie
5 +from data_models import User, Movie
6 +from movie_api import OMDBClient
7
8 app = Flask(__name__)
9 +app.config['ENV'] = 'production'
10 +
11 data_manager = SQliteDataManager("sqlite:///movie_app.db")
12
13 +# Error handlers
14 +@app.errorhandler(404)
15 +def page_not_found(e):
16 + return render_template("404.html"), 404
17 +
18 +
19 +@app.errorhandler(500)
20 +def internal_server_error(e):
21 + return render_template("500.html"), 500
22 +
23 +
24 +# Home Route with simple navigation
25 @app.route('/')
26 def home():
27 return render_template("home.html")
28
29
30 +# Users in a list view
31 @app.route('/users')
32 def list_users():
33 users = data_manager.users
17 -
34 return render_template("users.html",users=users)
35
36 +
37 +# User movies in a list view
38 @app.route('/users/<user_id>', methods=["GET", "POST"])
39 def user_movies(user_id):
40 if request.method == "GET":
@@ -41,14 +59,16 @@ def user_movies(user_id):
59 user=chosen_user, success=True)
60 except Exception as e:
61 session.rollback()
62 + user_movies_list = data_manager.get_user_movies(user_id)
63 return render_template("user_movies.html",
64 user_movies=user_movies_list,
46 - user=chosen_user, success=False)
65 + user=chosen_user, success=False, error=e)
66 else:
67 if data_manager.get_user_movie(user_id, movie.id):
68 return render_template("user_movies.html",
69 user_movies=data_manager.get_user_movies(user_id),
51 - user=chosen_user, success=False)
70 + user=chosen_user, success=False,
71 + error="Movie already exists")
72 try:
73 data_manager.set_user_movies(user_id=user_id, movie_id=movie.id)
74 user_movies_list = data_manager.get_user_movies(user_id)
@@ -58,10 +78,11 @@ def user_movies(user_id):
78 except Exception as e:
79 session.rollback()
80 return render_template("user_movies.html",
61 - user_movies=user_movies_list,
62 - user=chosen_user, success=False)
81 + user_movies=data_manager.get_user_movies(user_id),
82 + user=chosen_user, success=False, error=e)
83
84
85 +# single user movie view for updating
86 @app.route('/users/<user_id>/<movie_id>', methods=["GET", "POST"])
87 def update_user_movie(user_id, movie_id):
88 if request.method == "GET":
@@ -75,18 +96,20 @@ def update_user_movie(user_id, movie_id):
96 movie = data_manager.get_movie(movie_id)
97 user_movie = data_manager.get_user_movie(user_id, movie_id)
98 user_rating = request.form["user_rating"]
99 + user_comment = request.form["user_comment"] if "user_comment" in request.form else None
100 try:
101 data_manager.update_user_movie(user_id=user_id, movie_id=movie_id,
80 - update_data={"user_rating": user_rating})
102 + update_data={"user_rating": user_rating,
103 + "user_comment": user_comment})
104 new_user_movie = data_manager.get_user_movie(user_id, movie_id)
105 return render_template("user_movie.html", user_movie=new_user_movie,
106 user=chosen_user, movie=movie, success=True)
107 except Exception as e:
108 return render_template("user_movie.html", user_movie=user_movie,
86 - user=chosen_user, movie=movie, success=False)
87 -
109 + user=chosen_user, movie=movie, success=False, error=e)
110
111
112 +# Adding new users
113 @app.route('/users/new', methods=["GET", "POST"])
114 def new_user():
115 if request.method == "GET":
@@ -98,25 +121,30 @@ def new_user():
121 try:
122 session.add(user)
123 session.commit()
101 - success = True
102 - except Exception():
124 + return render_template("new_user.html", success=True)
125 + except Exception as e:
126 session.rollback()
104 - success = False
105 - return render_template("new_user.html", success=success)
127 + return render_template("new_user.html", success=False, error=e)
128
129 +
130 +# Adding new movies
131 @app.route('/movies/new', methods=["GET", "POST"])
132 def new_movie():
133 if request.method == "GET":
134 return render_template("new_movie.html")
135 elif request.method == "POST":
136 title = request.form["name"]
113 - movie = data_manager.set_movie(title)
114 - if movie:
115 - movie_added = True
116 - else:
117 - movie_added = False
118 - return render_template("new_movie.html", movie=movie, success=movie_added)
137 + try:
138 + newest_movie = data_manager.set_movie(title)
139 + return render_template("new_movie.html",
140 + movie_name=newest_movie.name, success=True)
141 + except Exception as e:
142 + error_msg = str(e)
143 + return render_template("new_movie.html", movie_name=title,
144 + success=False, error = error_msg)
145
146 +
147 +# see list of all movies in database
148 @app.route('/movies', methods=["GET", "POST"])
149 def list_movies():
150 if request.method == "GET":
@@ -128,6 +156,20 @@ def list_movies():
156 movies = data_manager.movies
157 return render_template("movies.html", movies=movies, success=deleted)
158
159 +
160 +# get details of a single movie
161 +@app.route('/movies/<movie_id>', methods=["GET", "POST"])
162 +def movie_details(movie_id):
163 + if request.method == "GET":
164 + movie = data_manager.get_movie(movie_id)
165 + return render_template("movie_details.html", movie=movie)
166 + elif request.method == "POST":
167 + movie_id = request.form["movie_id"]
168 + deleted = data_manager.delete_movie(int(movie_id))
169 + movies = data_manager.movies
170 + return render_template("movies.html", movies=movies, success=deleted)
171 +
172 +# delete user movie from database
173 @app.route('/users/<user_id>/delete/<movie_id>', methods=["GET", "POST"])
174 def delete_user_movie(user_id, movie_id):
175 if request.method == "GET":
@@ -144,7 +186,26 @@ def delete_user_movie(user_id, movie_id):
186 return render_template("user_movies.html", user_movies=user_movies_list,
187 user=chosen_user, movie=movie, movie_deleted=deleted)
188
189 +
190 +# movie recommendation from AI
191 +@app.route('/users/<user_id>/recommend_movie', methods=["GET", "POST"])
192 +def recommendation(user_id):
193 + if request.method == "GET":
194 + chosen = data_manager.get_user(user_id)
195 + return render_template("recommend.html", user=chosen)
196 + elif request.method == "POST":
197 + chosen= data_manager.get_user(user_id)
198 + data_string = ""
199 + for movie in data_manager.get_user_movies(user_id):
200 + data_string += f"Title: {movie["name"]} User Rating: {movie['user_rating']},"
201 + recommend = AIRequest().ai_request(data_string)
202 + poster = OMDBClient().get_movie(recommend["movie"]["title"])
203 + recommend["movie"]["poster"] = poster["poster"]
204 + recommend["movie"]["imdb"] = poster["rating"]
205 + return render_template("recommend.html", user=chosen, movie=recommend["movie"], reasoning=recommend["reasoning"])
206 +
207 +
208 if __name__ == "__main__":
209
149 - app.run(debug=True,port=5000)
210 + app.run(debug=True, host='0.0.0.0', port=5001)
211
data_models.py
+7 -2
@@ -22,7 +22,8 @@ class UserMovie(Base):
22 id = Column(Integer, primary_key=True)
23 user_id = Column('user_id', Integer, ForeignKey('users.id'))
24 movie_id = Column('movie_id', Integer, ForeignKey('movies.id'))
25 - user_rating = Column('user_rating', Float, default=0.0 )# Add the rating column here
25 + user_rating = Column('user_rating', Float, default=0.0 )
26 + user_comment = Column('user_comment', String)
27 users = relationship("User", back_populates="user_movies", overlaps="users,movies")
28 movies = relationship("Movie", back_populates="user_movies", overlaps="users,movies")
29
@@ -37,7 +38,8 @@ class User(Base):
38 name = Column(String)
39 movies = relationship("Movie", secondary="user_movies",
40 back_populates="users", overlaps="user_movies,movies")
40 - user_movies = relationship("UserMovie", back_populates="users", overlaps="user_movies,movies")
41 + user_movies = relationship("UserMovie", back_populates="users",
42 + overlaps="user_movies,movies")
43
44 def __repr__(self):
45 return f"<User(name='{self.name}', id={self.id if self.id else 'None'})>"
@@ -52,6 +54,9 @@ class Movie(Base):
54 year = Column(Integer)
55 poster = Column(String)
56 rating = Column(Float)
57 + genre = Column(String)
58 + country = Column(String)
59 + plot = Column(String)
60 users = relationship("User", secondary="user_movies",
61 back_populates="movies", overlaps="user_movies,users")
62 user_movies = relationship("UserMovie", back_populates="movies",
datamanager/sqlite_data_manager.py
+22 -3
@@ -39,6 +39,7 @@ class SQliteDataManager(DataManagerInterface):
39 finally:
40 session.close()
41
42 +
43 @property
44 def users(self) -> list[Type[User]]:
45 """
@@ -68,6 +69,7 @@ class SQliteDataManager(DataManagerInterface):
69 session.add(user)
70 session.commit()
71
72 +
73 @property
74 def movies(self) -> list[Type[Movie]]:
75 """
@@ -131,8 +133,13 @@ class SQliteDataManager(DataManagerInterface):
133 "director": movie.director,
134 "year": movie.year,
135 "poster": movie.poster,
136 + "genre": movie.genre,
137 + "country": movie.country,
138 + "plot": movie.plot,
139 "rating": movie.rating,
135 - "user_rating": association.user_rating
140 + "user_rating": association.user_rating,
141 + "user_comment": association.user_comment if association.user_comment
142 + else None
143 })
144 return movies_with_ratings
145 return []
@@ -149,8 +156,12 @@ class SQliteDataManager(DataManagerInterface):
156 "director": movie.director,
157 "year": movie.year,
158 "poster": movie.poster,
159 + "genre": movie.genre,
160 + "country": movie.country,
161 + "plot": movie.plot,
162 "rating": movie.rating,
163 "user_rating": user_movie.user_rating,
164 + "user_comment": user_movie.user_comment if user_movie.user_comment else None
165 }
166 return {}
167
@@ -176,7 +187,8 @@ class SQliteDataManager(DataManagerInterface):
187 with self.SessionFactory() as session:
188 movie = session.query(Movie).filter_by(id=movie_id).first()
189 if movie:
179 - session.query(UserMovie).filter_by(user_id=user_id, movie_id=movie_id).update(update_data)
190 + session.query(UserMovie).filter_by(user_id=user_id,
191 + movie_id=movie_id).update(update_data)
192 session.commit()
193 return {
194 "id": movie.id,
@@ -184,7 +196,14 @@ class SQliteDataManager(DataManagerInterface):
196 "director": movie.director,
197 "year": movie.year,
198 "poster": movie.poster,
187 - "user_rating": update_data["user_rating"]
199 + "genre": movie.genre,
200 + "country": movie.country,
201 + "plot": movie.plot,
202 + "rating": movie.rating,
203 + "user_rating": update_data["user_rating"] if "user_rating" in update_data
204 + else 0.0,
205 + "user_comment": update_data["user_comment"] if "user_comment" in update_data
206 + else None
207 }
208 return None
209
movie_api.py
+3 -2
@@ -16,9 +16,10 @@ class OMDBClient:
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:
19 + if response.status_code == 200 and response.json()["Response"] == "True":
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"]}
22 + "country": response.json()["Country"], "genre": response.json()["Genre"],
23 + "plot": response.json()["Plot"], "rating": response.json()["imdbRating"]}
24 return new_movie
25 return None
\ No newline at end of file
requirements.txt
+57 -8
@@ -1,8 +1,57 @@
1 -flask~=3.1.0
2 -Django~=5.2.1
3 -pytest~=8.3.5
4 -SQLAlchemy~=2.0.40
5 -dotenv~=0.9.9
6 -python-dotenv~=1.1.0
7 -requests~=2.32.3
8 -google~=3.0.0
\ No newline at end of file
1 +annotated-types==0.7.0
2 +anyio==4.9.0
3 +asgiref==3.8.1
4 +beautifulsoup4==4.13.4
5 +blinker==1.9.0
6 +cachetools==5.5.2
7 +certifi==2025.4.26
8 +charset-normalizer==3.4.2
9 +click==8.1.8
10 +contourpy==1.3.2
11 +cycler==0.12.1
12 +Django==5.2.1
13 +django-request==1.7.0
14 +dotenv==0.9.9
15 +exception==0.1.0
16 +Flask==3.1.0
17 +Flask-SQLAlchemy==3.1.1
18 +fonttools==4.58.0
19 +google==3.0.0
20 +google-auth==2.40.1
21 +google-genai==1.15.0
22 +h11==0.16.0
23 +httpcore==1.0.9
24 +httpx==0.28.1
25 +idna==3.10
26 +iniconfig==2.1.0
27 +itsdangerous==2.2.0
28 +Jinja2==3.1.6
29 +kiwisolver==1.4.8
30 +MarkupSafe==3.0.2
31 +matplotlib==3.10.3
32 +numpy==2.2.5
33 +packaging==25.0
34 +pillow==11.2.1
35 +pluggy==1.5.0
36 +pyasn1==0.6.1
37 +pyasn1_modules==0.4.2
38 +pydantic==2.11.4
39 +pydantic_core==2.33.2
40 +pyparsing==3.2.3
41 +pytest==8.3.5
42 +python-dateutil==2.9.0.post0
43 +python-dotenv==1.1.0
44 +requests==2.32.3
45 +response==0.5.0
46 +rsa==4.9.1
47 +scipy==1.15.3
48 +six==1.17.0
49 +sniffio==1.3.1
50 +soupsieve==2.7
51 +SQLAlchemy==2.0.40
52 +sqlparse==0.5.3
53 +typing-inspection==0.4.0
54 +typing_extensions==4.13.0
55 +urllib3==2.4.0
56 +websockets==15.0.1
57 +Werkzeug==3.1.3
static/styles.css
+220 -18
@@ -1,29 +1,231 @@
1 -/* Add a black background color to the top navigation */
1 +/* General reset */
2 +body {
3 + background-color: black;
4 + color: darkgreen;
5 + font-family: Arial, sans-serif;
6 + margin: 0;
7 + padding: 0;
8 +}
9 +
10 +/* Navigation */
11 .topnav {
3 - border: 1px solid black;
4 - border-radius: 5px;
5 - background-color: #333;
6 - overflow: hidden;
12 + background-color: black;
13 + display: flex;
14 + gap: 15px;
15 + padding: 12px 20px;
16 + border-bottom: 2px solid darkred;
17 + justify-content: center;
18 }
19
9 -/* Style the links inside the navigation bar */
20 .topnav a {
11 - float: left;
12 - color: #f2f2f2;
13 - text-align: center;
14 - padding: 14px 16px;
21 + color: darkgreen;
22 text-decoration: none;
16 - font-size: 17px;
23 + padding: 8px 14px;
24 + border-radius: 8px;
25 + transition: background-color 0.3s ease, color 0.3s ease;
26 }
27
19 -/* Change the color of links on hover */
28 +.topnav a.active,
29 .topnav a:hover {
21 - background-color: #ddd;
30 + background-color: darkgreen;
31 + color: darkred;
32 +}
33 +
34 +/* Title styling */
35 +h1.title {
36 + text-align: center;
37 + margin: 20px 0;
38 + color: darkgreen;
39 +}
40 +
41 +/* Status message */
42 +.status-message p {
43 + text-align: center;
44 + font-weight: bold;
45 + color: darkgreen;
46 + margin-bottom: 20px;
47 +}
48 +
49 +/* Carousel container with horizontal scroll */
50 +.css-carousel {
51 + display: flex;
52 + overflow-x: auto;
53 + scroll-snap-type: x mandatory;
54 + -webkit-overflow-scrolling: touch;
55 + padding-bottom: 12px;
56 + scrollbar-width: thin;
57 + scrollbar-color: darkred black;
58 + border-bottom: 2px solid darkred;
59 + scroll-behavior: smooth;
60 + color: darkgreen;
61 + max-width: 100%;
62 +}
63 +
64 +/* Scrollbar styling */
65 +.css-carousel::-webkit-scrollbar {
66 + height: 8px;
67 +}
68 +
69 +.css-carousel::-webkit-scrollbar-track {
70 + background: black;
71 +}
72 +
73 +.css-carousel::-webkit-scrollbar-thumb {
74 + background-color: darkred;
75 + border-radius: 10px;
76 + border: 2px solid black;
77 +}
78 +
79 +/* Scroll instruction text */
80 +.scroll-instruction {
81 + color: darkgreen;
82 + font-size: 0.9rem;
83 + text-align: center;
84 + margin-top: 6px;
85 + font-style: italic;
86 + user-select: none;
87 +}
88 +
89 +/* Individual movie card */
90 +.carousel-item {
91 + flex: 0 0 auto;
92 + scroll-snap-align: center;
93 + background-color: #111;
94 + border: 1px solid darkgreen;
95 + border-radius: 12px;
96 + margin: 0 10px;
97 + padding: 15px;
98 + max-width: 250px;
99 + color: darkgreen;
100 + box-sizing: border-box;
101 + display: flex;
102 + flex-direction: column;
103 + align-items: center;
104 + transition: transform 0.3s ease;
105 +}
106 +
107 +/* Slight scale on hover */
108 +.carousel-item:hover {
109 + transform: scale(1.05);
110 + border-color: darkred;
111 +}
112 +
113 +/* Movie image */
114 +.carousel-item img {
115 + border-radius: 10px;
116 + max-width: 100%;
117 + height: auto;
118 + margin-bottom: 10px;
119 + object-fit: cover;
120 +}
121 +
122 +/* Movie info list */
123 +.carousel-item ul {
124 + list-style: none;
125 + padding-left: 0;
126 + width: 100%;
127 + margin: 0;
128 + color: darkgreen;
129 +}
130 +
131 +.carousel-item ul li {
132 + margin: 5px 0;
133 + font-size: 0.9rem;
134 + color: darkgreen;
135 +}
136 +
137 +.carousel-item ul li a {
138 + color: darkgreen;
139 + text-decoration: none;
140 + transition: color 0.3s ease;
141 +}
142 +
143 +.carousel-item ul li a:hover {
144 + color: darkred;
145 +}
146 +
147 +
148 +
149 +/* Delete button styling */
150 +button,
151 +.back_button a{
152 + background-color: darkred;
153 + color: darkgreen;
154 + border: none;
155 + border-radius: 8px;
156 + padding: 8px 12px;
157 + cursor: pointer;
158 + font-size: 1rem;
159 + transition: background-color 0.3s ease, color 0.3s ease;
160 +}
161 +
162 +button:hover,
163 +.back_button a:hover {
164 + background-color: darkgreen;
165 + color: darkred;
166 +}
167 +
168 +.actions {
169 + display: flex;
170 + flex-wrap: wrap;
171 + gap: 20px; /* space between forms */
172 + justify-content: center;
173 + margin-bottom: 20px;
174 +}
175 +
176 +.action-form {
177 + background-color: #1a0000; /* very dark red background */
178 + padding: 15px;
179 + border-radius: 12px;
180 + display: flex;
181 + flex-direction: column;
182 + align-items: center;
183 + min-width: 220px;
184 + box-sizing: border-box;
185 +}
186 +
187 +.action-form label {
188 + margin-bottom: 8px;
189 + color: darkgreen;
190 +}
191 +
192 +.action-form input[type="text"] {
193 + padding: 8px;
194 + border-radius: 10px;
195 + border: none;
196 + margin-bottom: 12px;
197 + width: 100%;
198 + max-width: 200px;
199 +}
200 +
201 +.action-form button {
202 + background-color: darkred;
203 + color: darkgreen;
204 + border: none;
205 + padding: 10px 16px;
206 + border-radius: 12px;
207 + cursor: pointer;
208 + transition: background-color 0.3s ease;
209 + width: 100%;
210 + max-width: 200px;
211 +}
212 +
213 +.action-form button:hover {
214 + background-color: darkgreen;
215 color: black;
216 }
217
25 -/* Add a color to the active/current link */
26 -.topnav a.active {
27 - background-color: #04AA6D;
28 - color: white;
29 -}
\ No newline at end of file
218 +
219 +/* Responsive tweaks */
220 +@media (max-width: 600px) {
221 + .carousel-item {
222 + max-width: 180px;
223 + padding: 10px;
224 + }
225 +}
226 +
227 +@media (max-width: 400px) {
228 + .carousel-item {
229 + max-width: 150px;
230 + }
231 +}
templates/404.html new
+5
@@ -0,0 +1,5 @@
1 +{% extends "base.html" %}
2 +{% block title %}404{% endblock %}
3 +{% block content %}
4 + <h1>404 Error: Page Not Found!</h1>
5 +{% endblock %}
\ No newline at end of file
templates/500.html new
+5
@@ -0,0 +1,5 @@
1 +{% extends "base.html" %}
2 +{% block title %}500{% endblock %}
3 +{% block content %}
4 + <h1>500 Error: Internal Server Error.</h1>
5 +{% endblock %}
\ No newline at end of file
templates/base.html
+13 -12
@@ -1,22 +1,23 @@
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 - <meta charset="UTF-8">
5 - <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 - <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 -
4 + <meta charset="UTF-8" />
5 + <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>{% block title %}{% endblock %}</title>
9 - <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
7 + <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
8 </head>
9 <body>
10 <header class="topnav">
13 - <a class="active" href="/">Home</a>
14 - <a href="/users">Users</a>
15 - <a href="/movies">Movies</a>
16 - <a href="/users/new">New User</a>
17 - <a href="/movies/new">New Movie</a>
18 -
11 + {% set path = request.path %}
12 + <a href="/" class="{% if path == '/' %}active{% endif %}">Home</a>
13 + <a href="/users" class="{% if path.startswith('/users') and not path.endswith('/new') %}active{% endif %}">Users</a>
14 + <a href="/movies" class="{% if path.startswith('/movies') and not path.endswith('/new') %}active{% endif %}">Movies</a>
15 + <a href="/users/new" class="{% if path == '/users/new' %}active{% endif %}">New User</a>
16 + <a href="/movies/new" class="{% if path == '/movies/new' %}active{% endif %}">New Movie</a>
17 </header>
18 +
19 +<main>
20 {% block content %}{% endblock %}
21 +</main>
22 </body>
22 -</html>
\ No newline at end of file
23 +</html>
templates/home.html
+12 -5
@@ -1,9 +1,16 @@
1 {% extends "base.html" %}
2 {% block title %}Movie - WebApp{% endblock %}
3 {% block content %}
4 - <h1>Welcome to the Movie Web App</h1>
5 - <ul>
6 - <li><a href="/users" methods="GET">Users</a></li>
7 - <li><a href="/movies" methods="GET">Movies</a></li>
8 - </ul>
4 + <h1 class="home_title">Welcome to the Movie Web App</h1>
5 + <section class="home_section">
6 + <h2>Features</h2>
7 + <ul>
8 + <li>View all movies</li>
9 + <li>View a single movie</li>
10 + <li>View all users</li>
11 + <li>View a single user</li>
12 + <li>View all user's movies list</li>
13 + <li>View a single user's movie</li>
14 + </ul>
15 + </section>
16 {% endblock %}
\ No newline at end of file
templates/movie_details.html new
+18
@@ -0,0 +1,18 @@
1 +{% extends 'base.html' %}
2 +{% block title %}Movie Details{% endblock %}
3 +{% block content %}
4 +<h1>{{movie.name}} Details</h1>
5 + <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
6 + <ul>
7 + <li>{{ movie.name }} - ({{ movie.year }})</li>
8 + <li>Director: {{ movie.director }}</li>
9 + <li>Rating: {{ movie.rating }} </li>
10 + <li>Plot: {{ movie.plot }}</li>
11 + <li>Country: {{ movie.country }}</li>
12 + <li>Genre: {{ movie.genre }}</li>
13 + <form action="/movies" method="post">
14 + <button type="submit" id = "movie_id" name = "movie_id" value="{{ movie.id }}">
15 + Delete</button></form>
16 + <button type="submit"><a href="/movies">Back</a></button>
17 + </ul>
18 +{% endblock %}
templates/movies.html
+23 -14
@@ -1,22 +1,31 @@
1 {% extends 'base.html' %}
2 {% block title %}Movies List{% endblock %}
3 +
4 {% block content %}
4 -<h1>Movies List</h1>
5 - {% if success == True %}
6 - <p>Movie deleted successfully!</p>
7 - {% elif success == False %}
8 - <p>Failed to delete movie. Please try again.</p>
9 - {% elif success == None %}
5 +<h1 class="title">Movies List</h1>
6 +
7 +<div class="status-message">
8 + {% if success is not none %}
9 + <p>{{ 'Movie deleted successfully!' if success else 'Failed to delete movie. Please try again.' }}</p>
10 {% endif %}
11 +</div>
12
13 +<div class="movie-list">
14 + <div class="css-carousel" tabindex="0" aria-label="Movies carousel">
15 {% for movie in movies %}
13 - <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
14 - <ul>
15 - <li><a href="/movies/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li>
16 - <form action="/movies" method="post">
17 - <button type="submit" id = "movie_id" name = "movie_id" value="{{ movie.id }}">
18 - Delete</button></form>
19 - </ul>
16 + <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="{{ loop.index }} of {{ movies|length }}">
17 + <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200" />
18 + <ul>
19 + <li><a href="/movies/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li>
20 + <li>
21 + <form action="/movies" method="post">
22 + <button type="submit" id="movie_id" name="movie_id" value="{{ movie.id }}">Delete</button>
23 + </form>
24 + </li>
25 + </ul>
26 + </div>
27 {% endfor %}
21 -
28 + </div>
29 + <p class="scroll-instruction">Drag or scroll horizontally with your mouse or trackpad to browse movies.</p>
30 +</div>
31 {% endblock %}
templates/new_movie.html
+3 -2
@@ -3,9 +3,10 @@
3 {% block content %}
4 <h1>Add new Movie</h1>
5 {% if success == True %}
6 - <p>Movie: {{ movie.name }} added successfully!</p>
6 + <p>Movie: {{ movie_name }} added successfully!</p>
7 {% elif success == False %}
8 - <p>Failed to add movie. Please try again.</p>
8 + <p>Failed to add movie. {{ error }}. <br>
9 + Please try again.</p>
10 {% elif success == None %}
11 {% endif %}
12 <form action="/movies/new" method="POST">
templates/new_user.html
+2 -1
@@ -5,7 +5,8 @@
5 {% if success == True %}
6 <p>User added successfully!</p>
7 {% elif success == False %}
8 - <p>Failed to add user. Please try again.</p>
8 + <p>Failed to add user.{{ error }}. <br>
9 + Please try again.</p>
10 {% elif success == None %}
11 {% endif %}
12 <form action="/users/new" method="POST">
templates/recommend.html new
+24
@@ -0,0 +1,24 @@
1 +{% extends "base.html" %}
2 +{% block title %}{{user.name}} - Movie Recommendation{% endblock %}
3 +{% block content %}
4 + <h1 class="title" style="text-align: center;">{{user.name}} - Movie Recommendation</h1>
5 +
6 + <img height="200" alt="{{ movie.title }}" src="{{ movie.poster }}">
7 + <ul><li>{{ movie.title }} ({{ movie.year }})</li>
8 + <li>Director: {{ movie.director }}</li>
9 + <li>Imdb: {{ movie.imdb }} </li>
10 + <li>Plot: {{ movie.plot }}</li>
11 + <li>Country: {{ movie.country }}</li>
12 + <li>Genre: {{ movie.genre }}</li>
13 + <li>Reasoning: {{ reasoning }}</li>
14 + </ul>
15 + <button type="submit" class="back_button"><a href="/users/{{user.id}}">Back</a></button>
16 + <form action="/users/{{user.id}}" method="POST">
17 + <button type="submit" id = "name" name = "name" value="{{ movie.title }}">
18 + Add to {{user.name}} Movies List</button>
19 + </form>
20 + <form action="/users/{{user.id}}/recommend_movie" method="POST">
21 + <button type="submit">Recommend different Movie</button>
22 + </form>
23 +
24 +{% endblock %}
templates/user_movie.html
+14 -3
@@ -8,7 +8,7 @@
8 {% if success == True %}
9 <p>Movie updated successfully!</p>
10 {% elif success == False %}
11 - <p>Failed to update movie. <br>
11 + <p>Failed to update movie. {{error}}. <br>
12 Please try again!</p>
13 {% elif success == None %}
14 {% endif %}
@@ -18,14 +18,25 @@
18 <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
19 <ul><li>{{ movie.name }} ({{ movie.year }})</li>
20 <li>Director: {{ movie.director }}</li>
21 + <li>Plot: {{ movie.plot }}</li>
22 + <li>Country: {{ movie.country }}</li>
23 + <li>Genre: {{ movie.genre }}</li>
24 <li>Rating: {{ movie.rating }} </li>
25 <li>User Rating: {{ user_movie.user_rating }}</li>
26 + {% if user_movie.user_comment %}
27 + <li>User Comment: {{ user_movie.user_comment }}</li>
28 + {% endif %}
29 <li><form action="/users/{{user.id}}/{{movie.id}}"
30 method="POST">
31 <label for="user_rating">New User Rating:</label>
26 - <input type="text" id="user_rating" name="user_rating" required>
32 + <input type="text" id="user_rating" name="user_rating" min="0.1" max="10.0"
33 + step="0.1" required><br><br>
34 + <label for="user_comment">New User Comment:</label>
35 + <textarea id="user_comment" name="user_comment" rows="4" cols="50"
36 + placeholder="Enter your comment"></textarea>
37 <button type="submit">Update</button></form></li>
28 - <hr>
38 + <button type="submit" class="back_button">
39 + <a href="/users/{{user.id}}">Back</a></button>
40 </ul>
41
42 {% endblock %}
\ No newline at end of file
templates/user_movies.html
+55 -42
@@ -1,46 +1,59 @@
1 {% extends "base.html" %}
2 -{% block title %} {{ user.name }} - Movie List{% endblock %}
2 +{% block title %}{{ user.name }} - Movie List{% endblock %}
3 +
4 {% block content %}
4 - <h1 class="title" style="text-align: center;">{{user.name}} - Movie List</h1>
5 +<h1 class="title">{{ user.name }} - Movie List</h1>
6 +
7 +<div class="status-message">
8 + {% if success %}
9 + <p>{{ 'Movie added successfully!'
10 + if success else 'Failed to add movie. ' ~ error ~ '. Please try again!' }}</p>
11 + {% endif %}
12 + {% if movie_deleted %}
13 + <p>{{ 'Movie deleted successfully!'
14 + if movie_deleted else 'Failed to delete movie. Please try again.' }}</p>
15 + {% endif %}
16 +</div>
17 +
18 +<div class="actions">
19 + <form action="/users/{{ user.id }}/recommend_movie" method="POST">
20 + <button type="submit">Recommend Movie</button>
21 + </form>
22
6 - <header>
7 - {% if success == True %}
8 - <p>Movie added successfully!</p>
9 - {% elif success == False %}
10 - <p>Failed to add movie. Either movie already associated with user or movie does not
11 - exist. <br>
12 - Please try again!</p>
13 - {% elif success == None %}
14 - {% endif %}
15 - {% if movie_deleted == True %}
16 - <p>Movie deleted successfully!</p>
17 - {% elif movie_deleted == False %}
18 - <p>Failed to delete movie. Please try again.</p>
19 - {% elif movie_deleted == None %}
20 - {% endif %}
21 - <form action="/users/{{user.id}}" method="POST">
22 - <label for="name">Name:</label>
23 - <input type="text" id="name" name="name" required>
24 - <button type="submit">Add Movie</button>
25 - </form>
26 - </header>
27 - {% for movie in user_movies %}
28 - <img height="200" alt="{{ movie.name }}" src="{{ movie.poster }}">
29 - <ul><li>{{ movie.name }} ({{ movie.year }})</li>
30 - <form action="/users/{{user.id}}/delete/{{movie.id}}"
31 - method="POST">
32 - <button type="submit" id = "movie_id" name = "movie_id" value="{{ movie.id }}">
33 - Delete</button></form>
34 - <li>Director: {{ movie.director }}</li>
35 - <li>Rating: {{ movie.rating }} </li>
36 - <li>User Rating: {{ movie.user_rating }}</li>
37 - <li><form action="/users/{{user.id}}/{{movie.id}}"
38 - method="POST">
39 - <label for="user_rating">New User Rating:</label>
40 - <input type="text" id="user_rating" name="user_rating" required>
41 - <button type="submit">Update</button></form></li>
42 - <hr>
43 - </ul>
44 - {% endfor %}
23 + <form action="/users/{{ user.id }}" method="POST">
24 + <label for="name">Name:</label>
25 + <input type="text" id="name" name="name" required>
26 + <button type="submit">Add Movie</button>
27 + </form>
28 +</div>
29
46 -{% endblock %}
\ No newline at end of file
30 +<div class="movie-list">
31 + <div class="css-carousel" tabindex="0" aria-label="User's movies carousel">
32 + {% for movie in user_movies %}
33 + <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="{{ loop.index }} of {{ user_movies|length }}">
34 + <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200">
35 + <ul>
36 + <li><a href="/users/{{ user.id }}/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li>
37 + <li>Director: {{ movie.director }}</li>
38 + <li>Rating: {{ movie.rating }}</li>
39 + <li>User Rating: {{ movie.user_rating }}</li>
40 + <li>
41 + <form action="/users/{{ user.id }}/{{ movie.id }}" method="POST">
42 + <label for="user_rating_{{ movie.id }}">New User Rating:</label>
43 + <input type="text" id="user_rating_{{ movie.id }}" name="user_rating" min="0.1" max="10.0" step="0.1" required>
44 + <button type="submit">Update</button>
45 + </form>
46 + </li>
47 + <li>
48 + <form action="/users/{{ user.id }}/delete/{{ movie.id }}" method="POST">
49 + <button type="submit" name="movie_id" value="{{ movie.id }}">Delete</button>
50 + </form>
51 + </li>
52 + </ul>
53 + </div>
54 + {% endfor %}
55 + </div>
56 + <p class="scroll-instruction">Drag or scroll horizontally with pressing shift and
57 + using your mouse or trackpad to browse movies.</p>
58 +</div>
59 +{% endblock %}