got all buttons working
Lee Roy Stevenson committed
May 20, 2025 at 00:05 UTC
f8f69eda670272149298a784459f35d3e8a69286
12 files changed
+297
-125
app.py
+73
-15
@@ -1,5 +1,4 @@
1
-
2
-
1
+from django.contrib.messages import success
2
from flask import Flask, render_template, request
3
from datamanager.sqlite_data_manager import SQliteDataManager
4
from data_models import User, Movie, UserMovie
@@ -33,16 +32,59 @@ def user_movies(user_id):
32
if movie is None:
33
try:
34
movie = data_manager.set_movie(movie_title)
36
- session.add(movie)
37
- data_manager.set_user_movies(user_id=user_id, movie_id=movie.id, rating=movie.rating)
38
- session.commit()
35
+ if movie is None:
36
+ raise Exception("Movie not found")
37
+ data_manager.set_user_movies(user_id=user_id, movie_id=movie.id)
38
+ user_movies_list = data_manager.get_user_movies(user_id)
39
+ return render_template("user_movies.html",
40
+ user_movies=user_movies_list,
41
+ user=chosen_user, success=True)
42
+ except Exception as e:
43
+ session.rollback()
44
+ return render_template("user_movies.html",
45
+ user_movies=user_movies_list,
46
+ user=chosen_user, success=False)
47
+ else:
48
+ if data_manager.get_user_movie(user_id, movie.id):
49
+ return render_template("user_movies.html",
50
+ user_movies=data_manager.get_user_movies(user_id),
51
+ user=chosen_user, success=False)
52
+ try:
53
+ data_manager.set_user_movies(user_id=user_id, movie_id=movie.id)
54
user_movies_list = data_manager.get_user_movies(user_id)
40
- return render_template("user_movies.html", user_movies=user_movies_list,
41
- user=chosen_user, success=True)
55
+ return render_template("user_movies.html",
56
+ user_movies=user_movies_list,
57
+ user=chosen_user, success=True)
58
except Exception as e:
59
session.rollback()
44
- return render_template("user_movies.html", user_movies=user_movies_list,
45
- user=chosen_user, success=False)
60
+ return render_template("user_movies.html",
61
+ user_movies=user_movies_list,
62
+ user=chosen_user, success=False)
63
+
64
+
65
+@app.route('/users/<user_id>/<movie_id>', methods=["GET", "POST"])
66
+def update_user_movie(user_id, movie_id):
67
+ if request.method == "GET":
68
+ chosen_user = data_manager.get_user(user_id)
69
+ movie = data_manager.get_movie(movie_id)
70
+ user_movie = data_manager.get_user_movie(user_id, movie_id)
71
+ return render_template("user_movie.html", user_movie=user_movie,
72
+ user=chosen_user, movie=movie)
73
+ elif request.method == "POST":
74
+ chosen_user = data_manager.get_user(user_id)
75
+ movie = data_manager.get_movie(movie_id)
76
+ user_movie = data_manager.get_user_movie(user_id, movie_id)
77
+ user_rating = request.form["user_rating"]
78
+ try:
79
+ data_manager.update_user_movie(user_id=user_id, movie_id=movie_id,
80
+ update_data={"user_rating": user_rating})
81
+ new_user_movie = data_manager.get_user_movie(user_id, movie_id)
82
+ return render_template("user_movie.html", user_movie=new_user_movie,
83
+ user=chosen_user, movie=movie, success=True)
84
+ except Exception as e:
85
+ return render_template("user_movie.html", user_movie=user_movie,
86
+ user=chosen_user, movie=movie, success=False)
87
+
88
89
90
@app.route('/users/new', methods=["GET", "POST"])
@@ -70,10 +112,10 @@ def new_movie():
112
title = request.form["name"]
113
movie = data_manager.set_movie(title)
114
if movie:
73
- success = True
115
+ movie_added = True
116
else:
75
- success = False
76
- return render_template("new_movie.html", movie=movie, success=success)
117
+ movie_added = False
118
+ return render_template("new_movie.html", movie=movie, success=movie_added)
119
120
@app.route('/movies', methods=["GET", "POST"])
121
def list_movies():
@@ -82,11 +124,27 @@ def list_movies():
124
return render_template("movies.html",movies=movies)
125
elif request.method == "POST":
126
movie_id = request.form["movie_id"]
85
- data_manager.delete_movie(movie_id)
127
+ deleted = data_manager.delete_movie(int(movie_id))
128
movies = data_manager.movies
87
- return render_template("movies.html",movies=movies)
129
+ return render_template("movies.html", movies=movies, success=deleted)
130
+
131
+@app.route('/users/<user_id>/delete/<movie_id>', methods=["GET", "POST"])
132
+def delete_user_movie(user_id, movie_id):
133
+ if request.method == "GET":
134
+ chosen_user = data_manager.get_user(user_id)
135
+ movie = data_manager.get_movie(movie_id)
136
+ user_movie = data_manager.get_user_movie(user_id, movie_id)
137
+ return render_template("user_movie.html", user_movie=user_movie,
138
+ user=chosen_user, movie=movie)
139
+ elif request.method == "POST":
140
+ deleted = data_manager.delete_user_movie(user_id, movie_id)
141
+ chosen_user = data_manager.get_user(user_id)
142
+ movie = data_manager.get_movie(movie_id)
143
+ user_movies_list = data_manager.get_user_movies(user_id)
144
+ return render_template("user_movies.html", user_movies=user_movies_list,
145
+ user=chosen_user, movie=movie, movie_deleted=deleted)
146
147
if __name__ == "__main__":
148
91
- app.run(debug=True, host="127.0.0.1",port=5000)
149
+ app.run(debug=True,port=5000)
150
datamanager/sqlite_data_manager.py
+81
-36
@@ -1,6 +1,6 @@
1
2
from sqlalchemy import create_engine
3
-from sqlalchemy.orm import sessionmaker, joinedload, Session
3
+from sqlalchemy.orm import sessionmaker
4
from contextlib import contextmanager
5
from typing import List, Dict, Any, Type
6
@@ -21,7 +21,7 @@ class SQliteDataManager(DataManagerInterface):
21
Initialize the data manager with a database URL.
22
"""
23
self.engine = create_engine(db_url)
24
- self.SessionFactory = sessionmaker(bind=self.engine)
24
+ self.SessionFactory = sessionmaker(bind=self.engine, expire_on_commit=False)
25
Base.metadata.create_all(self.engine)
26
27
@contextmanager
@@ -74,11 +74,11 @@ class SQliteDataManager(DataManagerInterface):
74
Getter for movies.
75
Returns: a list of Movie objects
76
"""
77
- with self.SessionFactory() as db:
78
- return db.query(Movie).options(joinedload(Movie.users)).all()
77
+ with self.SessionFactory() as session:
78
+ return session.query(Movie).all()
79
80
def set_user_movies(self, user_id: int, movie_id: int, user_rating: float = 0.0)\
81
- -> None:
81
+ -> str | None:
82
"""
83
Set (add) a movie to a user's list with a rating,
84
either create a new association or update the rating if it exists (self-contained session).
@@ -87,13 +87,11 @@ class SQliteDataManager(DataManagerInterface):
87
with self.SessionFactory() as session:
88
user = session.query(User).filter_by(id=user_id).first()
89
movie = session.query(Movie).filter_by(id=movie_id).first()
90
- print(f"user:{user}, movie:{movie}")
90
92
- if user is not None and movie is not None:
91
+ if user and movie:
92
existing_associations = session.query(UserMovie).filter_by(
93
user_id=user_id, movie_id=movie_id
95
- ).all() #check if the association already exists
96
- print(f"existing_associations: {existing_associations}")
94
+ ).first() #check if the association already exists
95
if existing_associations:
96
# existing association update
97
session.query(UserMovie).filter_by(user_id=user_id, movie_id=movie_id).update(
@@ -103,18 +101,16 @@ class SQliteDataManager(DataManagerInterface):
101
# create new association
102
association = UserMovie(user_id=user_id, movie_id=movie_id,
103
user_rating=user_rating)
106
- print(f"association: {association}")
104
session.add(association)
108
- print(f"Added movie {movie_id} to user {user_id} with rating {user_rating}")
105
106
session.commit() # Commit within the function
107
108
113
- elif user is not None and movie is None:
114
- print("Failed to add movie!")
109
+ elif movie is None:
110
+ return "Failed to add movie, check ID and try again!"
111
112
else:
117
- print("User or movie not found.")
113
+ return "User not found."
114
115
def get_user_movies(self, user_id: int) -> List[Dict[str, Any]]:
116
"""
@@ -135,12 +131,30 @@ class SQliteDataManager(DataManagerInterface):
131
"director": movie.director,
132
"year": movie.year,
133
"poster": movie.poster,
138
- "user_rating": association.user_rating,
134
+ "rating": movie.rating,
135
+ "user_rating": association.user_rating
136
})
137
return movies_with_ratings
138
return []
139
140
141
+ def get_user_movie(self, user_id: int, movie_id: int) -> Dict[str, Any]:
142
+ with self.SessionFactory() as session:
143
+ user_movie = session.query(UserMovie).filter_by(user_id=user_id, movie_id=movie_id).first()
144
+ if user_movie:
145
+ movie = session.query(Movie).filter_by(id=movie_id).first()
146
+ return {
147
+ "id": movie.id,
148
+ "name": movie.name,
149
+ "director": movie.director,
150
+ "year": movie.year,
151
+ "poster": movie.poster,
152
+ "rating": movie.rating,
153
+ "user_rating": user_movie.user_rating,
154
+ }
155
+ return {}
156
+
157
+
158
def set_movie(self, movie_title: str) -> Type[Movie] | Movie:
159
"""
160
Add a new movie to the database.
@@ -148,7 +162,7 @@ class SQliteDataManager(DataManagerInterface):
162
"""
163
with self.SessionFactory() as session:
164
movie = session.query(Movie).filter_by(name=movie_title).first()
151
- if movie is not None:
165
+ if movie:
166
return movie
167
new_movie = OMDBClient().get_movie(title=movie_title)
168
if new_movie is None:
@@ -158,43 +172,74 @@ class SQliteDataManager(DataManagerInterface):
172
session.commit()
173
return movie
174
161
- def update_movie(self, movie_id: int, update_data: dict) -> dict | None:
175
+ def update_user_movie(self, user_id: int, movie_id: int, update_data: dict) -> dict | None:
176
with self.SessionFactory() as session:
177
movie = session.query(Movie).filter_by(id=movie_id).first()
178
if movie:
165
- for key, value in update_data.items():
166
- setattr(movie, key, value)
179
+ session.query(UserMovie).filter_by(user_id=user_id, movie_id=movie_id).update(update_data)
180
session.commit()
181
return {
182
"id": movie.id,
183
"name": movie.name,
184
"director": movie.director,
185
"year": movie.year,
173
- "poster": movie.poster
186
+ "poster": movie.poster,
187
+ "user_rating": update_data["user_rating"]
188
}
189
return None
190
191
def delete_movie(self, movie_id: int) -> bool:
192
"""
179
- Delete a movie and its associations.
180
-
181
- Args:
182
- movie_id: The ID of the movie to delete.
183
-
184
- Returns:
185
- True if the movie was successfully deleted, False otherwise.
193
+ Delete a movie from the movies table in the database
194
+ :param movie_id:
195
+ :return:
196
"""
187
- with (self.SessionFactory() as db):
188
- movie = db.query(Movie).filter_by(id = movie_id).first()
197
+ with self.SessionFactory() as session:
198
+ movie = session.query(Movie).filter_by(id = movie_id).first()
199
if movie:
190
- # Safely clear all associations through the association object
191
- movie.user_movies.clear()
200
+ session.flush() # Make sure pending updates are flushed
201
+ session.query(UserMovie).filter_by(movie_id = movie_id).delete()
202
+ session.query(Movie).filter_by(id = movie_id).delete()
203
+ session.commit()
204
+ return True
205
+ return False
206
+
207
+ def delete_user(self, user_id: int) -> bool:
208
+ """
209
+ Delete a user from the users table in the database
210
+ :param user_id:
211
+ :return:
212
+ """
213
+ with self.SessionFactory() as session:
214
+ user = session.query(User).filter_by(id = user_id).first()
215
+ if user:
216
+ session.flush() # Make sure pending updates are flushed
217
+ session.query(UserMovie).filter_by(user_id = user_id).delete()
218
+ session.query(User).filter_by(id = user_id).delete()
219
+ session.commit()
220
+ return True
221
+ return False
222
+
223
193
- db.delete(movie)
194
- db.commit()
224
+ def delete_user_movie(self, user_id: int, movie_id: int) -> bool:
225
+ with self.SessionFactory() as session:
226
+ association = session.query(UserMovie).filter_by(user_id=user_id, movie_id=movie_id).first()
227
+ if association:
228
+ session.delete(association)
229
+ session.commit()
230
return True
231
return False
232
233
+ def get_movie(self, movie_id: int) -> Movie | None:
234
+ """
235
+ Get a movie from the movies table in the database
236
+ :param movie_id:
237
+ :return:
238
+ """
239
+ with self.SessionFactory() as session:
240
+ movie = session.query(Movie).filter_by(id = movie_id).first()
241
+ return movie
242
+
243
def main():
244
data_manager = SQliteDataManager("sqlite:///movie_app.db")
245
@@ -213,7 +258,7 @@ def main():
258
259
# Associate movies with users and assign ratings
260
print(f"user1:{user1}, movie1:{movie1}")
216
- data_manager.set_user_movies(user1.id, movie1.id, 8.9)
261
+ data_manager.set_user_movies(user1.id, movie1.id)
262
print(data_manager.get_user_movies(user1.id))
263
data_manager.set_user_movies(user1.id, movie2.id, 8.5)
264
data_manager.set_user_movies(user2.id, movie2.id, 9.2)
@@ -227,8 +272,8 @@ def main():
272
print("All movies:", session.query(Movie).all())
273
274
# Example of updating a movie (use a new session)
230
- updated_movie = data_manager.update_movie(movie1.id,
231
- {"name": "The Matrix Reloaded", "user_rating":
275
+ updated_movie = data_manager.update_user_movie(user1.id, movie1.id,
276
+ {"user_rating":
277
7.2})
278
print("Updated movie:", updated_movie)
279
requirements.txt
+8
-2
@@ -1,2 +1,8 @@
1
-flask
2
-flask_sqlalchemy
\ No newline at end of file
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
static/styles.css
new
+29
@@ -0,0 +1,29 @@
1
+/* Add a black background color to the top navigation */
2
+.topnav {
3
+ border: 1px solid black;
4
+ border-radius: 5px;
5
+ background-color: #333;
6
+ overflow: hidden;
7
+}
8
+
9
+/* Style the links inside the navigation bar */
10
+.topnav a {
11
+ float: left;
12
+ color: #f2f2f2;
13
+ text-align: center;
14
+ padding: 14px 16px;
15
+ text-decoration: none;
16
+ font-size: 17px;
17
+}
18
+
19
+/* Change the color of links on hover */
20
+.topnav a:hover {
21
+ background-color: #ddd;
22
+ color: black;
23
+}
24
+
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
templates/base.html
new
+22
@@ -0,0 +1,22 @@
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
+
8
+ <title>{% block title %}{% endblock %}</title>
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
10
+</head>
11
+<body>
12
+<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
+
19
+</header>
20
+ {% block content %}{% endblock %}
21
+</body>
22
+</html>
\ No newline at end of file
templates/home.html
+4
-9
@@ -1,14 +1,9 @@
1
-<!DOCTYPE html>
2
-<html lang="en">
3
-<head>
4
- <meta charset="UTF-8">
5
- <title>Movie - WebApp</title>
6
-</head>
7
-<body>
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>
13
-</body>
14
-</html>
\ No newline at end of file
9
+ {% endblock %}
\ No newline at end of file
templates/movies.html
+11
-14
@@ -1,25 +1,22 @@
1
-<!DOCTYPE html>
2
-<html lang="en">
3
-<head>
4
- <meta charset="UTF-8">
5
- <title>Movies List</title>
6
-</head>
7
-<body>
1
+{% extends 'base.html' %}
2
+{% block title %}Movies List{% endblock %}
3
+{% 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 %}
10
+ {% endif %}
11
12
{% 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">
15
- <button type="submit" name = "movie_id" value ="{{ movie.id }}">
17
+ <button type="submit" id = "movie_id" name = "movie_id" value="{{ movie.id }}">
18
Delete</button></form>
19
</ul>
20
{% 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>
21
24
-</body>
25
-</html>
\ No newline at end of file
22
+{% endblock %}
templates/new_movie.html
+5
-13
@@ -1,13 +1,9 @@
1
-<!DOCTYPE html>
2
-<html lang="en">
3
-<head>
4
- <meta charset="UTF-8">
5
- <title>Add new Movie</title>
6
-</head>
7
-<body>
1
+{% extends "base.html" %}
2
+{% block title %}Add Movie{% endblock %}
3
+{% block content %}
4
<h1>Add new Movie</h1>
5
{% if success == True %}
10
- <p>Movie 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>
9
{% elif success == None %}
@@ -17,8 +13,4 @@
13
<input type="text" id="name" name="name" required>
14
<button type="submit">Add Movie</button>
15
</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
16
+ {% endblock %}
\ No newline at end of file
templates/new_user.html
+4
-12
@@ -1,10 +1,6 @@
1
-<!DOCTYPE html>
2
-<html lang="en">
3
-<head>
4
- <meta charset="UTF-8">
5
- <title>Add new User</title>
6
-</head>
7
-<body>
1
+{% extends 'base.html' %}
2
+{% block title %}Add new User{% endblock %}
3
+{% block content %}
4
<h1>Add new User</h1>
5
{% if success == True %}
6
<p>User added successfully!</p>
@@ -17,8 +13,4 @@
13
<input type="text" id="name" name="name" required>
14
<button type="submit">Add User</button>
15
</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
16
+{% endblock %}
\ No newline at end of file
templates/user_movie.html
new
+31
@@ -0,0 +1,31 @@
1
+{% extends "base.html" %}
2
+{% block title %}{{user.name}} - Movie List{% endblock %}
3
+
4
+{% block content %}
5
+ <h1 class="title" style="text-align: center;">{{user.name}} - Movie List</h1>
6
+
7
+ <header>
8
+ {% if success == True %}
9
+ <p>Movie updated successfully!</p>
10
+ {% elif success == False %}
11
+ <p>Failed to update movie. <br>
12
+ Please try again!</p>
13
+ {% elif success == None %}
14
+ {% endif %}
15
+
16
+ </header>
17
+
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>Rating: {{ movie.rating }} </li>
22
+ <li>User Rating: {{ user_movie.user_rating }}</li>
23
+ <li><form action="/users/{{user.id}}/{{movie.id}}"
24
+ method="POST">
25
+ <label for="user_rating">New User Rating:</label>
26
+ <input type="text" id="user_rating" name="user_rating" required>
27
+ <button type="submit">Update</button></form></li>
28
+ <hr>
29
+ </ul>
30
+
31
+{% endblock %}
\ No newline at end of file
templates/user_movies.html
+25
-14
@@ -1,19 +1,23 @@
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>
1
+{% extends "base.html" %}
2
+{% block title %} {{ user.name }} - Movie List{% endblock %}
3
+{% block content %}
4
+ <h1 class="title" style="text-align: center;">{{user.name}} - Movie List</h1>
5
6
<header>
7
{% if success == True %}
8
<p>Movie added successfully!</p>
9
{% elif success == False %}
14
- <p>Failed to add movie. Please try again.</p>
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>
@@ -23,13 +27,20 @@
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>
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>
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 %}
45
34
-</body>
35
-</html>
\ No newline at end of file
46
+{% endblock %}
\ No newline at end of file
templates/users.html
+4
-10
@@ -1,10 +1,6 @@
1
-<!DOCTYPE html>
2
-<html lang="en">
3
-<head>
4
- <meta charset="UTF-8">
5
- <title>Users - MovieWeb App</title>
6
-</head>
7
-<body>
1
+{% extends 'base.html' %}
2
+{% block title %}Users - MovieWeb{% endblock %}
3
+{% block content %}
4
<h1>Users</h1>
5
<ul><a href="/users/new" methods="GET">Add User</a></ul>
6
<ul><a href="/" methods="GET">Home</a></ul>
@@ -14,6 +10,4 @@
10
<li><a href="/users/{{user.id}}" methods="GET">{{user.name}}</a></li>
11
{% endfor %}
12
</ul>
17
-
18
-</body>
19
-</html>
\ No newline at end of file
13
+{% endblock %}