Chủ Nhật, 23 tháng 11, 2025

CĐ Tin học 10 – Bài 10: Tạo dữ liệu đầu vào với Format as Table
CĐ Tin học 10 – Bài 10

Tạo dữ liệu đầu vào với Format as Table

Dựa trên Dự án du lịch: học sinh học cách tạo các bảng dữ liệu đầu vào (giá vận chuyển, giá phòng, vé tham quan,…) và định dạng bảng bằng Format as Table trong phần mềm bảng tính.

🧳
Tình huống mở đầu: Một công ty du lịch cần lập bảng dự toán chi phí chuyến đi cho nhiều đoàn khách khác nhau. Nếu tính tay sẽ rất dễ nhầm lẫn.
Cần tính toán nhanh Dễ cập nhật khi dữ liệu đổi Trình bày rõ ràng, đẹp mắt
1. Khởi động: Vì sao cần phần mềm bảng tính?

Câu hỏi gợi ý cho HS:

  • Làm sao tính nhanh chi phí chuyến đi cho từng khách với yêu cầu khác nhau?
  • Nếu chỉ dùng máy tính cầm tay và giấy bút, có khó khăn gì?
  • Phần mềm nào của Tin học có thể giúp tính toán nhanh – chính xác – tự động?

Mục tiêu: Nhận ra việc sử dụng phần mềm bảng tính là cần thiết để tổ chức dữ liệu và tính toán trong dự án du lịch.

2. Kiến thức mới: Bảng dữ liệu & Format as Table

2.1. Quan sát bảng dữ liệu du lịch (tương tự Hình 1.2 SGK)

Ví dụ một bảng vé tham quan:

Điểm du lịch Vé người lớn Vé trẻ em Phụ phí (VND)
Vịnh Hạ Long 450.000 300.000 50.000
Sa Pa 400.000 250.000 40.000
Phú Quốc 500.000 320.000 60.000
Tổng (ví dụ) Dùng hàm =SUM() nếu cần

HS nhận xét:

  • Tiêu đề bảng in đậm, cỡ lớn, căn giữa, nền màu nhạt.
  • Các ô dữ liệu có đường viền, số thì căn phải, chữ căn trái.
  • Các hàng xen kẽ màu (Banded Rows) giúp dễ nhìn từng dòng.
  • Dòng cuối có thể dùng hàm SUM hoặc AVERAGE để tính tổng/trung bình.

2.2. Ưu điểm khi dùng Format as Table

  • Tự động thêm Filter để lọc, sắp xếp dữ liệu.
  • Dễ bật Total Row để tính tổng, trung bình,…
  • Bảng tự mở rộng khi thêm dữ liệu mới ở cuối.
  • Thay đổi Table Styles (màu sắc, kiểu trình bày) rất nhanh.
3. Các thành phần quan trọng của bảng (Table)
3.1. Thành phần & chức năng
Table Name
Tên của bảng, dùng trong công thức, ví dụ: =SUM(Transport[Đơn_giá])
Header Row
Dòng tiêu đề chứa tên cột (Loại xe, Đơn giá, Số km,…).
Total Row
Dòng cuối của bảng để tính tổng, trung bình,… bằng hàm SUM, AVERAGE,…
Banded Rows
Hàng xen kẽ màu giúp dễ đọc dữ liệu từng dòng.
First/Last Column
Nhấn mạnh cột đầu tiên hoặc cột cuối cùng (in đậm, tô màu).
Filter Button
Nút lọc ở dòng tiêu đề để lọc theo giá, loại dịch vụ,…
Table Styles
Bộ kiểu định dạng có sẵn, thay đổi màu nền, đường kẻ, kiểu chữ.
3.2. Bài tập nhanh

HS có thể thảo luận và trả lời:

  • Trong bảng “Giá phòng”, em sẽ đặt Table Name là gì?
  • Khi muốn hiển thị dòng tổng, em bật tùy chọn nào?
  • Hàng xen kẽ màu trong bảng gọi là gì?
Gợi ý: Nên đặt tên bảng bằng tiếng Anh/không dấu, không khoảng trắng, ví dụ: Transport, HotelPrice, Ticket.
4. Luyện tập: Tạo các bảng dữ liệu đầu vào

4.1. Xác định công thức dự toán chi phí

  • Chi phí vận chuyển: = Số_km × Đơn_giá/km
  • Chi phí lưu trú: = Số_đêm × Giá_phòng/đêm
  • Tổng chi phí chuyến đi: = SUM(các_chi_phí_thành_phần)

4.2. Tạo các bảng dữ liệu đầu vào

  • Bảng 1 – Giá vận chuyển (Table Name gợi ý: Transport)
    • Loại xe
    • Số chỗ
    • Đơn giá/km
    • Phụ phí
  • Bảng 2 – Giá phòng (Table Name: Hotel)
    • Loại phòng
    • Số người
    • Giá/đêm
    • Ghi chú
  • Bảng 3 – Vé tham quan (Table Name: Ticket)
    • Điểm du lịch
    • Vé người lớn
    • Vé trẻ em
    • Phụ phí

4.3. Định dạng bảng bằng Format as Table

  1. Chọn toàn bộ vùng dữ liệu (bao gồm dòng tiêu đề).
  2. Trong Excel: vào Home > Format as Table.
  3. Chọn một kiểu bảng (ví dụ Medium Style 2).
  4. Tích chọn “My table has headers” nếu bảng có tiêu đề.
  5. Đặt lại Table Name cho dễ nhớ (Transport, Hotel, Ticket,…).
  6. Bật các tuỳ chọn: Header Row, Total Row, Banded Rows, Filter Button.
Yêu cầu sản phẩm thực hành:
Có dòng tiêu đề rõ ràng Có màu xen kẽ dòng (Banded Rows) Có Total Row nếu cần Đặt Table Name đúng quy tắc
5. Vận dụng: Hoàn thiện bảng dự toán chuyến đi

Nhiệm vụ vận dụng:

  • Thực hiện các yêu cầu trong bài tập trang 32 SGK (nếu có trong lớp học).
  • Hoàn thiện các bảng dữ liệu: vận chuyển, lưu trú, ăn uống, vé tham quan,…
  • Định dạng toàn bộ bằng Format as Table.
  • Tạo công thức tính tổng chi phí chuyến du lịch cho đoàn khách.
Thử thách thêm: Hãy thay đổi số lượng khách hoặc số ngày đi, quan sát xem bảng có tự cập nhật chi phí không. Nếu có, nghĩa là em đã xây dựng được bảng dự toán động.
6. Quiz nhanh: Ôn lại kiến thức Format as Table

1. Lợi ích quan trọng nhất của Format as Table là gì?

2. Thành phần nào sau đây không phải là một phần của Table?

3. Khi đặt tên bảng (Table Name), cách nào là hợp lý nhất?

Nội dung được biên soạn lại dưới dạng bài giảng học sinh từ giáo án CĐ Tin học 10 – Bài 10 (Tạo dữ liệu đầu vào với Format as Table).

Thứ Tư, 12 tháng 11, 2025

PHỤ LỤC
HƯỚNG DẪN SỬ DỤNG HỆ THỐNG K12ONLINE
TẠI TRƯỜNG THPT PHAN CHU TRINH, GIA LAI

(Ban hành kèm theo …………)

I. CĂN CỨ XÂY DỰNG HƯỚNG DẪN

  • Chủ trương, đường lối của Đảng và Nhà nước về chuyển đổi số trong giáo dục; các văn bản chỉ đạo của Bộ GD&ĐT và Sở GD&ĐT Gia Lai.
  • Kế hoạch năm học 2025–2026 và kế hoạch chuyển đổi số của Trường THPT Phan Chu Trinh.
  • Hệ thống quản lý học và thi trực tuyến K12Online do Viettel phát triển.
  • Tài liệu, video tập huấn, cẩm nang sử dụng K12Online do nhà cung cấp phát hành.
  • Đặc điểm thực tế của Trường THPT Phan Chu Trinh: địa bàn miền núi, điều kiện kinh tế khó khăn, kỹ năng số còn hạn chế.

II. HƯỚNG DẪN SỬ DỤNG THEO ĐỐI TƯỢNG

Lưu ý chung: Tất cả các đối tượng đều dùng chung hệ thống K12Online, truy cập qua web hoặc ứng dụng di động. Mỗi người có một tài khoản riêng, không chia sẻ cho người khác.

1. ĐỐI VỚI NGƯỜI QUẢN TRỊ TRANG (ADMIN)

1.1. Vai trò, nhiệm vụ

  • Quản trị, thiết lập ban đầu hệ thống: năm học, lớp học, môn học.
  • Khởi tạo, nhập, phân quyền tài khoản GV và HS.
  • Quản lý các module: điểm danh, kho học liệu, báo cáo.
  • Hỗ trợ GV, HS; phối hợp với Viettel khi có lỗi kỹ thuật.

1.2. Các thao tác cơ bản

1) Đăng nhập hệ thống

  • Mở trình duyệt → nhập địa chỉ K12Online của trường.
  • Nhập tên đăng nhập, mật khẩu.
  • Đổi mật khẩu lần đầu.

2) Khởi tạo và quản lý tài khoản

  • Đồng bộ dữ liệu lớp học (nếu có tích hợp EMIS).
  • Import danh sách giáo viên, học sinh.
  • Xuất danh sách phát cho GVCN.

3) Phân quyền người dùng

  • Vào Cấu hình → Phân quyền.
  • Gán vai trò: Admin, BGH, tổ trưởng, GV, HS.

4) Cấu hình module

  • Cấu hình điểm danh: buổi học, tiết học.
  • Thiết lập kho học liệu, phân công giảng dạy.

5) Giám sát, hỗ trợ

  • Theo dõi báo cáo dạy – học, bài kiểm tra.
  • Hỗ trợ GV/HS khi quên mật khẩu, lỗi kỹ thuật.

2. ĐỐI VỚI TỔ TRƯỞNG / TỔ PHÓ CHUYÊN MÔN

2.1. Vai trò, nhiệm vụ

  • Quản lý chuyên môn tổ trên K12Online.
  • Kiểm duyệt bài giảng, kế hoạch bài dạy của GV.
  • Theo dõi tiến độ giảng dạy, chất lượng kiểm tra.

2.2. Các thao tác chính

1) Truy cập module tổ chuyên môn

  • Đăng nhập → Chọn Quản lý đào tạo → Tổ của mình.

2) Kiểm duyệt bài giảng

  • Xem danh sách bài chờ duyệt.
  • Xem trước nội dung.
  • Nhấn “Duyệt” hoặc “Từ chối” kèm góp ý.

3) Theo dõi giáo viên trong tổ

  • Kiểm tra bài giảng đã xuất bản.
  • Theo dõi bài kiểm tra, tiến độ chương trình.

4) Báo cáo chuyên môn

  • Tổng hợp số tiết dạy, bài kiểm tra trên hệ thống.

3. ĐỐI VỚI GIÁO VIÊN BỘ MÔN (GVBM)

3.1. Vai trò, nhiệm vụ

  • Dạy học các môn được phân công.
  • Xây dựng bài giảng, học liệu, bài kiểm tra.
  • Điểm danh, giao bài, chấm bài, nhận xét HS.

3.2. Các thao tác cơ bản

1) Truy cập lớp/môn được phân công

  • Vào Đào tạo → Danh sách lớp/môn.

2) Tạo bài giảng – học liệu

  • Vào Bài giảng → Thêm mới.
  • Thêm video, file PDF, hình ảnh, hoạt động tương tác.
  • Xuất bản hoặc gửi kiểm duyệt.

3) Tạo bài kiểm tra

  • Chọn loại bài kiểm tra.
  • Thiết lập thời gian, trộn đề, số câu hỏi.
  • Giao bài cho lớp.

4) Dạy học – giao nhiệm vụ

  • Tạo lớp học ảo, đăng thông báo.
  • Giao bài tập, câu hỏi thảo luận.
  • Thu bài, chấm bài, phản hồi HS.

5) Lưu ý vùng khó khăn

  • File nhẹ, video ngắn.
  • Cung cấp bản PDF để tải offline.
  • Hạn chế link ngoài.

4. ĐỐI VỚI GIÁO VIÊN CHỦ NHIỆM (GVCN)

4.1. Vai trò, nhiệm vụ

  • Quản lý lớp chủ nhiệm: sĩ số, thông tin HS.
  • Theo dõi chuyên cần, nề nếp.
  • Trao đổi với phụ huynh và GVBM.

4.2. Các thao tác chính

1) Kiểm tra danh sách lớp

  • Vào Quản lý lớp → Lớp chủ nhiệm.

2) Quản lý điểm danh

  • Vào Điểm danh → Chọn lớp, ngày, tiết học.
  • Đánh dấu có mặt/vắng.

3) Theo dõi kết quả học tập

  • Xem điểm, đánh giá, học lực của HS.

4) Liên lạc với phụ huynh

  • Dùng thông báo trường hoặc ứng dụng liên kết.

5) Lưu ý vùng khó

  • Hỗ trợ HS không có thiết bị.
  • Tổ chức nhóm học chung.

5. ĐỐI VỚI HỌC SINH

5.1. Vai trò, nhiệm vụ

  • Đăng nhập hệ thống để học, xem bài giảng, làm bài kiểm tra.
  • Tuân thủ quy định học trực tuyến.

5.2. Các thao tác cơ bản

1) Đăng nhập

  • Nhận tài khoản từ GVCN.
  • Đăng nhập, đổi mật khẩu dễ nhớ.

2) Vào lớp – xem bài học

  • Vào Lớp học → Môn học.
  • Xem video, tài liệu, tải bài về khi cần.

3) Làm bài kiểm tra

  • Chọn bài kiểm tra.
  • Làm bài → Nộp bài.

4) Xem điểm

  • Vào Kết quả để xem điểm và xem lại bài.

5) Lưu ý với HS vùng khó khăn

  • Ưu tiên dùng ứng dụng trên điện thoại.
  • Tải tài liệu lúc mạng mạnh để xem offline.
  • Báo GVCN nếu không có thiết bị.

Kết luận: Việc triển khai và sử dụng K12Online trong năm học 2025–2026 là nhiệm vụ trọng tâm của nhà trường, góp phần thúc đẩy chuyển đổi số và nâng cao chất lượng dạy học tại Trường THPT Phan Chu Trinh, Gia Lai.

Thứ Bảy, 8 tháng 11, 2025

Bài 1: Tự viết Theme Blogger chuẩn XML từ con số 0

Mục tiêu bài học:

  • Hiểu được cấu trúc cơ bản của theme Blogger (XML, thẻ b:skinb:section).
  • Biết cách khắc phục các lỗi phổ biến khi tạo theme thủ công.
  • Tự tạo được một theme Blogger hoạt động đầy đủ.

1. Khởi động

💡 Gợi ý: Bạn có biết Blogger không chấp nhận theme thiếu b:section hoặc b:skin không?

2. Kiến thức mới

2.1. Cấu trúc cơ bản của theme Blogger

Mọi theme Blogger đều bắt đầu bằng khai báo XML và gồm hai phần chính: <head><body>.

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html b:version='2' class='v2' expr:dir='data:blog.languageDirection'> <head>...</head> <body>...</body> </html>
⚠️ Lưu ý: Blogger sẽ báo lỗi nếu thiếu các thẻ đặc biệt:
  • <b:skin>: chứa CSS nội bộ, chỉ được có một.
  • <b:section>: chứa các widget, bắt buộc phải có ít nhất một.

2.2. Thêm thẻ <b:skin>

Đặt trong phần <head>, đây là nơi viết CSS cho theme:

<b:skin><![CDATA[ body {font-family: Arial; background:#f8f8f8;} header, footer {background:#333; color:#fff; text-align:center; padding:10px;} ]]></b:skin>
💡 Mẹo nhỏ: Hãy bọc CSS trong <![CDATA[ ... ]]> để tránh lỗi cú pháp XML.

2.3. Tạo ít nhất một b:section

Đây là nơi chứa các widget, ví dụ widget bài đăng Blog1:

<b:section id='main' class='content' showaddelement='yes' maxwidgets='10'> <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/> </b:section>

2.4. Hoàn chỉnh theme Blogger

Sao chép đoạn mã đầy đủ dưới đây và dán vào phần Chỉnh sửa HTML trong Blogger:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html> <html b:version='2' class='v2' expr:dir='data:blog.languageDirection' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'> <head> <title><data:blog.pageTitle/></title> <b:include data='blog' name='all-head-content'/> <b:skin><![CDATA[ body {font-family: Arial, sans-serif; background:#f8f8f8; margin:0;} header, footer {background:#333; color:#fff; text-align:center; padding:10px;} nav {background:#666; color:#fff; padding:8px;} main {display:flex; gap:16px; padding:16px;} .content {flex:1; background:#fff; padding:12px; border-radius:8px;} .right {width:300px; background:#fff; padding:12px; border-radius:8px;} ]]></b:skin> </head> <body> <header>Phần đầu trang</header> <nav>Menu điều hướng</nav> <main> <b:section id='main' class='content' showaddelement='yes' maxwidgets='10'> <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/> </b:section> <b:section id='sidebar' class='right' showaddelement='yes' maxwidgets='20'> <b:widget id='Profile1' type='Profile' title='Giới thiệu'/> </b:section> </main> <b:section id='footer-sec' class='footer' showaddelement='yes' maxwidgets='5'> <b:widget id='Attribution1' type='Attribution' title='' /> </b:section> <footer>Phần cuối trang</footer> <b:include data='blog' name='all-body-content'/> </body> </html>

3. Thực hành nhanh

✏️ Câu hỏi: Vì sao Blogger bắt buộc phải có ít nhất một b:section?
Hiện gợi ý

4. Bảng tóm tắt

ThẻChức năngLưu ý
<b:skin>Chứa CSS nội bộChỉ 1 thẻ duy nhất
<b:section>Vùng chứa widgetTối thiểu 1
<b:widget>Hiển thị module (bài đăng, hồ sơ...)Phải nằm trong section

5. Kết luận

🌿 Bạn đã nắm được cấu trúc cơ bản của theme Blogger. Hãy lưu theme, xem kết quả và bắt đầu thử thêm phần menu động, breadcrumb, hoặc giao diện responsive cho bài học tiếp theo!
Blogger Theme — Bài 1

Tự viết Theme Blogger chuẩn XML từ con số 0 (có thể chạy ngay)

Hướng dẫn từng bước để tạo một theme tối thiểu nhưng hợp lệ: có b:skin, ít nhất một b:section, và widget Blog1.

1) Xương sống của theme Blogger

Một file XML hợp lệ luôn gồm <head>, <body> và các namespace của Blogger.

Quan trọng

Thiếu <b:skin> → báo lỗi “There should be one and only one skin…”.
Thiếu <b:section> → báo lỗi “A theme must have at least one b:section tag.”

2) Thêm b:skin trong <head>

<![CDATA[ ... ]]> giúp nhúng CSS an toàn trong XML.

<b:skin><![CDATA[
body { font-family: Arial; background:#f8f8f8; }
header, footer { background:#333; color:#fff; text-align:center; padding:10px; }
]]></b:skin>

3) Tạo ít nhất một b:section chứa widget

<b:section id='main' class='content' showaddelement='yes' maxwidgets='10'>
  <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/>
</b:section>

4) Mã theme tối thiểu (chạy được ngay)

Dán toàn bộ đoạn dưới vào Giao diện → Chỉnh sửa HTML. Có sẵn Blog1, Profile1Attribution1.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:version='2' class='v2' expr:dir='data:blog.languageDirection'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'>

<head>
  <title><data:blog.pageTitle/></title>
  <b:include data='blog' name='all-head-content'/>

  <b:skin><![CDATA[
    body {font-family: Arial, sans-serif; background:#f8f8f8; margin:0;}
    header, footer {background:#333; color:#fff; text-align:center; padding:10px;}
    nav {background:#666; color:#fff; padding:8px;}
    main {display:flex; gap:16px; padding:16px;}
    .content {flex:1; background:#fff; padding:12px; border-radius:8px;}
    .right   {width:300px; background:#fff; padding:12px; border-radius:8px;}
  ]]></b:skin>
</head>

<body>
  <header>Phần đầu trang</header>
  <nav>Menu điều hướng</nav>

  <main>
    <b:section id='main' class='content' showaddelement='yes' maxwidgets='10'>
      <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/>
    </b:section>

    <b:section id='sidebar' class='right' showaddelement='yes' maxwidgets='20'>
      <b:widget id='Profile1' type='Profile' title='Giới thiệu'/>
    </b:section>
  </main>

  <b:section id='footer-sec' class='footer' showaddelement='yes' maxwidgets='5'>
    <b:widget id='Attribution1' type='Attribution' title='' />
  </b:section>

  <footer>Phần cuối trang</footer>
  <b:include data='blog' name='all-body-content'/>
</body>
</html>
❓ Lỗi thường gặp & cách sửa nhanh
  • There should be one and only one skin… → Bạn có 0 hoặc >1 b:skin. Để đúng 1 thẻ, đặt trong <head>.
  • A theme must have at least one b:section tag → Thêm <b:section> và ít nhất một widget, ví dụ Blog1.
  • CSS không ăn → Quên bọc trong <![CDATA[ ... ]]> hoặc selector bị theme khác ghi đè.
Tiếp tục Bài 2: Menu động & Responsive

🧭 Bài 2: Tạo menu động + responsive sidebar cho Blogger

Tiếp nối Bài 1: khung theme tối thiểu. Hôm nay ta thêm menu động (thêm/sửa trong Layout) và sidebar responsive.


🎯 Mục tiêu

  • Tạo Menu dùng LinkList (thêm link trong Layout → Add a Gadget).
  • Thiết kế sidebar ẩn ở màn hình nhỏ, hiện ở màn hình lớn.
  • Thêm nút Hamburger bật/tắt menu khi mobile.

1) Thêm CSS cho menu & sidebar (đặt vào <b:skin> trong theme)

Mẹo: Mở Theme → Edit HTML, tìm <b:skin>...</b:skin>, chèn thêm khối dưới bên trong CDATA.

/* ==== MENU TOP ==== */
.navbar{background:#0f172a;color:#fff;display:flex;align-items:center;gap:12px;padding:10px 14px;position:sticky;top:0;z-index:20}
.navbar .brand{font-weight:700;letter-spacing:.3px}
.navbar .toggle{margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border:1px solid rgba(255,255,255,.25);border-radius:10px;cursor:pointer}
.navbar ul{list-style:none;margin:0;padding:0;display:flex;gap:12px;flex-wrap:wrap}
.navbar a{color:#fff;text-decoration:none;padding:8px 10px;border-radius:8px}
.navbar a:hover{background:rgba(255,255,255,.12)}

/* mobile: menu ẩn sau nút toggle */
@media (max-width: 768px){
  .navbar ul{display:none;width:100%}
  .navbar ul.open{display:flex;flex-direction:column;padding-top:8px}
}

/* ==== LAYOUT CHÍNH ==== */
main{display:grid;grid-template-columns:1fr;gap:18px;padding:16px}
@media (min-width: 992px){
  main{grid-template-columns:1fr 300px}
}
.content,.sidebar{background:#ffffff;border:1px solid #e5e7eb;border-radius:14px;padding:14px}
.sidebar .widget{margin-bottom:14px}
  

2) Thêm phần tử menu + sidebar vào Template

Trong Theme → Edit HTML, chèn các <b:section> bên dưới vào vị trí phù hợp (ví dụ ngay sau <body> và trong <main>). Hãy dán đúng như XML (không đổi dấu).

Navbar (menu động) — thêm ngay sau dấu <body>:
<!-- NAVIGATION (LinkList để quản lý menu trong Layout) -->
<div class='navbar'>
  <div class='brand'><data:blog.title/></div>

  <div class='toggle' expr:onclick='"document.getElementById(\"navlinks\").classList.toggle(\"open\")"' aria-label='Toggle menu'>
    ☰
  </div>

  <ul id='navlinks'>
    <b:section id='nav' class='navlinks' showaddelement='yes' maxwidgets='1'>
      <b:widget id='LinkList1' type='LinkList' title='Menu' />
    </b:section>
  </ul>
</div>
  
Main + Sidebar — đặt trong phần thân trang (nếu theo Bài 1 bạn đã có <main>, chỉ cần đảm bảo có 2 section dưới):
<main>
  <b:section id='main' class='content' showaddelement='yes' maxwidgets='10'>
    <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/>
  </b:section>

  <b:section id='sidebar' class='sidebar' showaddelement='yes' maxwidgets='20'>
    <b:widget id='PopularPosts1' type='PopularPosts' title='Bài nổi bật'/>
    <b:widget id='Labels1' type='Label' title='Chủ đề'/>
    <b:widget id='Profile1' type='Profile' title='Tác giả'/>
  </b:section>
</main>
  

3) Thêm/sửa menu trong Layout

  1. Vào Layout ▶ khu vực “Menu” bạn vừa tạo (LinkList).
  2. Nhấn Edit ▶ thêm các mục: Tên & URL.
  3. Kéo thả để sắp xếp thứ tự. Lưu lại.

✅ Kết quả

  • Menu hiển thị link do bạn quản lý ở Layout.
  • Mobile có nút ☰ để bật/tắt menu (không cần jQuery).
  • Sidebar chuyển cột khi ≥ 992px, mobile thì xuống dưới nội dung.

4) Lỗi thường gặp & cách tránh

  • SAXParse “&” entity: chỉ xảy ra khi bạn dán nội dung bài viết vào Theme. Hãy dán code trên vào Theme, còn content bài giảng thì đăng như bài viết.
  • Trùng nhiều <b:skin>: Theme chỉ có 1 thẻ. Gộp CSS vào cùng CDATA cũ.
  • Thiếu <b:section>: đảm bảo có id='nav', id='main', id='sidebar'.

🚀 Bài 3 (sắp ra mắt): Tạo menu con (dropdown) + sticky sidebar + scroll-to-top.

Tự tay viết Theme Blogger chuẩn XML từ con số 0 (có thể chạy được ngay)

Bạn muốn tự học code theme Blogger theo cách chuyên nghiệp? Bài giảng này sẽ hướng dẫn bạn viết một mẫu theme Blogger đơn giản nhất — nhưng đúng chuẩn XML để trình soạn thảo Blogger chấp nhận và chạy được ngay.


1️⃣ Cấu trúc cơ bản của một theme Blogger

Một file theme Blogger (đuôi .xml) luôn tuân thủ cấu trúc sau:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:version='2' class='v2' ...>
  <head> ... </head>
  <body> ... </body>
</html>

Đây là khung cơ bản của mọi theme. Tuy nhiên, Blogger sẽ báo lỗi nếu thiếu hai phần sau:

  • <b:skin> – nơi chứa CSS nội bộ của theme (chỉ được có một).
  • <b:section> – vùng chứa các widget (bắt buộc phải có ít nhất một).

2️⃣ Thêm thẻ <b:skin> để tránh lỗi “There should be one and only one skin...”

Thẻ này phải đặt trong phần <head>. Dưới đây là ví dụ:

<b:skin><![CDATA[
body {
  font-family: Arial;
  background: #f8f8f8;
}
header, footer {
  background: #333;
  color: #fff;
  text-align: center;
  padding: 10px;
}
]]></b:skin>

Dấu <![CDATA[ ... ]]> giúp bạn chèn CSS vào XML mà không bị lỗi cú pháp.


3️⃣ Thêm thẻ <b:section> để tránh lỗi “A theme must have at least one b:section tag”

Một theme hợp lệ phải có ít nhất một <b:section> để chứa các widget. Thông thường, nó nằm ở vùng nội dung chính (<main>) và sidebar.

Ví dụ một section cơ bản:

<b:section id='main' class='content' showaddelement='yes' maxwidgets='10'>
  <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/>
</b:section>

4️⃣ Toàn bộ mã theme Blogger tối thiểu (chạy được ngay)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:version='2' class='v2' expr:dir='data:blog.languageDirection'
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:b='http://www.google.com/2005/gml/b'
  xmlns:data='http://www.google.com/2005/gml/data'
  xmlns:expr='http://www.google.com/2005/gml/expr'>

<head>
  <title><data:blog.pageTitle/></title>
  <b:include data='blog' name='all-head-content'/>

  <b:skin><![CDATA[
    body {font-family: Arial, sans-serif; background:#f8f8f8; margin:0;}
    header, footer {background:#333; color:#fff; text-align:center; padding:10px;}
    nav {background:#666; color:#fff; padding:8px;}
    main {display:flex; gap:16px; padding:16px;}
    .content {flex:1; background:#fff; padding:12px; border-radius:8px;}
    .right   {width:300px; background:#fff; padding:12px; border-radius:8px;}
  ]]></b:skin>
</head>

<body>
  <header>Phần đầu trang</header>
  <nav>Menu điều hướng</nav>

  <main>
    <b:section id='main' class='content' showaddelement='yes' maxwidgets='10'>
      <b:widget id='Blog1' type='Blog' title='Bài đăng' locked='true'/>
    </b:section>

    <b:section id='sidebar' class='right' showaddelement='yes' maxwidgets='20'>
      <b:widget id='Profile1' type='Profile' title='Giới thiệu'/>
    </b:section>
  </main>

  <b:section id='footer-sec' class='footer' showaddelement='yes' maxwidgets='5'>
    <b:widget id='Attribution1' type='Attribution' title=''/>
  </b:section>

  <footer>Phần cuối trang</footer>
  <b:include data='blog' name='all-body-content'/>
</body>
</html>

5️⃣ Kết luận

Sau khi dán đoạn mã này vào phần Sửa HTML trong Blogger, bạn đã có một theme hợp lệ. Từ đây, bạn có thể:

  • Thêm CSS nâng cao (responsive, grid, animation...)
  • Chèn thêm nhiều <b:section> để tạo layout phức tạp
  • Nhúng JS hoặc thư viện ngoài như Bootstrap, FontAwesome
Hãy thử chỉnh sửa, lưu lại và xem kết quả – bạn sẽ hiểu rõ cách hoạt động của Blogger theme hơn bất kỳ tài liệu nào khác! 🚀

Thứ Ba, 4 tháng 11, 2025

Bài 8: Full Flask App - Website Quản lý học sinh

Bài 8: Full Flask App - Website Quản lý học sinh

Bài học này sẽ hướng dẫn học sinh THPT tạo một Website quản lý học sinh hoàn chỉnh sử dụng Flask, SQLite, Bootstrap, Chart.jsFlask-Login. Chúng ta sẽ tích hợp tất cả các bài trước thành một ứng dụng duy nhất.

1. Mục tiêu bài học

  • Hiểu cách tích hợp đăng nhập và phân quyền với Flask-Login.
  • Thực hiện đầy đủ các thao tác CRUD trên dữ liệu học sinh.
  • Upload hình ảnh học sinh và hiển thị trên website.
  • Thống kê điểm trung bình bằng biểu đồ Chart.js.
  • Thiết kế giao diện responsive với Bootstrap.

2. Cấu trúc dự án

Chúng ta sẽ tạo cấu trúc dự án Flask như sau:


FlaskStudentApp/
│-- app.py
│-- students.db
│-- templates/
│   │-- base.html
│   │-- login.html
│   │-- dashboard.html
│   │-- student_list.html
│   │-- student_form.html
│   │-- chart.html
│-- static/
    │-- css/
    │-- images/

3. Cài đặt Flask và các thư viện cần thiết


pip install Flask Flask-Login Flask-WTF WTForms

4. Mã nguồn chính - app.py

Giải thích: Đây là file Flask chính, quản lý routing, database, và đăng nhập.


from flask import Flask, render_template, redirect, url_for, request, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, login_required, logout_user, UserMixin, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'phanchutrinh'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db'
app.config['UPLOAD_FOLDER'] = 'static/images'
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

# User model
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True)
    password = db.Column(db.String(150))
    role = db.Column(db.String(50))  # admin or teacher

# Student model
class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    age = db.Column(db.Integer)
    grade = db.Column(db.Float)
    photo = db.Column(db.String(100))

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/login', methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password, password):
            login_user(user)
            return redirect(url_for('dashboard'))
        flash('Sai tên đăng nhập hoặc mật khẩu!')
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

@app.route('/')
@login_required
def dashboard():
    students = Student.query.all()
    return render_template('dashboard.html', students=students)

# CRUD student
@app.route('/student/add', methods=['GET','POST'])
@login_required
def add_student():
    if request.method == 'POST':
        name = request.form['name']
        age = request.form['age']
        grade = request.form['grade']
        photo_file = request.files['photo']
        photo_path = None
        if photo_file:
            photo_path = os.path.join(app.config['UPLOAD_FOLDER'], photo_file.filename)
            photo_file.save(photo_path)
        student = Student(name=name, age=age, grade=grade, photo=photo_file.filename if photo_file else None)
        db.session.add(student)
        db.session.commit()
        flash('Thêm học sinh thành công!')
        return redirect(url_for('dashboard'))
    return render_template('student_form.html', action="Add")

@app.route('/student/edit/', methods=['GET','POST'])
@login_required
def edit_student(id):
    student = Student.query.get_or_404(id)
    if request.method == 'POST':
        student.name = request.form['name']
        student.age = request.form['age']
        student.grade = request.form['grade']
        photo_file = request.files['photo']
        if photo_file:
            student.photo = photo_file.filename
            photo_file.save(os.path.join(app.config['UPLOAD_FOLDER'], photo_file.filename))
        db.session.commit()
        flash('Cập nhật học sinh thành công!')
        return redirect(url_for('dashboard'))
    return render_template('student_form.html', action="Edit", student=student)

@app.route('/student/delete/')
@login_required
def delete_student(id):
    student = Student.query.get_or_404(id)
    db.session.delete(student)
    db.session.commit()
    flash('Xóa học sinh thành công!')
    return redirect(url_for('dashboard'))

# Chart route
@app.route('/chart')
@login_required
def chart():
    students = Student.query.all()
    names = [s.name for s in students]
    grades = [s.grade for s in students]
    return render_template('chart.html', names=names, grades=grades)

if __name__ == '__main__':
    db.create_all()
    # Tạo user mặc định admin nếu chưa có
    if not User.query.filter_by(username='admin').first():
        admin = User(username='admin', password=generate_password_hash('123456', method='sha256'), role='admin')
        db.session.add(admin)
        db.session.commit()
    app.run(debug=True)

5. Templates chính

Ví dụ: login.html





    
    
    Login


Đăng nhập

Tương tự, bạn có thể tạo dashboard.html, student_form.html, chart.html tích hợp Bootstrap & Chart.js để hiển thị dữ liệu và thống kê điểm.

6. Hướng dẫn chạy dự án

  1. Tạo thư mục dự án và các subfolder templates, static/images.
  2. Lưu file app.py và các template vào đúng thư mục.
  3. Chạy lệnh python app.py và mở trình duyệt http://127.0.0.1:5000/.
  4. Đăng nhập với username admin và password 123456.

7. Kết luận

Qua bài học này, học sinh THPT sẽ hiểu cách tạo một **ứng dụng web quản lý học sinh hoàn chỉnh** với Flask, học cách tích hợp **CRUD, đăng nhập, upload hình ảnh, thống kê**, và thiết kế giao diện responsive bằng Bootstrap. Đây là nền tảng để tham gia **hội giảng chuyên đề STEM hoặc các cuộc thi Tin học ứng dụng**.

Bài 7: Flask + Upload hình ảnh học sinh + Thống kê nâng cao

🖼️ BÀI 7: UPLOAD ẢNH HỌC SINH + THỐNG KÊ NÂNG CAO

Thực hành Flask – Lưu hình ảnh và hiển thị thống kê nâng cao


🎯 Mục tiêu

  • Biết cách upload file ảnh trong Flask.
  • Lưu đường dẫn ảnh vào cơ sở dữ liệu.
  • Hiển thị hình ảnh học sinh và điểm số trên trang thống kê.
Flask upload demo

⚙️ 1. Cấu trúc dự án


flask_upload/
 ├─ app.py
 ├─ static/uploads/
 ├─ students.db
 └─ templates/
      ├─ upload.html
      ├─ gallery.html

💻 2. File app.py


from flask import Flask, render_template, request, redirect, url_for
import sqlite3, os
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.secret_key = 'uploadkey'

# Tạo bảng
def init_db():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS students (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    hoten TEXT,
                    diem REAL,
                    image TEXT
                )''')
    conn.commit()
    conn.close()

@app.route('/')
def upload_form():
    return render_template('upload.html')

@app.route('/upload', methods=['POST'])
def upload_file():
    hoten = request.form['hoten']
    diem = request.form['diem']
    file = request.files['image']
    if file:
        filename = secure_filename(file.filename)
        path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(path)
        conn = sqlite3.connect('students.db')
        c = conn.cursor()
        c.execute("INSERT INTO students (hoten, diem, image) VALUES (?, ?, ?)", (hoten, diem, filename))
        conn.commit()
        conn.close()
    return redirect('/gallery')

@app.route('/gallery')
def gallery():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute("SELECT hoten, diem, image FROM students")
    data = c.fetchall()
    conn.close()
    return render_template('gallery.html', students=data)

if __name__ == '__main__':
    os.makedirs('static/uploads', exist_ok=True)
    init_db()
    app.run(debug=True)

📋 3. File templates/upload.html


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Upload ảnh học sinh</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light d-flex justify-content-center align-items-center vh-100">
<form action="/upload" method="POST" enctype="multipart/form-data" class="p-4 bg-white shadow rounded" style="width:350px;">
  <h3 class="text-primary text-center mb-3">🖼️ Upload ảnh học sinh</h3>
  <input class="form-control mb-2" type="text" name="hoten" placeholder="Họ và tên" required>
  <input class="form-control mb-2" type="number" step="0.1" name="diem" placeholder="Điểm TB" required>
  <input class="form-control mb-3" type="file" name="image" accept="image/*" required>
  <button class="btn btn-success w-100">Tải lên</button>
  <a href="/gallery" class="d-block text-center mt-2">📸 Xem thư viện</a>
</form>
</body>
</html>

📷 4. File templates/gallery.html


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Thư viện học sinh</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
  <h2 class="text-primary text-center mb-4">📸 Thư viện học sinh</h2>
  <div class="row">
    {% for hs in students %}
      <div class="col-md-3 mb-4">
        <div class="card shadow">
          <img src="{{ url_for('static', filename='uploads/' + hs[2]) }}" class="card-img-top" alt="{{ hs[0] }}">
          <div class="card-body text-center">
            <h5>{{ hs[0] }}</h5>
            <p>Điểm: {{ hs[1] }}</p>
          </div>
        </div>
      </div>
    {% endfor %}
  </div>
</div>
</body>
</html>

📈 5. Mở rộng nâng cao

  • Tính điểm TB toàn trường, hiển thị biểu đồ (Chart.js).
  • Tô màu khác nhau cho học sinh Giỏi / Khá / TB.
  • Cho phép upload nhiều ảnh cùng lúc.

© 2025 Trường THPT Phan Chu Trinh – Bài 7: Flask Upload + Thống kê nâng cao

Bài 6: Flask + Đăng nhập / Phân quyền người dùng

🔐 BÀI 6: FLASK + ĐĂNG NHẬP & PHÂN QUYỀN NGƯỜI DÙNG

Ứng dụng Flask-Login cơ bản cho trường THPT Phan Chu Trinh


🎯 Mục tiêu bài học

  • Hiểu khái niệm xác thực (login) và phân quyền người dùng.
  • Biết cách sử dụng Flask-Login để quản lý đăng nhập.
  • Tạo tài khoản quản trị (admin) và người dùng (giáo viên).
Flask login illustration

🧩 1. Cài đặt thư viện


pip install flask flask-login

⚙️ 2. Cấu trúc dự án


flask_login/
 ├─ app.py
 ├─ users.db
 └─ templates/
      ├─ login.html
      ├─ dashboard.html
      └─ unauthorized.html

💻 3. File app.py


from flask import Flask, render_template, redirect, url_for, request
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
import sqlite3

app = Flask(__name__)
app.secret_key = 'secret123'

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

# --- Tạo DB người dùng ---
def init_db():
    conn = sqlite3.connect('users.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT,
        password TEXT,
        role TEXT
    )''')
    c.execute("INSERT OR IGNORE INTO users (id, username, password, role) VALUES (1,'admin','123','admin')")
    c.execute("INSERT OR IGNORE INTO users (id, username, password, role) VALUES (2,'gv1','123','teacher')")
    conn.commit()
    conn.close()

# --- Flask-Login ---
class User(UserMixin):
    def __init__(self, id, username, role):
        self.id = id
        self.username = username
        self.role = role

@login_manager.user_loader
def load_user(user_id):
    conn = sqlite3.connect('users.db')
    c = conn.cursor()
    c.execute("SELECT id, username, role FROM users WHERE id=?", (user_id,))
    data = c.fetchone()
    conn.close()
    if data:
        return User(data[0], data[1], data[2])
    return None

@app.route('/')
def home():
    return redirect('/login')

@app.route('/login', methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        conn = sqlite3.connect('users.db')
        c = conn.cursor()
        c.execute("SELECT id, username, password, role FROM users WHERE username=?", (username,))
        data = c.fetchone()
        conn.close()
        if data and data[2] == password:
            user = User(data[0], data[1], data[3])
            login_user(user)
            return redirect('/dashboard')
        else:
            return "Sai tài khoản hoặc mật khẩu!"
    return render_template('login.html')

@app.route('/dashboard')
@login_required
def dashboard():
    if current_user.role == 'admin':
        message = "Bạn đang đăng nhập với quyền QUẢN TRỊ"
    else:
        message = "Bạn là GIÁO VIÊN"
    return render_template('dashboard.html', user=current_user, message=message)

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect('/login')

if __name__ == '__main__':
    init_db()
    app.run(debug=True)

📋 4. File templates/login.html


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Đăng nhập</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light d-flex align-items-center justify-content-center vh-100">
<form method="POST" class="p-4 bg-white shadow rounded" style="width:320px;">
  <h3 class="text-center text-primary mb-3">🔐 Đăng nhập</h3>
  <input type="text" name="username" class="form-control mb-3" placeholder="Tên đăng nhập" required>
  <input type="password" name="password" class="form-control mb-3" placeholder="Mật khẩu" required>
  <button class="btn btn-primary w-100">Đăng nhập</button>
</form>
</body>
</html>

📊 5. File templates/dashboard.html


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Trang quản lý</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5 text-center">
  <h2>Xin chào, {{ user.username }} 👋</h2>
  <p>{{ message }}</p>
  <a href="/logout" class="btn btn-danger mt-3">Đăng xuất</a>
</div>
</body>
</html>

🧠 Thử thách

  • Thêm chức năng “Đăng ký tài khoản mới”.
  • Chỉ Admin mới được xem danh sách giáo viên.
  • Giáo viên chỉ được truy cập trang riêng của mình.

© 2025 Trường THPT Phan Chu Trinh – Bài 6: Flask Login & Role System

Bài 5: Flask + Bootstrap + Chart.js - Thống kê học sinh

📊 BÀI 5: FLASK + BOOTSTRAP + BIỂU ĐỒ (CHART.JS)

Ứng dụng Flask hiển thị thống kê điểm trung bình học sinh


🎯 Mục tiêu bài học

  • Kết hợp Flask với Bootstrap để tạo giao diện hiện đại, thân thiện.
  • Dùng Chart.js để hiển thị biểu đồ thống kê điểm trung bình.
  • Tổng hợp dữ liệu từ SQLite để vẽ biểu đồ.
Flask Chart.js demo

🧩 1. Cấu trúc thư mục dự án


flask_chart/
 ├─ app.py
 ├─ students.db
 └─ templates/
      ├─ index.html
      └─ chart.html

⚙️ 2. File app.py


from flask import Flask, render_template
import sqlite3

app = Flask(__name__)

# --- Tạo bảng dữ liệu nếu chưa có ---
def init_db():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS students (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    hoten TEXT,
                    lop TEXT,
                    diem REAL
                )''')
    conn.commit()
    conn.close()

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/chart')
def chart():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute("SELECT lop, AVG(diem) FROM students GROUP BY lop")
    data = c.fetchall()
    conn.close()

    # Chuyển dữ liệu thành 2 danh sách: lớp và điểm trung bình
    labels = [row[0] for row in data]
    values = [round(row[1], 2) for row in data]

    return render_template('chart.html', labels=labels, values=values)

if __name__ == '__main__':
    init_db()
    app.run(debug=True)
💡 Giải thích: – Lấy dữ liệu điểm trung bình theo từng lớp bằng câu SQL GROUP BY. – Gửi dữ liệu sang chart.html để vẽ biểu đồ Chart.js.

📄 3. File templates/index.html – Trang chủ


<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>Trang chủ - Thống kê học sinh</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">

<div class="container mt-5 text-center">
  <h1 class="text-primary">📘 Thống kê học sinh với Flask</h1>
  <p>Dự án Flask cuối cùng - THPT Phan Chu Trinh</p>
  <a href="/chart" class="btn btn-success mt-3">Xem biểu đồ thống kê điểm TB</a>
</div>

</body>
</html>

📊 4. File templates/chart.html – Hiển thị biểu đồ


<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>Biểu đồ thống kê điểm trung bình</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>

<body class="bg-white">
  <div class="container mt-5 mb-5">
    <h2 class="text-center text-primary">📊 Biểu đồ thống kê điểm trung bình theo lớp</h2>
    <canvas id="myChart" height="120"></canvas>

    <div class="text-center mt-4">
      <a href="/" class="btn btn-outline-secondary">⬅️ Quay lại trang chủ</a>
    </div>
  </div>

  <script>
  const ctx = document.getElementById('myChart');
  const chart = new Chart(ctx, {
    type: 'bar',
    data: {
      labels: {{ labels|tojson }},
      datasets: [{
        label: 'Điểm trung bình theo lớp',
        data: {{ values|tojson }},
        backgroundColor: [
          '#3498db','#1abc9c','#9b59b6','#f1c40f','#e67e22','#e74c3c'
        ]
      }]
    },
    options: {
      scales: {
        y: {
          beginAtZero: true,
          max: 10
        }
      }
    }
  });
  </script>
</body>
</html>

📈 5. Kết quả hiển thị

  • Biểu đồ cột hiển thị điểm trung bình của từng lớp.
  • Màu sắc tự động thay đổi theo lớp học.
  • Dễ dàng mở rộng thành biểu đồ tròn, đường, radar...
Chart.js Flask Demo

🧠 6. Thử thách nâng cao

  • Thêm biểu đồ tròn (Pie Chart) thống kê học lực: Giỏi – Khá – Trung bình.
  • Kết hợp Bootstrap Card hiển thị tổng số học sinh, điểm cao nhất.
  • Thêm filter chọn lớp để xem riêng một lớp.

📚 Kết luận

  • Học sinh đã kết hợp được 3 công nghệ: Flask + SQLite + Chart.js.
  • Biết cách trình bày giao diện web bằng Bootstrap.
  • Đây là nền tảng để phát triển ứng dụng web trực quan và sinh động hơn.

© 2025 Trường THPT Phan Chu Trinh – Gia Lai | Bài giảng Flask – Phần 5: Biểu đồ thống kê (Chart.js)

Bài 4: Flask + SQLite (CRUD hoàn chỉnh)

📘 BÀI 4: FLASK + SQLITE (CẬP NHẬT & XÓA DỮ LIỆU – CRUD HOÀN CHỈNH)


🎯 Mục tiêu bài học

  • Hiểu mô hình CRUD (Create – Read – Update – Delete).
  • Hoàn thiện ứng dụng Flask quản lý học sinh.
  • Tạo giao diện chỉnh sửa và xóa học sinh trực tiếp từ bảng dữ liệu.
CRUD Flask illustration

🧩 1. Cấu trúc thư mục dự án


flask_crud/
 ├─ app.py
 ├─ students.db
 └─ templates/
      ├─ index.html
      ├─ list.html
      └─ edit.html

⚙️ 2. File app.py


import sqlite3
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

# --- Tạo database ---
def init_db():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS students (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    hoten TEXT,
                    lop TEXT,
                    diem REAL
                )''')
    conn.commit()
    conn.close()

# --- Trang nhập học sinh ---
@app.route('/')
def index():
    return render_template('index.html')

# --- Lưu học sinh mới ---
@app.route('/add', methods=['POST'])
def add():
    hoten = request.form.get('hoten')
    lop = request.form.get('lop')
    diem = request.form.get('diem')
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute("INSERT INTO students (hoten, lop, diem) VALUES (?, ?, ?)",
              (hoten, lop, diem))
    conn.commit()
    conn.close()
    return redirect('/list')

# --- Xem danh sách ---
@app.route('/list')
def list_students():
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute("SELECT * FROM students")
    data = c.fetchall()
    conn.close()
    return render_template('list.html', students=data)

# --- Chỉnh sửa ---
@app.route('/edit/', methods=['GET', 'POST'])
def edit(id):
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    if request.method == 'POST':
        hoten = request.form.get('hoten')
        lop = request.form.get('lop')
        diem = request.form.get('diem')
        c.execute("UPDATE students SET hoten=?, lop=?, diem=? WHERE id=?",
                  (hoten, lop, diem, id))
        conn.commit()
        conn.close()
        return redirect(url_for('list_students'))
    else:
        c.execute("SELECT * FROM students WHERE id=?", (id,))
        data = c.fetchone()
        conn.close()
        return render_template('edit.html', hs=data)

# --- Xóa ---
@app.route('/delete/')
def delete(id):
    conn = sqlite3.connect('students.db')
    c = conn.cursor()
    c.execute("DELETE FROM students WHERE id=?", (id,))
    conn.commit()
    conn.close()
    return redirect('/list')

if __name__ == '__main__':
    init_db()
    app.run(debug=True)
💡 Giải thích:
- /edit/<id> hiển thị form chỉnh sửa học sinh theo mã.
- /delete/<id> xóa học sinh được chọn.
- url_for() giúp Flask điều hướng linh hoạt.

📋 3. File templates/index.html – Form thêm học sinh


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Thêm học sinh</title>
<style>
body { font-family: Arial; background: #e9f4ff; display: flex; justify-content: center; align-items: center; height: 100vh; }
.form-box { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.2); width: 400px; }
input { width: 100%; padding: 8px; margin-top: 6px; border: 1px solid #ccc; border-radius: 5px; }
button { width: 100%; background: #2980b9; color: #fff; padding: 10px; border: none; border-radius: 5px; margin-top: 15px; }
a { display: block; text-align: center; margin-top: 10px; }
</style>
</head>
<body>
  <form class="form-box" action="/add" method="POST">
    <h2>➕ Thêm học sinh</h2>
    <label>Họ và tên:</label>
    <input type="text" name="hoten" required>
    <label>Lớp:</label>
    <input type="text" name="lop" required>
    <label>Điểm trung bình:</label>
    <input type="number" name="diem" min="0" max="10" step="0.1" required>
    <button type="submit">Lưu học sinh</button>
    <a href="/list">📋 Xem danh sách</a>
  </form>
</body>
</html>

📄 4. File templates/list.html – Danh sách học sinh


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Danh sách học sinh</title>
<style>
body { font-family: Arial; background: #f0f4f8; padding: 30px; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
th, td { border: 1px solid #ccc; padding: 10px; text-align: center; }
th { background: #e9f2ff; }
a.button { background: #2980b9; color: white; padding: 6px 12px; border-radius: 5px; text-decoration: none; }
a.del { background: #e74c3c; }
</style>
</head>
<body>
  <h2>📚 Danh sách học sinh</h2>
  <table>
    <tr>
      <th>Mã</th><th>Họ tên</th><th>Lớp</th><th>Điểm TB</th><th>Hành động</th>
    </tr>
    {% for hs in students %}
    <tr>
      <td>{{ hs[0] }}</td><td>{{ hs[1] }}</td><td>{{ hs[2] }}</td><td>{{ hs[3] }}</td>
      <td>
        <a class="button" href="/edit/{{ hs[0] }}">✏️ Sửa</a>
        <a class="button del" href="/delete/{{ hs[0] }}" onclick="return confirm('Bạn có chắc muốn xóa?')">🗑️ Xóa</a>
      </td>
    </tr>
    {% endfor %}
  </table>
  <a href="/" class="button">➕ Thêm học sinh mới</a>
</body>
</html>

🧾 5. File templates/edit.html – Chỉnh sửa học sinh


<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Chỉnh sửa học sinh</title>
<style>
body { font-family: Arial; background: #f0f4f8; display: flex; justify-content: center; align-items: center; height: 100vh; }
.form-box { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.2); width: 400px; }
input { width: 100%; padding: 8px; margin-top: 6px; border: 1px solid #ccc; border-radius: 5px; }
button { width: 100%; background: #2980b9; color: #fff; padding: 10px; border: none; border-radius: 5px; margin-top: 15px; }
</style>
</head>
<body>
  <form class="form-box" method="POST">
    <h2>✏️ Cập nhật học sinh</h2>
    <label>Họ và tên:</label>
    <input type="text" name="hoten" value="{{ hs[1] }}" required>
    <label>Lớp:</label>
    <input type="text" name="lop" value="{{ hs[2] }}" required>
    <label>Điểm trung bình:</label>
    <input type="number" name="diem" value="{{ hs[3] }}" min="0" max="10" step="0.1" required>
    <button type="submit">Cập nhật</button>
    <a href="/list">⬅️ Quay lại</a>
  </form>
</body>
</html>

🏁 6. Kết quả khi chạy

Khi truy cập http://127.0.0.1:5000, bạn có thể:

  • ➕ Thêm học sinh
  • 📋 Xem danh sách
  • ✏️ Sửa học sinh
  • 🗑️ Xóa học sinh
Flask CRUD demo

🚀 7. Thử thách mở rộng cho học sinh

  • Thêm ô tìm kiếm theo tên hoặc lớp.
  • Thêm phân loại học lực (Giỏi – Khá – Trung bình).
  • Làm trang thống kê tổng số học sinh và điểm TB cao nhất.

📚 Kết luận

  • Bạn đã xây dựng được ứng dụng Flask hoàn chỉnh có cơ sở dữ liệu thực.
  • Đây là mô hình chuẩn CRUD trong lập trình web.
  • Có thể dùng kiến thức này để tạo ứng dụng quản lý học sinh, thư viện, hoặc câu lạc bộ trong trường.

© 2025 Trường THPT Phan Chu Trinh – Gia Lai | Bài giảng Flask – Phần 4: CRUD hoàn chỉnh

Bài đăng phổ biến

💬 Bình luận

💬 Bình luận

📌 Danh sách bình luận