Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)
Trong phần trước của loạt bài này, chúng ta đã cài đặt chức năng upload hình ảnh cho người dùng khi thêm một mong ước. Chúng ta cũng đã thêm một vài tuỳ chọn liên quan đến mong ước của người dùng trên trang Add Wish. Trong hướng dẫn này, chúng ta sẽ nâng nó lên một cấp độ cao hơn bằng cách cài đặt chức năng cho phép like một mong ước nào đó.
Bắt đầu
Hãy bắt đầu bằng cách clone mã nguồn của phần trước của hướng dẫn từ GitHub.
1 |
git clone https://github.com/jay3dec/PythonFlaskMySQLApp_Part6.git |
Khi mã nguồn đã được clone, hãy chuyển đến thư mục dự án và khởi động máy chủ web.
1 |
cd PythonFlaskMySQLApp_Part6
|
2 |
python app.py |
Trỏ trình duyệt của bạn đến http://localhost:5002/ và bạn sẽ thấy ứng dụng đang chạy.
Tạo Giao diện Dashboard
Chúng ta sẽ tạo một trang mới gọi là dashboard
, nơi tất cả những mong ước từ những người dùng khác nhau sẽ được hiển thị. Bất kỳ người dùng nào cũng có thể like hoặc nhận xét về những mong ước được hiển thị trong dashboard. Vì vậy, hãy điều hướng đến thư mục templates và tạo một tập tin gọi là dashboard.html
. Mở dashboard.html
và thêm code HTML sau đây:
1 |
<!DOCTYPE html>
|
2 |
<html lang="en"> |
3 |
|
4 |
<head>
|
5 |
<title>Python Flask Bucket List App</title> |
6 |
|
7 |
|
8 |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> |
9 |
|
10 |
<link href="http://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet"> |
11 |
|
12 |
|
13 |
|
14 |
</head>
|
15 |
|
16 |
<body>
|
17 |
|
18 |
<div class="container"> |
19 |
<div class="header"> |
20 |
<nav>
|
21 |
<ul class="nav nav-pills pull-right"> |
22 |
<li role="presentation" class="active"><a href="#">Dashboard</a></li> |
23 |
<li role="presentation"><a href="/userHome">My List</a></li> |
24 |
<li role="presentation"><a href="/showAddWish">Add Item</a></li> |
25 |
<li role="presentation"><a href="/logout">Logout</a></li> |
26 |
</ul>
|
27 |
</nav>
|
28 |
<h3 class="text-muted">Python Flask App</h3> |
29 |
</div>
|
30 |
|
31 |
<div class="well"> |
32 |
<div class="row"> |
33 |
<div class="col-sm-4 col-md-4"> |
34 |
<div class="thumbnail"> |
35 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
36 |
<div class="caption"> |
37 |
<h3>Bungee Jumping</h3> |
38 |
<p>vehicula ut id elit.</p> |
39 |
<p>
|
40 |
<button type="button" class="btn btn-danger btn-sm"> |
41 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
42 |
</button>
|
43 |
</p>
|
44 |
</div>
|
45 |
</div>
|
46 |
</div>
|
47 |
<div class="col-sm-4 col-md-4"> |
48 |
<div class="thumbnail"> |
49 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
50 |
<div class="caption"> |
51 |
<h3>Bungee Jumping</h3> |
52 |
<p>vehicula ut id elit.</p> |
53 |
<p>
|
54 |
<button type="button" class="btn btn-danger btn-sm"> |
55 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
56 |
</button>
|
57 |
</p>
|
58 |
</div>
|
59 |
</div>
|
60 |
</div>
|
61 |
<div class="col-sm-4 col-md-4"> |
62 |
<div class="thumbnail"> |
63 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
64 |
<div class="caption"> |
65 |
<h3>Bungee Jumping</h3> |
66 |
<p>vehicula ut id elit.</p> |
67 |
<p>
|
68 |
<button type="button" class="btn btn-danger btn-sm"> |
69 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
70 |
</button>
|
71 |
</p>
|
72 |
</div>
|
73 |
</div>
|
74 |
</div>
|
75 |
|
76 |
|
77 |
<div class="row"> |
78 |
<div class="col-sm-4 col-md-4"> |
79 |
<div class="thumbnail"> |
80 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
81 |
<div class="caption"> |
82 |
<h3>Bungee Jumping</h3> |
83 |
<p>vehicula ut id elit.</p> |
84 |
<p>
|
85 |
<button type="button" class="btn btn-danger btn-sm"> |
86 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
87 |
</button>
|
88 |
</p>
|
89 |
</div>
|
90 |
</div>
|
91 |
</div>
|
92 |
<div class="col-sm-4 col-md-4"> |
93 |
<div class="thumbnail"> |
94 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
95 |
<div class="caption"> |
96 |
<h3>Bungee Jumping</h3> |
97 |
<p>vehicula ut id elit.</p> |
98 |
<p>
|
99 |
<button type="button" class="btn btn-danger btn-sm"> |
100 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
101 |
</button>
|
102 |
</p>
|
103 |
</div>
|
104 |
</div>
|
105 |
</div>
|
106 |
<div class="col-sm-4 col-md-4"> |
107 |
<div class="thumbnail"> |
108 |
<img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;"> |
109 |
<div class="caption"> |
110 |
<h3>Bungee Jumping</h3> |
111 |
<p>vehicula ut id elit.</p> |
112 |
<p>
|
113 |
<button type="button" class="btn btn-danger btn-sm"> |
114 |
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> |
115 |
</button>
|
116 |
</p>
|
117 |
</div>
|
118 |
</div>
|
119 |
</div>
|
120 |
</div>
|
121 |
|
122 |
</div>
|
123 |
|
124 |
<footer class="footer"> |
125 |
<p>© Company 2015</p> |
126 |
</footer>
|
127 |
|
128 |
</div>
|
129 |
</body>
|
130 |
|
131 |
</html>
|
Mở app.py
và tạo một route mới gọi là /showDashboard
. Sử dụng route này, chúng ta sẽ hiển thị trang dashboard.
1 |
@app.route('/showDashboard') |
2 |
def showDashboard(): |
3 |
return render_template('dashboard.html') |
Sửa đổi phương thức /validateLogin
để chuyển hướng người dùng khi đăng nhập thành công đến trang dashboard thay vì trang chủ của người dùng.
1 |
return redirect('/showDashboard') |
Lưu các thay đổi ở trên và khởi động lại máy chủ. Trỏ trình duyệt đến http://localhost:50002 và đăng nhập bằng địa chỉ email và mật khẩu hợp lệ. Sau khi đăng nhập, bạn sẽ có thể thấy trang dashboard.



Như đã thấy trong hình trên, chúng ta sẽ hiển thị tất cả các mong ước được tạo ra bởi những người dùng khác nhau và cho phép những người dùng khác truy cập vào chúng.
Điền Nội dung vào Dashboard
Trước tiên, chúng ta cần lấy dữ liệu từ cơ sở dữ liệu để đưa vào dashboard. Vì vậy, chúng ta hãy tạo một thủ tục lưu trữ để lấy những mong ước được tạo ra bởi người dùng.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_GetAllWishes`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetAllWishes`() |
7 |
BEGIN
|
8 |
select wish_id,wish_title,wish_description,wish_file_path from tbl_wish where wish_private = 0; |
9 |
END$$ |
10 |
|
11 |
DELIMITER ; |
12 |
Thủ tục lưu trữ ở trên sẽ lấy tất cả các mong ước từ bảng tbl_wish
mà không được đánh dấu là riêng tư.
Tiếp theo, chúng ta sẽ tạo một phương thức Python mới để gọi thủ tục lưu trữ sp_GetAllWishes
. Mở app.py
và thêm code sau đây cho phương thức getAllWishes
.
1 |
@app.route('/getAllWishes') |
2 |
def getAllWishes(): |
3 |
try: |
4 |
if session.get('user'): |
5 |
|
6 |
conn = mysql.connect() |
7 |
cursor = conn.cursor() |
8 |
cursor.callproc('sp_GetAllWishes') |
9 |
result = cursor.fetchall() |
10 |
|
11 |
|
12 |
|
13 |
wishes_dict = [] |
14 |
for wish in result: |
15 |
wish_dict = { |
16 |
'Id': wish[0], |
17 |
'Title': wish[1], |
18 |
'Description': wish[2], |
19 |
'FilePath': wish[3]} |
20 |
wishes_dict.append(wish_dict) |
21 |
|
22 |
|
23 |
|
24 |
return json.dumps(wishes_dict) |
25 |
else: |
26 |
return render_template('error.html', error = 'Unauthorized Access') |
27 |
except Exception as e: |
28 |
return render_template('error.html',error = str(e)) |
Trong phương thức trên, trước tiên chúng ta kiểm tra session người dùng hợp lệ và sau đó tạo một kết nối MySQL. Bằng đối tượng kết nối conn
của MySQL, chúng ta sử dụng một con trỏ để gọi thủ tục lưu trữ sp_GetAllWishes
để lấy dữ liệu cần thiết. Khi đã lấy được dữ liệu, chúng ta phân tích kết quả và trả về một chuỗi JSON
thích hợp.
Chúng ta sẽ gọi phương thức /getAllWishes
đã được tạo ra ở trên khi trang dashboard được tải. Mở dashboard.html
và sử dụng jQuery AJAX, hãy gọi đến /getAllWishes
khi document.ready
.
1 |
$(function() { |
2 |
$.ajax({ |
3 |
url: '/getAllWishes', |
4 |
type: 'GET', |
5 |
success: function(response) { |
6 |
console.log(response); |
7 |
},
|
8 |
error: function(error) { |
9 |
console.log(error); |
10 |
}
|
11 |
});
|
12 |
})
|
Lưu các thay đổi ở trên và khởi động lại máy chủ. Sau khi đã đăng nhập vào ứng dụng, hãy kiểm tra giao diện console của trình duyệt và bạn sẽ có thể thấy các dữ liệu được lấy từ cơ sở dữ liệu.
1 |
[{
|
2 |
"Description": "Bungee Jumping", |
3 |
"FilePath": "static/Uploads/de5f8a10-54ea-49f4-80ce-35626277047e.jpg", |
4 |
"Id": 10, |
5 |
"Title": "Bungee Jumping" |
6 |
}, { |
7 |
"Description": "Mount Everest climb", |
8 |
"FilePath": "static/Uploads/e3e8f7fa-6cb9-4cc3-9989-a80e5089546f.png", |
9 |
"Id": 11, |
10 |
"Title": "Mount Everest climb" |
11 |
}, { |
12 |
"Description": "River Rafting", |
13 |
"FilePath": "static/Uploads/dff3a64c-5193-42b5-9cdb-9d67a7bbacab.png", |
14 |
"Id": 14, |
15 |
"Title": "River Rafting" |
16 |
}, { |
17 |
"Description": "Deep Sea Diving", |
18 |
"FilePath": "static/Uploads/b0656759-c038-46b4-9529-c208aaa6bfb7.png", |
19 |
"Id": 15, |
20 |
"Title": "Deep Sea Diving" |
21 |
}]
|
Sử dụng dữ liệu từ phản hồi, chúng ta sẽ điền vào trang dashboard của chúng ta. Trước tiên, hãy xoá code HTML ở giữa div .well
khỏi dashboard.html
.
1 |
<div class="well"> |
2 |
|
3 |
<!-- We'll populate this dynamically -->
|
4 |
|
5 |
</div>
|
Trong hàm callback success khi gọi AJAX, phân tích response
thành một đối tượng JavaScript.
1 |
var data = JSON.parse(response); |
Chúng ta sẽ cần tạo code HTML thumbnail tự động bằng cách sử dụng jQuery cho mỗi bộ ba mong ước trên một hàng. Đầu tiên chúng ta hãy tạo một hàm JavaScript để tạo ra code HTML tự động. Đây là code HTML mà chúng ta sẽ tạo ra tự động bằng jQuery:
1 |
<div class="col-sm-4 col-md-4"> |
2 |
<div class="thumbnail"><img src="static/Uploads/de5f8a10-54ea-49f4-80ce-35626277047e.jpg" data-holder-rendered="true" style="height: 150px; width: 150px; display: block"> |
3 |
<div class="caption"> |
4 |
<h3>Testing App</h3> |
5 |
<p>hello</p> |
6 |
<p>
|
7 |
<button type="button" class="btn btn-danger btn-sm"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span></button> |
8 |
</p>
|
9 |
</div>
|
10 |
</div>
|
11 |
</div>
|
Chúng ta sẽ đặt tên cho hàm JavaScript là CreateThumb
. Trong hàm này, chúng ta sẽ tạo các phần tử HTML và nối chúng với các phần tử cha của chúng để có được code HTML như ở trên.
1 |
function CreateThumb(id,title, desc, filepath) { |
2 |
|
3 |
var mainDiv = $('<div>').attr('class', 'col-sm-4 col-md-4'); |
4 |
|
5 |
var thumbNail = $('<div>').attr('class', 'thumbnail'); |
6 |
|
7 |
var img = $('<img>').attr({ |
8 |
'src': filepath, |
9 |
'data-holder-rendered': true, |
10 |
'style': 'height: 150px; width: 150px; display: block' |
11 |
});
|
12 |
|
13 |
var caption = $('<div>').attr('class', 'caption'); |
14 |
|
15 |
var title = $('<h3>').text(title); |
16 |
|
17 |
var desc = $('<p>').text(desc); |
18 |
|
19 |
|
20 |
var p = $('<p>'); |
21 |
|
22 |
var btn = $('<button>').attr({ |
23 |
'id': 'btn_' + id, |
24 |
'type': 'button', |
25 |
'class': 'btn btn-danger btn-sm' |
26 |
});
|
27 |
|
28 |
var span = $('<span>').attr({ |
29 |
'class': 'glyphicon glyphicon-thumbs-up', |
30 |
'aria-hidden': 'true' |
31 |
});
|
32 |
|
33 |
p.append(btn.append(span)); |
34 |
|
35 |
|
36 |
|
37 |
caption.append(title); |
38 |
caption.append(desc); |
39 |
caption.append(p); |
40 |
|
41 |
thumbNail.append(img); |
42 |
thumbNail.append(caption); |
43 |
mainDiv.append(thumbNail); |
44 |
return mainDiv; |
45 |
|
46 |
|
47 |
}
|
Các code ở trên là khá đơn giản vì vậy tôi sẽ không đi sâu vào chi tiết.
Tiếp theo, chúng ta sẽ lặp qua phản hồi JSON
và tạo HTML bằng hàm CreateThumb
. Chúng ta dự định hiển thị ba mong ước trên mỗi hàng. Vì vậy, chúng ta sẽ kiểm tra điều đó và tạo một hàng mới cho ba mong ước. Thêm code sau đây vào hàm callback success
khi gọi AJAX trong dashboard.html
.
1 |
var itemsPerRow = 0; |
2 |
var div = $('<div>').attr('class', 'row'); |
3 |
for (var i = 0; i < data.length; i++) { |
4 |
|
5 |
|
6 |
if (itemsPerRow < 3) { |
7 |
|
8 |
if (i == data.length - 1) { |
9 |
div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath)); |
10 |
$('.well').append(div); |
11 |
} else { |
12 |
div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath)); |
13 |
itemsPerRow++; |
14 |
}
|
15 |
} else { |
16 |
$('.well').append(div); |
17 |
div = $('<div>').attr('class', 'row'); |
18 |
div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath)); |
19 |
if (i == data.length - 1) { |
20 |
$('.well').append(div); |
21 |
}
|
22 |
itemsPerRow = 1; |
23 |
}
|
24 |
|
25 |
}
|
Lưu các thay đổi và khởi động lại máy chủ. Đăng nhập vào ứng dụng và khi ở trên trang dashboard, bạn sẽ có thể thấy những mong ước được thêm bởi những người dùng khác nhau, với một tuỳ chọn để thích chúng.



Tiếp theo, hãy thêm một sự kiện click
vào các nút like ở dưới thumbnail của các mong ước. Vì chúng ta đã tạo các nút một cách tự động, nên chúng ta sẽ cần gắn sự kiện click vào các nút bằng phương thức on của jQuery.
1 |
$(document).on('click', '[id^="btn_"]', function() { |
2 |
// Event function can be added here
|
3 |
});
|
Cài đặt Chức năng Like
Hãy bắt đầu bằng cách tạo ra một bảng để theo dõi những like mà một mong ước cụ thể thu được. Tạo một bảng gọi là tbl_likes
.
1 |
CREATE TABLE `BucketList`.`tbl_likes` ( |
2 |
`wish_id` INT NOT NULL, |
3 |
`like_id` INT NOT NULL AUTO_INCREMENT, |
4 |
`user_id` INT NULL, |
5 |
`wish_like` INT NULL DEFAULT 0 ; |
6 |
PRIMARY KEY (`like_id`)); |
Bây giờ bất cứ khi nào người dùng like hoặc không like một mong ước cụ thể, chúng ta sẽ cập nhật bảng này. Hãy tạo một thủ tục lưu trữ MySQL để cập nhật bảng trên.
1 |
DELIMITER $$ |
2 |
|
3 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_AddUpdateLikes`( |
4 |
p_wish_id int, |
5 |
p_user_id int, |
6 |
p_like int |
7 |
)
|
8 |
BEGIN
|
9 |
if (select exists (select 1 from tbl_likes where wish_id = p_wish_id and user_id = p_user_id)) then |
10 |
|
11 |
update tbl_likes set wish_like = p_like where wish_id = p_wish_id and user_id = p_user_id; |
12 |
|
13 |
else
|
14 |
|
15 |
insert into tbl_likes( |
16 |
wish_id, |
17 |
user_id, |
18 |
wish_like
|
19 |
)
|
20 |
values( |
21 |
p_wish_id, |
22 |
p_user_id, |
23 |
p_like
|
24 |
);
|
25 |
|
26 |
end if; |
27 |
END
|
Trong thủ tục lưu trữ này, chúng ta chỉ cần kiểm tra có người nào like một mong ước hay không. Nếu có người đó like, thì chúng ta cập nhật hoặc thêm một like mới.
Hãy tạo ra một phương thức Python để gọi thủ tục được lưu trữ ở trên.
1 |
@app.route('/addUpdateLike',methods=['POST']) |
2 |
def addUpdateLike(): |
3 |
try: |
4 |
if session.get('user'): |
5 |
_wishId = request.form['wish'] |
6 |
_like = request.form['like'] |
7 |
_user = session.get('user') |
8 |
|
9 |
|
10 |
conn = mysql.connect() |
11 |
cursor = conn.cursor() |
12 |
cursor.callproc('sp_AddUpdateLikes',(_wishId,_user,_like)) |
13 |
data = cursor.fetchall() |
14 |
|
15 |
if len(data) is 0: |
16 |
conn.commit() |
17 |
return json.dumps({'status':'OK'}) |
18 |
else: |
19 |
return render_template('error.html',error = 'An error occurred!') |
20 |
|
21 |
else: |
22 |
return render_template('error.html',error = 'Unauthorized Access') |
23 |
except Exception as e: |
24 |
return render_template('error.html',error = str(e)) |
25 |
finally: |
26 |
cursor.close() |
27 |
conn.close() |
Đây là phương thức Python sẽ gọi thủ tục lưu trữ sp_AddUpdateLikes
. Trong phương thức này, chúng ta kiểm tra session người dùng hợp lệ và sau đó truyền ID
và trạng thái like
của mong ước vào thủ tục được lưu trữ để cập nhật. Khi người dùng nhấp chuột vào nút like, chúng ta cần phải gọi phương thức Python /addUpdateLike
. Do đó, hãy thêm đoạn code sau vào sự kiện nhấn vào nút like
trong dashboard.html
.
1 |
$(document).on('click', '[id^="btn_"]', function() { |
2 |
$.ajax({ |
3 |
url: '/addUpdateLike', |
4 |
method: 'POST', |
5 |
data: { |
6 |
wish: $(this).attr('id').split('_')[1], |
7 |
like: 1 |
8 |
},
|
9 |
success: function(response) { |
10 |
console.log(response); |
11 |
},
|
12 |
error: function(error) { |
13 |
console.log(error); |
14 |
}
|
15 |
});
|
16 |
});
|
Hiện tại, chúng ta đang gán giá trị của like một cách thủ công. Vì vậy, hãy lưu các thay đổi và khởi động lại máy chủ. Đăng nhập vào ứng dụng và nhấp vào nút like dưới bất kỳ thumbnail của mong ước nào. Bây giờ kiểm tra bảng tbl_likes
và bạn sẽ thấy một mục dữ liệu trong đó.
Tóm tắt
Trong phần này của hướng dẫn, chúng ta đã điền dữ liệu vào trang dashboard của ứng dụng bằng những mong ước được tạo bởi những người dùng khác nhau. Chúng ta cũng gắn một nút like vào mỗi thumbnail để người dùng có thể like một mong ước cụ thể. Trong phần tiếp theo, chúng ta sẽ xem cách bỏ like và hiển thị tổng số lượt like đã nhận được bởi một mong ước cụ thể.
Hãy cho chúng tôi biết những đề xuất hoặc bất kỳ hiệu chỉnh nào trong phần bình luận bên dưới nhé. Mã nguồn từ hướng dẫn này có sẵn trên GitHub.