LambdaLabTM
Computer Science · Class 12 Project · Subject Code 083
Sample ProjectPython + MySQL⏱️ 12 min read

School Library Issue Register

Issue and return books, hold copies to account, and print the overdue list.

1Introduction: the problem it solves

A school library with two thousand books runs on a card tray and a register. Nobody can answer 'do we still have a copy of this?' without walking to the shelf, and the overdue list is compiled by reading every page of the register.

Both questions are one query each if the register lives in a database instead of a book.

who would use it

A school or college library, a community reading room, or a departmental book bank.

Why it is worth computerising

A card tray works because a library is small. As the collection grows, two questions become slow at exactly the same rate: whether a copy of a title is free, and who is holding a book that is overdue. Neither can be answered without walking to the shelf or reading the register page by page, and both are asked every day.

Storing the same information in three linked tables changes the cost of asking. The copies count is kept honest by the program itself, because the same action that records an issue reduces it. The overdue list, which took an afternoon, becomes one query — and can therefore be run every morning instead of once a term.

Objectives

  1. To hold the books, the members and the loans in three related tables
  2. To refuse an issue when no copy is free, so the shelf and the register agree
  3. To calculate the due date from the loan period instead of writing it by hand
  4. To work out the fine automatically from the days a book is overdue
  5. To produce the overdue list as a single query rather than by reading the register

2How the job is done today

Before writing anything it is worth asking how the work is handled at present, and why each of those answers falls short. These were examined:

Card tray and issue register

The traditional system, and a good one until the collection grows. Answering 'is a copy free?' means walking to the shelf, and the overdue list means reading every page.

Library management software (Koha, SOUL)

Full library systems used by colleges. They are far larger than a school library needs and take real effort to install and maintain.

Spreadsheet of issues

An improvement on paper. It cannot easily keep the copies count honest, because nothing links an issue back to the book record.

3Functionalities

  • Three tables: the books, the members and the loans between them
  • Issuing checks that a copy is actually free before writing the loan
  • The copies count goes down on issue and back up on return, so the shelf and the database agree
  • Due date is worked out from the loan period, not typed in
  • Returns calculate the fine from the days overdue
  • The overdue list is a single JOIN across all three tables

The functions that provide them

FunctionArgumentsWhat it does
issue_book()book_id, member_idGive a book out, if a copy is free.
return_book()loan_idTake a book back, and charge a fine if it is late.
overdue_list()Every book still out past its due date.

4Technical details

LanguagePython 3
StorageMySQL, through mysql-connector-python
Modules used
  • mysql.connector — the Python-MySQL connection
  • datetime — date arithmetic for due dates and fines
  • MySQL — three related tables with foreign keys

5How the data is stored

Three tables, and the relationships are the point. `loans` holds only the ids of a book and a member; the titles and names live once, in their own tables. That is why a book renamed in `books` is renamed everywhere at once.

Table: books

FieldTypeDescription
book_idINT (primary key, auto)Unique number for each title, generated by MySQL
titleVARCHAR(60) (required)Name of the book
authorVARCHAR(40)Author's name
copiesINTHow many copies are on the shelf right now

Table: members

FieldTypeDescription
member_idVARCHAR(6) (primary key)Library card number, e.g. M014
nameVARCHAR(40) (required)Member's name
classVARCHAR(6)Class the member studies in

Table: loans

FieldTypeDescription
loan_idINT (primary key, auto)Unique number for each issue
book_idINTWhich book was issued — refers to books
member_idVARCHAR(6)Who took it — refers to members
issue_dateDATEDate the book left the library
due_dateDATEIssue date plus the loan period
return_dateDATEDate it came back; empty while still out
fineDECIMAL(6,2)Fine charged on return, if any

Sample rows in books

titleauthorcopies
Wings of FireA P J Abdul Kalam3
The Jungle BookR Kipling2

Sample rows in members

M014Ananya SenXII-A
M021Rohit DasXI-B

Sample rows in loans

book_idmember_idissue_datedue_date
2M0212026-08-012026-08-15

6How it works, step by step

1
Issue

issue_book() checks the copies count, refuses if none are free, then INSERTs the loan and UPDATEs the count.

2
Due date

date.today() + timedelta(days=14) — the library's loan period is a constant at the top of the file.

3
Return

return_book() finds the due date, works out days late, stores the fine and puts the copy back.

4
Overdue

One SELECT with two JOINs, filtered on return_date IS NULL and a due date in the past.

7Source code

library.py
# ---------------------------------------------------------------------------
# library.py
#
# Issue register for the LambdaLab Public School library, using Python with
# MySQL.
#
# Three tables carry the data: books, members, and the loans between them. A
# loan stores only the ids of a book and a member — the title and the name live
# once, in their own tables, so nothing can disagree with itself.
# ---------------------------------------------------------------------------

import mysql.connector                      # the Python-MySQL connection
from datetime import date, timedelta        # for due dates and fines

LOAN_DAYS = 14                 # how long a book may be kept
FINE_PER_DAY = 2.0             # charged for each day it is late

# The connection is opened once and used by every function below. Change the
# user and password to the ones on your own computer.
db = mysql.connector.connect(host="localhost", user="root",
                             passwd="school", database="lambdalab_library")

# A cursor is what actually carries an SQL statement to the server and brings
# the answer back.
cur = db.cursor()


def issue_book(book_id, member_id):
    """Give a book out, if a copy is free."""
    # %s is a placeholder. The value goes in the tuple that follows, and the
    # connector inserts it safely — never build SQL by joining strings.
    cur.execute("SELECT title, copies FROM books WHERE book_id = %s", (book_id,))
    row = cur.fetchone()                    # one row, or None if there is none

    if row is None:
        print("No such book.")
        return

    title, copies = row

    # Checked BEFORE anything is written, so the register can never show a copy
    # out that does not exist.
    if copies < 1:
        print("'{}' — all copies are out.".format(title))
        return

    due = date.today() + timedelta(days=LOAN_DAYS)     # date arithmetic

    cur.execute("INSERT INTO loans (book_id, member_id, issue_date, due_date)"
                " VALUES (%s, %s, %s, %s)", (book_id, member_id, date.today(), due))

    # The same action that records the loan reduces the count, so the shelf and
    # the database cannot drift apart.
    cur.execute("UPDATE books SET copies = copies - 1 WHERE book_id = %s", (book_id,))

    db.commit()                             # nothing is saved until this runs
    print("Issued '{}' to {}. Due on {}.".format(title, member_id, due))


def return_book(loan_id):
    """Take a book back, and charge a fine if it is late."""
    cur.execute("SELECT book_id, due_date FROM loans WHERE loan_id = %s", (loan_id,))
    book_id, due = cur.fetchone()

    # Subtracting two dates gives a timedelta; .days turns it into a number.
    late = (date.today() - due).days
    fine = late * FINE_PER_DAY if late > 0 else 0      # no fine if not late

    cur.execute("UPDATE loans SET return_date = %s, fine = %s WHERE loan_id = %s",
                (date.today(), fine, loan_id))
    cur.execute("UPDATE books SET copies = copies + 1 WHERE book_id = %s", (book_id,))

    db.commit()
    print("Returned. Fine: Rs {:.2f}".format(fine))


def overdue_list():
    """Every book still out past its due date."""
    # One query across all three tables. The JOINs bring the member's name and
    # the book's title alongside the loan, which holds only their ids.
    # return_date IS NULL means still out; NULL cannot be tested with = .
    cur.execute("SELECT l.loan_id, m.name, b.title, l.due_date"
                " FROM loans l JOIN members m ON l.member_id = m.member_id"
                " JOIN books b ON l.book_id = b.book_id"
                " WHERE l.return_date IS NULL AND l.due_date < %s"
                " ORDER BY l.due_date", (date.today(),))

    print("{:<8}{:<16}{:<26}{:<12}".format("LOAN", "MEMBER", "TITLE", "DUE"))

    for loan_id, name, title, due in cur.fetchall():   # every matching row
        print("{:<8}{:<16}{:<26}{:<12}".format(loan_id, name, title, str(due)))


# --- the program itself ----------------------------------------------------

issue_book(1, "M014")          # give Wings of Fire to member M014
overdue_list()                 # then show what is overdue today

db.close()                     # always close the connection when finished
schema.sql
CREATE DATABASE IF NOT EXISTS lambdalab_library;
USE lambdalab_library;

CREATE TABLE books (
    book_id   INT PRIMARY KEY AUTO_INCREMENT,
    title     VARCHAR(60) NOT NULL,
    author    VARCHAR(40),
    copies    INT DEFAULT 1
);

CREATE TABLE members (
    member_id VARCHAR(6) PRIMARY KEY,
    name      VARCHAR(40) NOT NULL,
    class     VARCHAR(6)
);

CREATE TABLE loans (
    loan_id     INT PRIMARY KEY AUTO_INCREMENT,
    book_id     INT,
    member_id   VARCHAR(6),
    issue_date  DATE,
    due_date    DATE,
    return_date DATE DEFAULT NULL,
    fine        DECIMAL(6,2) DEFAULT NULL,
    FOREIGN KEY (book_id)   REFERENCES books(book_id),
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);

INSERT INTO books (title, author, copies) VALUES
    ('Wings of Fire', 'A P J Abdul Kalam', 3),
    ('The Jungle Book', 'R Kipling', 2);

INSERT INTO members VALUES
    ('M014', 'Ananya Sen', 'XII-A'),
    ('M021', 'Rohit Das', 'XI-B');

INSERT INTO loans (book_id, member_id, issue_date, due_date) VALUES
    (2, 'M021', '2026-08-01', '2026-08-15');
⬇️ Take it with you

The full report as a PDF — cover page, certificate, index, code, output, future scope and references, ready to print and fill in. Or the code on its own as a zip.

or one file:

8Sample output

This is a real run, not a mock-up — the transcript below is what the program actually printed.

Command Prompt
C:\LambdaLab\Projects\Library> python library.py
Issued 'Wings of Fire' to M014. Due on 2026-09-09.
LOAN    MEMBER          TITLE                     DUE         
1       Rohit Das       The Jungle Book           2026-08-15
Note
Before this will run, import schema.sql into MySQL and change the user and password in the connect() call to your own. The output above is what the queries return against the sample rows in that schema.

9Testing

Every case below was actually run and the result recorded as it appeared — including the ones expected to fail.

Test caseExpectedActual
Overdue list on a date after a due dateOne loan listed1 overdue loan(s)
Overdue list on a date before any due dateNone listed0 overdue loan(s)
Issuing when the copies count is zeroRefused; count stays 0copies = 0
Joining loans to members and booksMember name and title returnedRohit Das / The Jungle Book
A member id that does not existNo rows, no error0 row(s)

Two kinds of case are in there on purpose. The boundary cases test the edge of a rule, where a program is most often wrong by one. The failure cases check that it refuses cleanly and says why, instead of quietly producing a wrong answer.

10Advantages

Set against the ways the job is done today:

  • The copies count cannot drift from the shelf, because one action changes both
  • The overdue list takes a second, so it can be produced daily
  • A title's details are stored once and cannot disagree with themselves
  • Fines are calculated from dates rather than estimated
  • The register cannot be lost, torn or written in twice

11Future scope

What this version does not do

A report should be honest about its own limits, and each of these is the reason for one of the additions below:

  • One copy is not distinguished from another, so a damaged copy cannot be traced
  • There is no reservation facility for a title that is out
  • Fines are calculated but not recorded as paid or waived
  • Membership has no expiry, so a student who has left can still borrow

Proposed enhancements

This is also where you make the project yours. Pick one or two of these, or something nobody here thought of:

  • A membership card with a barcode, scanned at the counter
  • Email a reminder two days before a book is due
  • Reserve a title that is currently out
  • A most-borrowed report, so the librarian knows what to buy more of
  • A fine waiver for a genuine reason, recorded with who approved it

12What you may have to teach yourself

CBSE expects some self-learning in a project, and says so. For this one, that means:

  • Foreign keys, and why the loans table stores ids rather than names
  • How to install mysql-connector-python and connect from Python
  • JOIN across three tables, which is the query the whole project turns on

13Conclusion

Three tables replaced a card tray and a register, and the two questions the library could never answer quickly — is a copy free, and who is overdue — became a lookup and a single query.

The part worth keeping from this project is the discipline of storing each fact once. The loans table holds ids and nothing else, and every problem of two records disagreeing disappears with that decision.

14References

Every report needs a bibliography. This one used:

  • Computer Science with Python, Class XII — the NCERT / CBSE prescribed textbook, for the chapters on database concepts, SQL and interfacing Python with a database
  • Computer Science with Python, Class XI — for functions, lists, dictionaries and string handling
  • Python 3 documentation — https://docs.python.org/3/
  • MySQL 8.0 Reference Manual and the Connector/Python Developer Guide — https://dev.mysql.com/doc/
  • CBSE Senior School Curriculum, Computer Science (Subject Code 083) — the project guidelines this report follows
  • LambdaLab — https://www.lambdalab.in
Key Takeaway
The PDF above is the whole report. Cover page, certificate, acknowledgement, index, everything on this page, and the references — in the order CBSE marks them, ready to print. The cover page, certificate and acknowledgement arrive with blank rules where names go, because a certificate with somebody else's name printed on it is not a template. Fill those in, get the certificate signed, and replace the problem with one you have looked into yourself.