Tkinter Quiz System with SQLite and SQLAlchemy

Tkinter Quiz System with SQLite

Introduction

We will learn how to build a Student Quiz System using Python Tkinter, SQLite, and SQLAlchemy.


Build a Tkinter Quiz App with SQLite Database | Python GUI Tutorial #tkinter #sqlite

Student Quiz System Using Tkinter and SQLAlchemy 🔝

This project is a Student Quiz System built using Tkinter and SQLAlchemy. The system allows students to take quizzes, records their scores, and displays results in a structured format.

1. Importing Required Modules


import os
import random
import tkinter as tk
from tkinter import ttk, messagebox
from sqlalchemy import create_engine, text
import ttkbootstrap as tb
    

2. Database Connection Setup


current_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(current_dir, "student_quiz.db")  
DB_FILE = f"sqlite:///{db_path}"

engine = create_engine(DB_FILE, echo=False)  
conn = engine.connect()
    
  • Establishes the database connection using SQLAlchemy.
  • create_engine() initializes the database engine.
  • connect() opens a connection to the database.

3. Fetching Questions from the Database


def get_questions():
    result = conn.execute(text("SELECT id, question, option1, option2, option3, option4, correct_option FROM questions"))
    return [dict(row) for row in result.mappings()]  

questions = get_questions()  
    
  • Fetches quiz questions from the database.
  • Uses SQLAlchemy’s execute() to run an SQL query.
  • Converts results into a list of dictionaries for easy access.

4. Setting Up the Tkinter Window


root = tb.Window(themename="superhero")
root.title("plus2net.com Student Quiz System")
root.geometry("650x500")
    
  • Creates the main application window.
  • Uses ttkbootstrap for modern UI themes.

5. Creating Tabs for Quiz and Marks


notebook = ttk.Notebook(root)
notebook.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")

quiz_tab = ttk.Frame(notebook)
marks_tab = ttk.Frame(notebook)
notebook.add(quiz_tab, text="πŸ“– Quiz")
notebook.add(marks_tab, text="πŸ“Š Student Marks")
    
  • Uses a tabbed interface to separate quiz and student marks.
  • Each tab is a separate frame inside the notebook.

6. Quiz Functionality - Start Quiz


def start_quiz():
    if not student_id.get().isdigit():
        messagebox.showerror("Error", "Enter a valid Student ID!")
        return
    start_btn.grid_forget()
    question_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10, sticky='w')
    show_question()
    
  • Validates the student ID before starting the quiz.
  • Hides the "Start Quiz" button and displays the questions.

7. Handling Quiz Progression


def next_question():
    global current_question_index, score
    if selected_option.get() == 0:
        messagebox.showwarning("Warning", "Please select an answer!")
        return
    if selected_option.get() == questions[current_question_index]["correct_option"]:
        score += 1
    current_question_index += 1
    show_question()
    
  • Moves to the next question while checking the selected answer.
  • Updates the score if the selected answer is correct.

8. Displaying Student Marks


def load_student_marks():
    tree.delete(*tree.get_children())  
    data = conn.execute(text("SELECT id, name, class_name, COALESCE(mark, 'Not Attempted') AS mark FROM student"))  
    for row in data:
        tree.insert("", "end", values=row)
    
  • Retrieves student marks from the database.
  • Displays the data in a table using Treeview.


import os
import random
import tkinter as tk
from tkinter import ttk, messagebox
from sqlalchemy import create_engine, text
import ttkbootstrap as tb

# βœ… Database Connection Using SQLAlchemy
current_dir = os.path.dirname(os.path.abspath(__file__))  
db_path = os.path.join(current_dir, "student_quiz.db")  
DB_FILE = f"sqlite:///{db_path}"

engine = create_engine(DB_FILE, echo=False)  
conn = engine.connect()

# βœ… Fetch All Questions from Database
def get_questions():
    result = conn.execute(text("SELECT id, question, option1, option2, option3, option4, correct_option FROM questions"))
    return [dict(row) for row in result.mappings()]  

questions = get_questions()  

# βœ… Tkinter Window Setup
root = tb.Window(themename="superhero")
root.title("plus2net.com  Student Quiz System")
root.geometry("650x500")

# βœ… Create Notebook (Tab View)
notebook = ttk.Notebook(root)
notebook.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")

# βœ… Create Tabs
quiz_tab = ttk.Frame(notebook)
marks_tab = ttk.Frame(notebook)
notebook.add(quiz_tab, text="πŸ“– Quiz")
notebook.add(marks_tab, text="πŸ“Š Student Marks")

# βœ… Variables
student_id = tk.StringVar()
current_question_index = 0
score = 0
selected_option = tk.IntVar()

# βœ… GUI: Student ID Input (Inside Quiz Tab)
tk.Label(quiz_tab, text="Enter Student ID:", font=("Arial", 12)).grid(row=0, column=0, padx=10, pady=5, sticky="w")
tk.Entry(quiz_tab, textvariable=student_id, font=("Arial", 12)).grid(row=0, column=1, padx=10, pady=5)
start_btn = tb.Button(quiz_tab, text="Start Quiz", bootstyle="primary", command=lambda: start_quiz())
start_btn.grid(row=0, column=2, padx=10, pady=5)

# βœ… Question Frame (Initially Hidden)
question_frame = ttk.Frame(quiz_tab)
question_label = tk.Label(question_frame, text="", font=("Arial", 14), wraplength=500)
question_label.grid(row=1, column=0, columnspan=2, pady=10,sticky="nsew")
options = []
for i in range(4):
    r = tk.Radiobutton(question_frame, text="", variable=selected_option, value=i+1, font=("Arial", 12))
    r.grid(row=i+2, column=0, columnspan=2, sticky="w", padx=20)
    options.append(r)

# βœ… Buttons
next_btn = tb.Button(question_frame, text="Next", bootstyle="success", command=lambda: next_question())
submit_btn = tb.Button(question_frame, text="Submit", bootstyle="danger", command=lambda: finish_quiz())

# βœ… Display Question
# βœ… Display Question with Left Alignment
def show_question():
    global current_question_index
    if current_question_index < len(questions):
        q = questions[current_question_index]
        question_label.config(text=f"Q{current_question_index+1}: {q['question']}", anchor="w", justify="left")
        for i in range(4):
            options[i].config(text=q[f"option{i+1}"])
        selected_option.set(0)  
    else:
        finish_quiz()


# βœ… Start Quiz
def start_quiz():
    if not student_id.get().isdigit():
        messagebox.showerror("Error", "Enter a valid Student ID!")
        return
    start_btn.grid_forget()
    question_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10,sticky='w')
    next_btn.grid(row=6, column=0, padx=5, pady=10)
    show_question()

# βœ… Handle Next Question
def next_question():
    global current_question_index, score
    if selected_option.get() == 0:
        messagebox.showwarning("Warning", "Please select an answer!")
        return
    if selected_option.get() == questions[current_question_index]["correct_option"]:
        score += 1
    current_question_index += 1
    show_question()

# βœ… Finish Quiz & Update Score in Database
def finish_quiz():
    global score
    messagebox.showinfo("Quiz Finished", f"Your Score: {score} / {len(questions)}")
    
    # βœ… Update student marks using SQLAlchemy
    conn.execute(text("UPDATE student SET mark = :mark WHERE id = :id"), {"mark": score, "id": int(student_id.get())})
    conn.commit()
    
    question_frame.grid_forget()
    load_student_marks()

# βœ… Show Student Marks in Treeview (Inside Marks Tab)
columns = ("ID", "Name", "Class", "Mark")
tree = ttk.Treeview(marks_tab, columns=columns, show="headings", height=8)  # Reduced height
for col in columns:
    tree.heading(col, text=col)
    tree.column(col, width=100, anchor="center")  # Reduced column width

tree.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")

# βœ… Load Student Marks into Table
def load_student_marks():
    tree.delete(*tree.get_children())  
    data = conn.execute(text("SELECT id, name, class_name, COALESCE(mark, 'Not Attempted') AS mark FROM student"))  
    for row in data:
        id, name, class_name, mark = row  # Ensure proper column mapping
        tree.insert("", "end", values=(id, name, class_name, mark))



# βœ… Load Student Marks Initially
load_student_marks()

# βœ… Run Tkinter Loop
root.mainloop()

1. Database Setup 🔝

Tkinter Quiz System with SQLite

Before using the app we need to set up the database containing students details and quiz questions.

1.1 Creating `setup_database.py`

# Import required modules
import os
from sqlalchemy import create_engine, text

# Define database path
current_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(current_dir, "student_quiz.db")
DB_FILE = f"sqlite:///{db_path}"

# Create connection
engine = create_engine(DB_FILE, echo=False)
conn = engine.connect()

# Create Tables
conn.execute(text("DROP TABLE IF EXISTS student"))
conn.execute(text("DROP TABLE IF EXISTS questions"))
  • SQLite database: Created in the current directory.
  • SQLAlchemy: Used for database connection and execution.
  • Drop Tables: Resets data ( delete tables ) before creating tables.

1.2 Inserting Sample Data

# Insert Sample Questions
questions = [
    {"question": "What is 2+2?", "option1": "3", "option2": "4", "correct_option": 2}
]
conn.execute(text("INSERT INTO questions (question, option1, option2, correct_option) VALUES (:question, :option1, :option2, :correct_option)"), questions)

# Commit and Close
conn.commit()
conn.close()
  • Stores questions and answers in SQLite.
  • Ensures correct answer validation.
import os
from sqlalchemy import create_engine, text

# βœ… Get the absolute path of the script's directory
current_dir = os.path.dirname(os.path.abspath(__file__))  
db_path = os.path.join(current_dir, "student_quiz.db")  # Ensure database is created in the same directory
DB_FILE = f"sqlite:///{db_path}"

# βœ… Create database connection using SQLAlchemy (Disabled echo to reduce logs)
engine = create_engine(DB_FILE, echo=False)  
conn = engine.connect()

# βœ… Drop Tables (To Reset Database)
conn.execute(text("DROP TABLE IF EXISTS student"))
conn.execute(text("DROP TABLE IF EXISTS questions"))

# βœ… Create 'student' Table
conn.execute(text("""
    CREATE TABLE student (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        class_name TEXT NOT NULL,  -- Changed from 'class' to 'class_name' to avoid SQL keyword conflict
        mark INTEGER NULL
    )
"""))

# βœ… Create 'questions' Table
conn.execute(text("""
    CREATE TABLE questions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        question TEXT NOT NULL,
        option1 TEXT NOT NULL,
        option2 TEXT NOT NULL,
        option3 TEXT NOT NULL,
        option4 TEXT NOT NULL,
        correct_option INTEGER NOT NULL CHECK (correct_option BETWEEN 1 AND 4)
    )
"""))

# βœ… Insert Sample Students using named parameters
students = [
    {"id": 1, "name": "Alice", "class_name": "Grade 10", "mark": None},
    {"id": 2, "name": "Bob", "class_name": "Grade 11", "mark": None},
    {"id": 3, "name": "Charlie", "class_name": "Grade 12", "mark": None}
]
conn.execute(text("INSERT INTO student (id, name, class_name, mark) VALUES (:id, :name, :class_name, :mark)"), students)

# βœ… Insert Sample Questions using named parameters
questions = [
    {"question": "What is 2 + 2?", "option1": "3", "option2": "4", "option3": "5", "option4": "6", "correct_option": 2},
    {"question": "What is the capital of France?", "option1": "Berlin", "option2": "Madrid", "option3": "Paris", "option4": "Rome", "correct_option": 3},
    {"question": "Which is the largest planet?", "option1": "Mars", "option2": "Jupiter", "option3": "Earth", "option4": "Saturn", "correct_option": 2},
    {"question": "Who wrote 'Hamlet'?", "option1": "Shakespeare", "option2": "Hemingway", "option3": "Dante", "option4": "Tolstoy", "correct_option": 1},
    {"question": "What is H2O?", "option1": "Oxygen", "option2": "Hydrogen", "option3": "Water", "option4": "Salt", "correct_option": 3}
]
conn.execute(text("""
    INSERT INTO questions (question, option1, option2, option3, option4, correct_option) 
    VALUES (:question, :option1, :option2, :option3, :option4, :correct_option)
"""), questions)

# βœ… Commit and Close Connection
conn.commit()
result = conn.execute(text("SELECT * FROM student"))
for row in result:
    print(row)
conn.close()

print(f"βœ… Database setup completed successfully at: {db_path}")
Next: Quiz using Json data


Subscribe to our YouTube Channel here


Subscribe

* indicates required
Subscribe to plus2net

    plus2net.com



    16-02-2025

    All Works first time, great Program, clear instructions on how to use, Explained very well in the Vidio




    Python Video Tutorials
    Python SQLite Video Tutorials
    Python MySQL Video Tutorials
    Python Tkinter Video Tutorials
    We use cookies to improve your browsing experience. . Learn more
    HTML MySQL PHP JavaScript ASP Photoshop Articles FORUM . Contact us
    ©2000-2024 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer