المشروع النهائي: إدارة درجات الطلاب (الجزء 3)

في الدرسين السابقين، بنينا طبقة البيانات وطبقة المنطق. في هذا الدرس، نجمع كل شيء معاً — كتابة واجهة قائمة سطر أوامر كاملة. بعد إكمال هذا الدرس، سيكون لديك نظام إدارة درجات طلاب قابل للاستخدام حقاً.


كود النظام الكامل

ادمج الكود من الدروس الثلاثة السابقة في ملف واحد، بالإضافة إلى واجهة القائمة:

مثال: نظام إدارة الدرجات الكامل

PYTHON
import json
import os
import statistics

# ====== التهيئة ======
DATA_FILE = "grade_system.json"

DEFAULT_DATA = {
    "students": {},
    "subjects": ["Chinese", "Math", "English"],
    "classes": []
}

# ====== طبقة البيانات ======

def load_data():
    if not os.path.exists(DATA_FILE):
        save_data(DEFAULT_DATA.copy())
        return DEFAULT_DATA.copy()
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_data(data):
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def add_student(student_id, name, class_name):
    data = load_data()
    if student_id in data["students"]:
        return False, f"Student ID {student_id} already exists!"
    data["students"][student_id] = {"name": name, "class_name": class_name, "scores": {}}
    if class_name not in data["classes"]:
        data["classes"].append(class_name)
    save_data(data)
    return True, f"Added student: {name} ({student_id})"

def delete_student(student_id):
    data = load_data()
    if student_id not in data["students"]:
        return False, f"Student ID {student_id} does not exist!"
    name = data["students"][student_id]["name"]
    del data["students"][student_id]
    save_data(data)
    return True, f"Deleted student: {name}"

def add_score(student_id, subject, score):
    data = load_data()
    if student_id not in data["students"]:
        return False, f"Student ID {student_id} does not exist!"
    if subject not in data["subjects"]:
        return False, f"Subject {subject} does not exist!"
    if not isinstance(score, (int, float)) or score < 0 or score > 100:
        return False, "Score must be between 0 and 100!"
    data["students"][student_id]["scores"][subject] = score
    save_data(data)
    return True, f"Entered {data['students'][student_id]['name']}'s {subject}: {score}"

# ====== طبقة المنطق ======

def get_student_report(student_id):
    data = load_data()
    student = data["students"].get(student_id)
    if not student:
        return None
    report = {"name": student["name"], "class_name": student["class_name"],
              "scores": dict(student["scores"])}
    scores = list(student["scores"].values())
    if scores:
        report["average"] = round(sum(scores) / len(scores), 1)
        report["total"] = sum(scores)
        report["max_score"] = max(scores)
        report["min_score"] = min(scores)
    else:
        report["average"] = report["total"] = report["max_score"] = report["min_score"] = None
    return report

def get_class_ranking(class_name=None):
    data = load_data()
    results = []
    for sid, info in data["students"].items():
        if class_name and info["class_name"] != class_name:
            continue
        scores = list(info["scores"].values())
        total = sum(scores) if scores else 0
        avg = round(total / len(scores), 1) if scores else 0
        results.append({"id": sid, "name": info["name"], "class": info["class_name"],
                        "total": total, "avg": avg, "count": len(scores)})
    results.sort(key=lambda x: x["total"], reverse=True)
    for i, r in enumerate(results, 1):
        r["rank"] = i
    return results

def get_subject_stats():
    data = load_data()
    stats = {}
    for subject in data["subjects"]:
        scores = []
        for info in data["students"].values():
            if subject in info["scores"]:
                scores.append(info["scores"][subject])
        if scores:
            stats[subject] = {"avg": round(sum(scores) / len(scores), 1),
                              "max": max(scores), "min": min(scores), "count": len(scores)}
    return stats

# ====== طبقة العرض ======

def show_main_menu():
    print("\n" + "=" * 40)
    print("Student Grade Management System")
    print("=" * 40)
    print("1. Student Management")
    print("2. Grade Management")
    print("3. Statistics and Reports")
    print("4. View Data")
    print("5. Exit")
    print("=" * 40)

def show_student_menu():
    print("\n--- Student Management ---")
    print("1. Add Student")
    print("2. Delete Student")
    print("3. Return to Main Menu")

def show_score_menu():
    print("\n--- Grade Management ---")
    print("1. Enter Grade")
    print("2. Return to Main Menu")

def show_stats_menu():
    print("\n--- Statistics and Reports ---")
    print("1. Individual Report Card")
    print("2. School-Wide Ranking")
    print("3. Subject Averages")
    print("4. Return to Main Menu")

def student_management():
    """القائمة الفرعية لإدارة الطلاب"""
    while True:
        show_student_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Student ID: ").strip()
            name = input("Name: ").strip()
            cls = input("Class: ").strip()
            success, msg = add_student(sid, name, cls)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "2":
            sid = input("Enter student ID to delete: ").strip()
            success, msg = delete_student(sid)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "3":
            break
        
        else:
            print("Invalid choice!")

def score_management():
    """القائمة الفرعية لإدارة الدرجات"""
    while True:
        show_score_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Student ID: ").strip()
            subject = input("Subject: ").strip()
            try:
                score = float(input("Score (0-100): ").strip())
            except ValueError:
                print("Error: Score must be a number!")
                continue
            success, msg = add_score(sid, subject, score)
            print(f"  {'OK' if success else 'Error'}: {msg}")
        
        elif choice == "2":
            break
        
        else:
            print("Invalid choice!")

def stats_query():
    """القائمة الفرعية لاستعلامات الإحصائيات"""
    while True:
        show_stats_menu()
        choice = input("Enter choice: ").strip()
        
        if choice == "1":
            sid = input("Enter student ID: ").strip()
            report = get_student_report(sid)
            if not report:
                print("Error: Student not found!")
                continue
            print(f"\nReport Card — {report['name']} ({report['class_name']})")
            print("-" * 30)
            for subject, score in report["scores"].items():
                print(f"  {subject}: {score}")
            if report["total"] is not None:
                print(f"  Total: {report['total']}")
                print(f"  Average: {report['average']}")
                print(f"  Highest: {report['max_score']}")
                print(f"  Lowest: {report['min_score']}")
            else:
                print("  (No grades yet)")
        
        elif choice == "2":
            ranking = get_class_ranking()
            if not ranking:
                print("No data!")
                continue
            print("\nSchool-Wide Ranking")
            print(f"{'Rank':<4}{'Name':<10}{'Class':<10}{'Total':<8}{'Avg':<6}")
            print("-" * 38)
            for r in ranking:
                print(f"{r['rank']:<4}{r['name']:<10}{r['class']:<10}{r['total']:<8}{r['avg']:<6}")
        
        elif choice == "3":
            stats = get_subject_stats()
            if not stats:
                print("No grade data!")
                continue
            print("\nSubject Statistics")
            print(f"{'Subject':<10}{'Average':<10}{'Max':<8}{'Min':<8}{'Count':<4}")
            print("-" * 38)
            for subject, info in stats.items():
                print(f"{subject:<10}{info['avg']:<10}{info['max']:<8}{info['min']:<8}{info['count']:<4}")
        
        elif choice == "4":
            break
        
        else:
            print("Invalid choice!")

def view_data():
    """اعرض البيانات الأولية"""
    data = load_data()
    students = data["students"]
    if not students:
        print("No student data yet")
        return
    
    print(f"\nTotal: {len(students)} students")
    for sid, info in sorted(students.items()):
        scores = info["scores"]
        score_str = ", ".join(f"{s}:{v}" for s, v in scores.items()) if scores else "No grades"
        print(f"  {sid} {info['name']} ({info['class_name']}) -> {score_str}")

# ====== البرنامج الرئيسي ======

def main():
    print("Welcome to the Student Grade Management System!")
    
    while True:
        show_main_menu()
        choice = input("Select option (1-5): ").strip()
        
        if choice == "1":
            student_management()
        elif choice == "2":
            score_management()
        elif choice == "3":
            stats_query()
        elif choice == "4":
            view_data()
        elif choice == "5":
            print("Thank you for using the system! Goodbye!")
            break
        else:
            print("Invalid choice. Please enter 1-5.")


if __name__ == "__main__":
    main()
▶ جرّب الكود

ملخص المشروع

الطبقة الدوال المسؤولية
طبقة البيانات load/save_data إدخال/إخراج ملف JSON
طبقة الكيان add/delete_student CRUD الطلاب
طبقة الكيان add_score CRUD الدرجات
طبقة المنطق get_student_report بطاقة تقرير فردية
طبقة المنطق get_class_ranking حساب الترتيب
طبقة المنطق get_subject_stats إحصائيات المواد
طبقة العرض main/main_menu حلقة القائمة
طبقة العرض student/score/stats_management قوائم فرعية

❓ أسئلة شائعة

س ألن تسبب حلقة while True في قائمة الأوامر حلقة لا نهائية؟
ج لا، لأن كل قائمة لديها خيار (مثل إدخال 0 أو رقم محدد) ينفذ break للخروج من الحلقة. طالما أن كل قائمة لديها خيار "خروج"، فهي آمنة. س:هل الكلمة المفتاحية in كافية للبحث الضبابي، أم أحتاج تعبيرات عادية؟ ج:لهذا المشروع، keyword in name كافية. إذا كنت تحتاج بحثاً أكثر تعقيداً (مطابقة فقط في البداية، غير حساس لحالة الأحرف، مجموعات كلمات مفتاحية متعددة)، ستحتاج تعبيرات عادية — مغطاة في الدرس 31. س:كيف أُحزم المشروع لاستخدام الآخرين؟ ج:أبسط طريقة هي إرسال ملف .py مباشرة — أي شخص لديه بايثون مثبت يمكنه تشغيله. لإنشاء ملف قابل للتنفيذ (.exe)، استخدم PyInstaller: pip install pyinstaller && pyinstaller --onefile main.py.

📖 ملخص

  • عمارة ثلاثية الطبقات: طبقة البيانات ← طبقة المنطق ← طبقة العرض، كل منها بمسؤوليات واضحة
  • طبقة البيانات: تغليف قراءة/كتابة ملف JSON، عمليات CRUD الأساسية
  • طبقة المنطق: إحصائيات الدرجات، الترتيب، قواعد تحليل الأعمال
  • طبقة العرض: واجهة القائمة، الإدخال/الإخراج، تفاعل المستخدم
  • معالجة الاستثناءات: التحقق من صحة الإدخال، معالجة البيانات الفارغة، معالجة عدم وجود ملف
  • المشروع يبلغ حوالي 200 سطر من الكود الأساسي، ينفّذ نظاماً كاملاً لإدارة درجات الطلاب

📝 تمارين

  1. مبتدئ (الصعوبة ⭐): شغّل النظام الكامل، أضف 3 طلاب ودرجاتهم، وأنشئ تقرير ترتيب.

  2. متوسط (الصعوبة ⭐⭐): أضف ميزة "تصدير تقرير" إلى النظام تُصدّر نتائج الترتيب إلى ملف ranking.csv (باستخدام وحدة csv).

  3. متقدم (الصعوبة ⭐⭐⭐): بدون الرجوع إلى الكود أعلاه، ابنِ "نظام إدارة مبيعات الكتب" من الصفر. تشمل الميزات: إضافة كتاب (العنوان، المؤلف، سعر الوحدة، المخزون)، تسجيل بيع (عنوان الكتاب، الكمية ← يُنقص المخزون تلقائياً)، عرض تقرير المخزون (مرتب حسب حجم المبيعات). استخدم JSON لاستمرارية البيانات.

100%