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 %}