Chuyển tới nội dung chính

Dự Án Tổng Hợp — Quản Lý Sinh Viên

Giai đoạn 3 – Nâng Cao & Thực Hành · Mục tiêu: Tổng hợp mọi kiến thức để xây dựng ứng dụng hoàn chỉnh.


1. Giới thiệu dự án

Xây dựng chương trình Quản lý Sinh viên với CRUD, lưu trữ JSON.

Tính năng & Kiến thức áp dụng

Tính năngKiến thức
Thêm sinh viênOOP, input, validation
Hiển thị danh sáchList, format, sort
Tìm kiếmString methods, filter
Cập nhật / XóaDict, exception handling
Thống kêHàm tổng hợp, comprehension
Lưu / đọc fileJSON, file I/O
Xuất báo cáoCSV, formatting

Cấu trúc thư mục

quan_ly_sv/
├── main.py
├── models/
│ ├── __init__.py
│ └── sinh_vien.py
├── services/
│ ├── __init__.py
│ └── sv_service.py
├── utils/
│ ├── __init__.py
│ ├── file_helper.py
│ └── validator.py
└── data/
└── sinh_vien.json

2. Code chi tiết

2.1. Class SinhVien

# models/sinh_vien.py

class SinhVien:
"""Đại diện cho một sinh viên."""

def __init__(self, ma_sv, ho_ten, lop,
diem_toan, diem_ly, diem_hoa):
self.ma_sv = ma_sv
self.ho_ten = ho_ten
self.lop = lop
self.diem_toan = diem_toan
self.diem_ly = diem_ly
self.diem_hoa = diem_hoa

@property
def diem_tb(self):
return round((self.diem_toan + self.diem_ly + self.diem_hoa) / 3, 2)

@property
def xep_loai(self):
if any(d < 3 for d in [self.diem_toan, self.diem_ly, self.diem_hoa]):
return "Yếu "
dtb = self.diem_tb
if dtb >= 9: return "Xuất sắc "
if dtb >= 8: return "Giỏi "
if dtb >= 6.5: return "Khá "
if dtb >= 5: return "TB "
return "Yếu "

def to_dict(self):
return {
"ma_sv": self.ma_sv,
"ho_ten": self.ho_ten,
"lop": self.lop,
"diem_toan": self.diem_toan,
"diem_ly": self.diem_ly,
"diem_hoa": self.diem_hoa,
}

@classmethod
def from_dict(cls, data):
return cls(**data)

def __str__(self):
return (f"| {self.ma_sv:^8} | {self.ho_ten:<20} | {self.lop:^6} | "
f"{self.diem_toan:>5} | {self.diem_ly:>5} | {self.diem_hoa:>5} | "
f"{self.diem_tb:>6} | {self.xep_loai:<13} |")

2.2. File Helper

# utils/file_helper.py

import json
import csv
import os

DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
JSON_PATH = os.path.join(DATA_DIR, "sinh_vien.json")


def doc_du_lieu():
if not os.path.exists(JSON_PATH):
return []
try:
with open(JSON_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []


def luu_du_lieu(ds):
os.makedirs(DATA_DIR, exist_ok=True)
with open(JSON_PATH, "w", encoding="utf-8") as f:
json.dump(ds, f, ensure_ascii=False, indent=2)


def xuat_csv(ds, file_path):
if not ds:
return
with open(file_path, "w", encoding="utf-8-sig", newline="") as f:
fields = ["ma_sv", "ho_ten", "lop", "diem_toan",
"diem_ly", "diem_hoa", "diem_tb", "xep_loai"]
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(ds)

2.3. Validator

# utils/validator.py

def nhap_so(prompt, min_val=None, max_val=None):
while True:
try:
val = float(input(prompt))
if min_val is not None and val < min_val:
print(f" Phải >= {min_val}")
continue
if max_val is not None and val > max_val:
print(f" Phải <= {max_val}")
continue
return val
except ValueError:
print(" Nhập số hợp lệ!")


def nhap_chuoi(prompt, min_len=1):
while True:
val = input(prompt).strip()
if len(val) >= min_len:
return val
print(f" Nhập ít nhất {min_len} ký tự!")

2.4. Service

# services/sv_service.py

from models.sinh_vien import SinhVien
from utils.file_helper import doc_du_lieu, luu_du_lieu, xuat_csv
from utils.validator import nhap_so, nhap_chuoi


class SVService:
def __init__(self):
self.ds_sv = []
self._tai_du_lieu()

def _tai_du_lieu(self):
self.ds_sv = [SinhVien.from_dict(d) for d in doc_du_lieu()]

def _luu(self):
luu_du_lieu([sv.to_dict() for sv in self.ds_sv])

def _tim_theo_ma(self, ma):
for sv in self.ds_sv:
if sv.ma_sv.upper() == ma.upper():
return sv
return None

def them_sv(self):
print("\n THÊM SINH VIÊN")
ma = nhap_chuoi("Mã SV: ").upper()
if self._tim_theo_ma(ma):
print(f" Mã {ma} đã tồn tại!")
return

ten = nhap_chuoi("Họ tên: ").title()
lop = nhap_chuoi("Lớp: ").upper()
toan = nhap_so("Điểm Toán (0-10): ", 0, 10)
ly = nhap_so("Điểm Lý (0-10): ", 0, 10)
hoa = nhap_so("Điểm Hóa (0-10): ", 0, 10)

self.ds_sv.append(SinhVien(ma, ten, lop, toan, ly, hoa))
self._luu()
print(f" Đã thêm {ten} ({ma})")

def hien_thi_ds(self):
if not self.ds_sv:
print("\n Chưa có sinh viên!")
return
self._in_bang(" DANH SÁCH SINH VIÊN", self.ds_sv)

def _in_bang(self, tieu_de, ds):
ke = "+" + "-"*10 + "+" + "-"*22 + "+" + "-"*8 + "+" + \
"-"*7 + "+" + "-"*7 + "+" + "-"*7 + "+" + "-"*8 + "+" + "-"*15 + "+"
print(f"\n{tieu_de}")
print(ke)
print(f"| {'Mã SV':^8} | {'Họ tên':<20} | {'Lớp':^6} | "
f"{'Toán':>5} | {'Lý':>5} | {'Hóa':>5} | "
f"{'ĐTB':>6} | {'Xếp loại':<13} |")
print(ke)
for sv in ds:
print(sv)
print(ke)
print(f" Tổng: {len(ds)} sinh viên")

def tim_kiem(self):
print("\n TÌM KIẾM")
print("1. Theo mã SV")
print("2. Theo tên")
print("3. Theo lớp")
chon = input("Chọn: ").strip()

if chon == "1":
ma = nhap_chuoi("Mã SV: ").upper()
sv = self._tim_theo_ma(ma)
if sv:
self._in_bang(f" Kết quả: {ma}", [sv])
else:
print(f" Không tìm thấy {ma}")
elif chon == "2":
kw = nhap_chuoi("Tên: ").lower()
kq = [sv for sv in self.ds_sv if kw in sv.ho_ten.lower()]
if kq:
self._in_bang(f" Kết quả: '{kw}'", kq)
else:
print(f" Không tìm thấy '{kw}'")
elif chon == "3":
lop = nhap_chuoi("Lớp: ").upper()
kq = [sv for sv in self.ds_sv if sv.lop.upper() == lop]
if kq:
self._in_bang(f" Lớp {lop}", kq)
else:
print(f" Không có SV lớp {lop}")

def xoa_sv(self):
ma = nhap_chuoi("\n Mã SV cần xóa: ").upper()
sv = self._tim_theo_ma(ma)
if not sv:
print(f" Không tìm thấy {ma}")
return
xn = input(f"Xóa {sv.ho_ten}? (y/n): ").strip().lower()
if xn == "y":
self.ds_sv.remove(sv)
self._luu()
print(f" Đã xóa {sv.ho_ten}")

def thong_ke(self):
if not self.ds_sv:
print("\n Chưa có dữ liệu!")
return

dtb = [sv.diem_tb for sv in self.ds_sv]
xl = [sv.xep_loai for sv in self.ds_sv]

print("\n THỐNG KÊ")
print("=" * 40)
print(f"Tổng SV: {len(self.ds_sv)}")
print(f"ĐTB cao: {max(dtb)}")
print(f"ĐTB thấp: {min(dtb)}")
print(f"ĐTB chung: {sum(dtb)/len(dtb):.2f}")

print("\n Phân loại:")
for loai in ["Xuất sắc ", "Giỏi ", "Khá ", "TB ", "Yếu "]:
sl = xl.count(loai)
pt = sl / len(self.ds_sv) * 100
print(f" {loai:<15} {sl:>3} ({pt:>5.1f}%) {'█' * int(pt/5)}")

def xuat_bao_cao(self):
if not self.ds_sv:
print("\n Chưa có dữ liệu!")
return
data = []
for sv in self.ds_sv:
d = sv.to_dict()
d["diem_tb"] = sv.diem_tb
d["xep_loai"] = sv.xep_loai
data.append(d)
xuat_csv(data, "data/bao_cao_sv.csv")
print(" Đã xuất file data/bao_cao_sv.csv")

2.5. Main

# main.py

from services.sv_service import SVService


def main():
svc = SVService()

actions = {
"1": svc.them_sv,
"2": svc.hien_thi_ds,
"3": svc.tim_kiem,
"4": svc.xoa_sv,
"5": svc.thong_ke,
"6": svc.xuat_bao_cao,
}

while True:
print("\n" + "=" * 45)
print(" QUẢN LÝ SINH VIÊN")
print("=" * 45)
print(" 1. Thêm sinh viên")
print(" 2. Hiển thị danh sách")
print(" 3. Tìm kiếm")
print(" 4. Xóa sinh viên")
print(" 5. Thống kê")
print(" 6. Xuất báo cáo CSV")
print(" 0. Thoát")
print("-" * 45)

chon = input(" Chọn: ").strip()

if chon == "0":
print(" Tạm biệt!")
break

action = actions.get(chon)
if action:
try:
action()
except Exception as e:
print(f" Lỗi: {e}")
else:
print(" Lựa chọn không hợp lệ!")


if __name__ == "__main__":
main()

3. Chạy chương trình

# Tạo cấu trúc thư mục
mkdir -p quan_ly_sv/models quan_ly_sv/services quan_ly_sv/utils quan_ly_sv/data

# Tạo __init__.py
touch quan_ly_sv/models/__init__.py
touch quan_ly_sv/services/__init__.py
touch quan_ly_sv/utils/__init__.py

# Copy code → chạy
cd quan_ly_sv
python main.py

4. Mở rộng

Tính năngKiến thức mới
GUI (Tkinter)Thư viện GUI
SQLiteSQL, sqlite3
Web app (Flask)Web framework
Biểu đồmatplotlib
Unit testpytest

Tổng kết khoá học

Chúc mừng bạn đã hoàn thành 13 bài Python!

Giai đoạnNội dung
Nhập mônCài đặt, biến, kiểu dữ liệu, điều kiện, vòng lặp
Cấu trúc dữ liệuString, List, Tuple, Set, Dict, Function
Nâng caoFile I/O, OOP, Module, Dự án thực tế

Bước tiếp theo

  • Web: Flask / Django
  • Data Science: Pandas, NumPy, Matplotlib
  • AI/ML: scikit-learn, TensorFlow
  • Game: Pygame

:::tip Lời khuyên "Cách tốt nhất để học lập trình là viết code mỗi ngày. Đừng chỉ đọc — hãy thực hành!" :::


Quay lại: Roadmap