Final Project: Student Grade Management (Part 1)
This is the capstone project of the Python tutorial. Over three lessons, we will build a complete student grade management system from scratch. The project has three layers: data layer (JSON persistence), logic layer (grade processing), and presentation layer (command-line interface). This lesson builds the foundation — requirements and the data layer.
Project Overview
Student Grade Management System
├── Lesson 34: Data Layer — Requirements analysis + JSON/CSV persistence
├── Lesson 35: Logic Layer — Grade statistics + Sorting + Exception handling
└── Lesson 36: Presentation Layer — Menu interface + Search + Reports
Requirements Specification
1. Student Management
├── Add student (name, student ID, class)
└── Delete student (by student ID)
2. Grade Management
├── Enter grades (by student ID for multiple subjects)
├── Modify grades
└── Delete grade records
3. Statistics and Analysis
├── Individual report card
├── Class ranking
├── Subject averages
└── Score distribution
4. Data Persistence
├── Store data in JSON format to a file
└── Auto-load on startup
Data Structure Design
Example: JSON Data Structure
# Data stored in JSON format
{
"students": {
"2024001": {
"name": "Zhang San",
"class_name": "Class 1",
"scores": {
"Chinese": 85,
"Math": 92,
"English": 78
}
},
"2024002": {
"name": "Li Si",
"class_name": "Class 1",
"scores": {
"Chinese": 90,
"Math": 88,
"English": 95
}
}
},
"subjects": ["Chinese", "Math", "English"],
"classes": ["Class 1", "Class 2"]
}
Tip: Why use student ID as the key? Using student ID (string) as the dictionary key makes lookups O(1) — whether there are 10 or 10,000 students, the lookup speed is the same.
Data Layer Implementation
Example: Data Layer Implementation and Testing
import json
import os
DATA_FILE = "grade_system.json"
# ====== Default Data Structure ======
DEFAULT_DATA = {
"students": {},
"subjects": ["Chinese", "Math", "English"],
"classes": []
}
# ====== Data Layer ======
def load_data():
"""Load data from JSON file"""
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):
"""Save data to JSON file"""
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# ====== Student Management ======
def add_student(student_id, name, class_name):
"""Add a student"""
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": {}
}
# Update class list
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):
"""Delete a student"""
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 get_student(student_id):
"""Get a single student's info"""
data = load_data()
return data["students"].get(student_id)
def get_all_students():
"""Get all students"""
data = load_data()
return data["students"]
# ====== Grade Management ======
def add_score(student_id, subject, score):
"""Enter a grade"""
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: {score}"
def update_score(student_id, subject, score):
"""Update a grade (same function as add_score)"""
return add_score(student_id, subject, score)
def delete_score(student_id, subject):
"""Delete a grade for a subject"""
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["students"][student_id]["scores"]:
return False, f"{data['students'][student_id]['name']} has no {subject} score"
del data["students"][student_id]["scores"][subject]
save_data(data)
return True, f"Deleted {data['students'][student_id]['name']}'s {subject} score"
# ====== Test Code ======
if __name__ == "__main__":
print("=== Testing Data Layer ===")
print(add_student("2024001", "Zhang San", "Class 1"))
print(add_student("2024002", "Li Si", "Class 1"))
print(add_student("2024003", "Wang Wu", "Class 2"))
print(add_score("2024001", "Chinese", 85))
print(add_score("2024001", "Math", 92))
print(add_score("2024002", "Chinese", 90))
student = get_student("2024001")
print(f"\nZhang San's info: {student}")
print(f"\nTotal students: {len(get_all_students())}")
Design Highlights
- Uniform function return: Each function returns a
(bool, str)tuple —Truemeans success,Falsemeans failure, and the string is the message. - Encapsulated repeated operations: The
load_data()andsave_data()functions are reused across all data operations. - Input validation: Score range checking and student ID existence checks are done in the data layer, so upper-layer callers don't need to repeat them.
- Ready for expansion: Lessons 35 and 36 will add functionality on top of this data layer.
FAQ
os.path.join() to construct paths and avoid hardcoding.(success, message) tuple instead of raising exceptions?success, which is lighter than try/except. It's suitable for small CLI tools; for larger projects, custom exception classes are recommended.Summary
- The project uses a three-layer architecture: Data Layer -> Logic Layer -> Presentation Layer; this lesson completes the data layer
- Data is persisted in JSON format; student IDs serve as dictionary keys for O(1) lookup
- Scores range from 0 to 100, validated on entry
- All operation functions return
(success, message)tuples for easy caller handling
Exercises
-
Beginner (Difficulty: Star): Run the data layer code above, add 2-3 students with their grades, and verify the JSON file is generated correctly.
-
Intermediate (Difficulty: Star-Star): Add an
update_student(student_id, name=None, class_name=None)function to the data layer that only updates the provided fields (leaving others unchanged). -
Advanced (Difficulty: Star-Star-Star): Add batch entry functionality to
add_score— accept a dictionary like{"Chinese": 85, "Math": 92}to enter multiple grades at once. Requirement: if any score is invalid, roll back the entire batch operation (don't save any data).



