بناء نظام إدارة مكتبة باستخدام بايثون وSQLite | مشروع عملي

بناء نظام إدارة مكتبة باستخدام بايثون وSQLite

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

سنقسم المشروع إلى ثلاثة ملفات فقط حتى يبقى منظمًا وسهل الفهم: ملف للاتصال بقاعدة البيانات، وملف لعمليات الكتب، وملف لواجهة الطرفية والتعامل مع المستخدم.

{alertInfo} هذه نسخة تعليمية لإدارة الكتب فقط. لا تحتوي بعد على الأعضاء أو الاستعارة والإرجاع أو واجهة رسومية؛ هذه إضافات تصلح لمراحل لاحقة من المشروع.

تحميل ملفات المشروع

يمكنك تحميل ملفات المشروع الجاهزة، ثم فتح الأكواد وتجربتها وتعديلها على جهازك.

تحميل ملفات المشروع

{getToc} $title={محتوى المقال}

ماذا سنبني في هذا المشروع؟

  • إضافة كتاب جديد.
  • عرض جميع الكتب.
  • البحث بالعنوان أو المؤلف أو التصنيف.
  • عرض كتاب باستخدام رقمه.
  • تعديل بيانات كتاب.
  • حذف كتاب بعد التأكيد.
  • حفظ البيانات داخل قاعدة SQLite دائمة.
  • التحقق من بعض المدخلات الأساسية.
  • تقسيم المشروع إلى ملفات منفصلة بدل وضع كل شيء في ملف واحد.

لماذا نستخدم SQLite؟

SQLite مناسبة جدًا للمشاريع التعليمية والصغيرة لأنها تحفظ البيانات داخل ملف واحد، ولا تحتاج إلى تشغيل خادم قاعدة بيانات منفصل. في بايثون نستطيع التعامل معها من خلال وحدة sqlite3 المدمجة.

سيُنشأ ملف library.db تلقائيًا عند تشغيل المشروع للمرة الأولى.

هيكل المشروع

library_system/
├── main.py
├── database.py
├── books.py
└── library.db   # يُنشأ تلقائيًا
الملفالمسؤولية
database.pyالاتصال بقاعدة البيانات وإنشاء جدول الكتب.
books.pyتنفيذ عمليات الإضافة والقراءة والبحث والتعديل والحذف.
main.pyعرض القائمة والتعامل مع المستخدم وربط أجزاء البرنامج.
library.dbتخزين البيانات بصورة دائمة.
هيكل مشروع نظام إدارة المكتبة في بايثون

الخطوة الأولى: إنشاء قاعدة البيانات في database.py

أنشئ ملفًا باسم database.py وضع فيه:

import sqlite3
from pathlib import Path


BASE_DIR = Path(__file__).parent
DATABASE_FILE = BASE_DIR / "library.db"


def get_connection():
    connection = sqlite3.connect(DATABASE_FILE)
    connection.row_factory = sqlite3.Row
    return connection


def initialize_database():
    connection = get_connection()

    try:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS books (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                author TEXT NOT NULL,
                category TEXT NOT NULL,
                year INTEGER,
                available INTEGER NOT NULL DEFAULT 1
            )
            """
        )
        connection.commit()
    finally:
        connection.close()


if __name__ == "__main__":
    initialize_database()
    print("تم إنشاء قاعدة البيانات بنجاح.")

تحديد مسار قاعدة البيانات

نستخدم Path(__file__).parent للحصول على مجلد المشروع، ثم نحدد ملف قاعدة البيانات:

BASE_DIR = Path(__file__).parent
DATABASE_FILE = BASE_DIR / "library.db"

بهذه الطريقة يبقى ملف قاعدة البيانات داخل مجلد المشروع بغض النظر عن نظام التشغيل.

إنشاء الاتصال

def get_connection():
    connection = sqlite3.connect(DATABASE_FILE)
    connection.row_factory = sqlite3.Row
    return connection

السطر sqlite3.Row يسمح لنا لاحقًا بقراءة الأعمدة باستخدام أسمائها مثل book["title"] بدل الاعتماد فقط على أرقام الفهارس.

إنشاء جدول books

داخل initialize_database() نستخدم:

CREATE TABLE IF NOT EXISTS books

وهذا يعني: أنشئ الجدول إذا لم يكن موجودًا، ولا تعِد إنشاءه في كل تشغيل.

العمودوظيفته
idرقم فريد للكتاب ويزداد تلقائيًا.
titleعنوان الكتاب.
authorاسم المؤلف.
categoryالتصنيف.
yearسنة النشر.
availableحالة التوفر، وقيمتها الافتراضية 1.

الخطوة الثانية: كتابة عمليات الكتب في books.py

أنشئ ملف books.py. هذا الملف لا يتعامل مباشرة مع قوائم المستخدم؛ وظيفته تنفيذ عمليات الكتب في قاعدة البيانات.

from database import get_connection


def add_book(title, author, category, year):
    connection = get_connection()

    try:
        connection.execute(
            """
            INSERT INTO books (title, author, category, year)
            VALUES (?, ?, ?, ?)
            """,
            (title, author, category, year),
        )
        connection.commit()
    finally:
        connection.close()


def get_all_books():
    connection = get_connection()

    try:
        books = connection.execute(
            """
            SELECT id, title, author, category, year, available
            FROM books
            ORDER BY id DESC
            """
        ).fetchall()
        return books
    finally:
        connection.close()


def get_book_by_id(book_id):
    connection = get_connection()

    try:
        book = connection.execute(
            """
            SELECT id, title, author, category, year, available
            FROM books
            WHERE id = ?
            """,
            (book_id,),
        ).fetchone()
        return book
    finally:
        connection.close()


def search_books(keyword):
    connection = get_connection()

    try:
        search_value = f"%{keyword}%"

        books = connection.execute(
            """
            SELECT id, title, author, category, year, available
            FROM books
            WHERE title LIKE ?
               OR author LIKE ?
               OR category LIKE ?
            ORDER BY id DESC
            """,
            (search_value, search_value, search_value),
        ).fetchall()

        return books
    finally:
        connection.close()


def update_book(book_id, title, author, category, year):
    connection = get_connection()

    try:
        cursor = connection.execute(
            """
            UPDATE books
            SET title = ?,
                author = ?,
                category = ?,
                year = ?
            WHERE id = ?
            """,
            (title, author, category, year, book_id),
        )
        connection.commit()
        return cursor.rowcount > 0
    finally:
        connection.close()


def delete_book(book_id):
    connection = get_connection()

    try:
        cursor = connection.execute(
            """
            DELETE FROM books
            WHERE id = ?
            """,
            (book_id,),
        )
        connection.commit()
        return cursor.rowcount > 0
    finally:
        connection.close()

إضافة كتاب باستخدام INSERT

الدالة add_book() تنفذ:

INSERT INTO books (title, author, category, year)
VALUES (?, ?, ?, ?)

علامات ? أماكن للقيم. ثم تُمرر القيم بصورة منفصلة:

(title, author, category, year)

بعد تنفيذ عملية التغيير نستدعي commit() حتى يتم حفظها داخل قاعدة البيانات.

عرض جميع الكتب باستخدام SELECT

SELECT id, title, author, category, year, available
FROM books
ORDER BY id DESC

ثم نستخدم fetchall() للحصول على جميع النتائج.

الحصول على كتاب واحد

الدالة get_book_by_id() تستخدم fetchone() لأنها تريد صفًا واحدًا فقط:

SELECT id, title, author, category, year, available
FROM books
WHERE id = ?

البحث باستخدام LIKE

إذا بحث المستخدم عن كلمة مثل بايثون فإننا نحولها إلى:

%بايثون%

ثم نبحث عنها في العنوان أو المؤلف أو التصنيف باستخدام LIKE.

عمليات CRUD في مشروع مكتبة بايثون وSQLite

تعديل كتاب باستخدام UPDATE

نحدد الكتاب بواسطة id ثم نغيّر بياناته:

UPDATE books
SET title = ?,
    author = ?,
    category = ?,
    year = ?
WHERE id = ?

بعد التنفيذ نستخدم cursor.rowcount > 0 لمعرفة هل تم تعديل صف فعليًا.

حذف كتاب باستخدام DELETE

DELETE FROM books
WHERE id = ?

ومرة أخرى نعيد قيمة منطقية تخبرنا هل تم حذف سجل أم لا.

لماذا نستخدم try وfinally؟

كل دالة تفتح اتصالًا بقاعدة البيانات ثم تغلقه داخل finally:

connection = get_connection()

try:
    # تنفيذ استعلامات قاعدة البيانات
    ...
finally:
    connection.close()

الفكرة هي التأكد من إغلاق الاتصال بعد انتهاء العملية حتى إذا حدث خطأ أثناء التنفيذ.

الخطوة الثالثة: واجهة البرنامج في main.py

الملف main.py يجمع كل أجزاء البرنامج ويعرض القائمة للمستخدم.

from database import initialize_database
from books import (
    add_book,
    get_all_books,
    get_book_by_id,
    search_books,
    update_book,
    delete_book,
)


def show_menu():
    print("\n" + "=" * 50)
    print("نظام إدارة المكتبة")
    print("=" * 50)
    print("1. إضافة كتاب")
    print("2. عرض جميع الكتب")
    print("3. البحث عن كتاب")
    print("4. عرض كتاب برقم محدد")
    print("5. تعديل بيانات كتاب")
    print("6. حذف كتاب")
    print("7. الخروج")
    print("=" * 50)


def read_required_text(message):
    while True:
        value = input(message).strip()

        if value:
            return value

        print("هذا الحقل مطلوب، حاول مرة أخرى.")


def read_year():
    while True:
        year = input("أدخل سنة النشر: ").strip()

        if year.isdigit():
            return int(year)

        print("يرجى إدخال سنة صحيحة.")


def read_book_id():
    while True:
        book_id = input("أدخل رقم الكتاب: ").strip()

        if book_id.isdigit():
            return int(book_id)

        print("يرجى إدخال رقم صحيح.")


def display_book(book):
    if book is None:
        print("لم يتم العثور على الكتاب.")
        return

    status = "متاح" if book["available"] else "مستعار"

    print("-" * 60)
    print(f"رقم الكتاب: {book['id']}")
    print(f"العنوان: {book['title']}")
    print(f"المؤلف: {book['author']}")
    print(f"التصنيف: {book['category']}")
    print(f"سنة النشر: {book['year']}")
    print(f"الحالة: {status}")
    print("-" * 60)


def display_books(books):
    if not books:
        print("لا توجد كتب لعرضها.")
        return

    print("\nقائمة الكتب:")
    print("=" * 100)

    for book in books:
        status = "متاح" if book["available"] else "مستعار"

        print(
            f"الرقم: {book['id']} | "
            f"العنوان: {book['title']} | "
            f"المؤلف: {book['author']} | "
            f"التصنيف: {book['category']} | "
            f"السنة: {book['year']} | "
            f"الحالة: {status}"
        )

    print("=" * 100)


def add_book_menu():
    print("\n--- إضافة كتاب جديد ---")

    title = read_required_text("أدخل عنوان الكتاب: ")
    author = read_required_text("أدخل اسم المؤلف: ")
    category = read_required_text("أدخل التصنيف: ")
    year = read_year()

    add_book(title, author, category, year)
    print("تمت إضافة الكتاب بنجاح.")


def show_all_books_menu():
    print("\n--- جميع الكتب ---")
    books = get_all_books()
    display_books(books)


def search_books_menu():
    print("\n--- البحث عن كتاب ---")

    keyword = read_required_text(
        "أدخل العنوان أو المؤلف أو التصنيف: "
    )

    books = search_books(keyword)
    display_books(books)


def show_book_by_id_menu():
    print("\n--- عرض كتاب محدد ---")

    book_id = read_book_id()
    book = get_book_by_id(book_id)
    display_book(book)


def update_book_menu():
    print("\n--- تعديل بيانات كتاب ---")

    book_id = read_book_id()
    old_book = get_book_by_id(book_id)

    if old_book is None:
        print("لم يتم العثور على كتاب بهذا الرقم.")
        return

    print("\nالبيانات الحالية:")
    display_book(old_book)

    print("أدخل البيانات الجديدة:")

    title = read_required_text("العنوان الجديد: ")
    author = read_required_text("المؤلف الجديد: ")
    category = read_required_text("التصنيف الجديد: ")
    year = read_year()

    updated = update_book(
        book_id,
        title,
        author,
        category,
        year,
    )

    if updated:
        print("تم تعديل الكتاب بنجاح.")
    else:
        print("لم يتم تعديل الكتاب.")


def delete_book_menu():
    print("\n--- حذف كتاب ---")

    book_id = read_book_id()
    book = get_book_by_id(book_id)

    if book is None:
        print("لم يتم العثور على كتاب بهذا الرقم.")
        return

    print("\nبيانات الكتاب:")
    display_book(book)

    confirmation = input(
        "هل أنت متأكد؟ اكتب نعم للتأكيد: "
    ).strip().lower()

    if confirmation not in ("نعم", "yes", "y"):
        print("تم إلغاء الحذف.")
        return

    deleted = delete_book(book_id)

    if deleted:
        print("تم حذف الكتاب بنجاح.")
    else:
        print("لم يتم حذف الكتاب.")


def main():
    initialize_database()

    print("مرحبًا بك في نظام إدارة المكتبة.")

    while True:
        show_menu()
        choice = input("اختر رقم العملية: ").strip()

        if choice == "1":
            add_book_menu()
        elif choice == "2":
            show_all_books_menu()
        elif choice == "3":
            search_books_menu()
        elif choice == "4":
            show_book_by_id_menu()
        elif choice == "5":
            update_book_menu()
        elif choice == "6":
            delete_book_menu()
        elif choice == "7":
            print("شكرًا لاستخدام نظام إدارة المكتبة.")
            break
        else:
            print("اختيار غير صحيح.")


if __name__ == "__main__":
    main()

كيف تعمل القائمة الرئيسية؟

بعد إنشاء قاعدة البيانات يبدأ البرنامج حلقة مستمرة:

while True:
    show_menu()
    choice = input("اختر رقم العملية: ").strip()

ثم يستخدم if / elif لاستدعاء الدالة المناسبة. وعند اختيار الرقم 7 ينفذ break وتنتهي الحلقة.

التحقق من المدخلات

منع النص الفارغ

الدالة read_required_text() تعيد الطلب إذا ضغط المستخدم Enter بدون كتابة قيمة.

قراءة السنة والرقم

في read_year() وread_book_id() نستخدم isdigit() للتأكد من أن النص يتكون من أرقام قبل تحويله إلى int.

{alertWarning} هذا تحقق أساسي مناسب للنسخة التعليمية. في مشروع إنتاجي ستحتاج قواعد أقوى مثل التحقق من مدى السنة، وعدم تكرار البيانات، ومعالجة أخطاء قاعدة البيانات بصورة أوسع.

كيف تعمل عملية إضافة كتاب من البداية إلى النهاية؟

المستخدم يختار 1
        ↓
add_book_menu()
        ↓
قراءة العنوان والمؤلف والتصنيف والسنة
        ↓
add_book(...)
        ↓
get_connection()
        ↓
INSERT INTO books
        ↓
commit()
        ↓
close()
        ↓
رسالة نجاح

هذا التسلسل يوضح مبدأ مهمًا: main.py يتعامل مع المستخدم، وbooks.py يحتوي منطق الكتب، وdatabase.py يدير الاتصال وقاعدة البيانات.

مسار إضافة كتاب من main.py إلى SQLite

تشغيل المشروع

افتح الطرفية داخل مجلد المشروع:

cd library_system

ثم شغّل:

python main.py

وسيظهر:

نظام إدارة المكتبة
1. إضافة كتاب
2. عرض جميع الكتب
3. البحث عن كتاب
4. عرض كتاب برقم محدد
5. تعديل بيانات كتاب
6. حذف كتاب
7. الخروج

تجربة عملية

اختر 1 ثم أدخل مثلًا:

عنوان الكتاب: تعلم بايثون
اسم المؤلف: أحمد محمد
التصنيف: برمجة
سنة النشر: 2026

بعد الإضافة اختر 2 لعرض الكتب. ستلاحظ أيضًا إنشاء ملف library.db داخل المجلد.

ما الذي تعلمته من المشروع؟

  • إنشاء قاعدة SQLite من بايثون.
  • إنشاء الجداول باستخدام SQL.
  • الفرق بين fetchall() وfetchone().
  • استخدام INSERT وSELECT وLIKE وUPDATE وDELETE.
  • حفظ التغييرات باستخدام commit().
  • فصل مسؤوليات المشروع بين عدة ملفات.
  • قراءة مدخلات المستخدم والتحقق الأساسي منها.

هل هذا نظام مكتبة كامل؟

لا. هو نسخة أولى تعليمية جاهزة للعمل لإدارة الكتب. ويمكن تطويره لاحقًا بإضافة:

  • جدول للأعضاء.
  • الاستعارة والإرجاع.
  • منع استعارة كتاب مستعار.
  • تواريخ الاستعارة والإرجاع.
  • التقارير.
  • معالجة أخطاء أوسع.
  • اختبارات آلية.
  • واجهة رسومية أو واجهة ويب.

هذه الإضافات تستحق مراحل أو دروسًا مستقلة بدل حشرها كلها في درس واحد.

أسئلة شائعة

هل أحتاج إلى تثبيت SQLite قبل تشغيل المشروع؟

المشروع يستخدم وحدة sqlite3 المدمجة في بايثون، لذلك لا يحتاج إلى إضافة مكتبة Python خارجية لهذا الدرس.

أين تُحفظ بيانات الكتب؟

تُحفظ داخل ملف library.db الذي يتم إنشاؤه في مجلد المشروع عند التشغيل.

لماذا قسمنا المشروع إلى ثلاثة ملفات؟

حتى يكون لكل جزء مسؤولية واضحة: قاعدة البيانات في database.py، عمليات الكتب في books.py، وواجهة المستخدم في main.py.

ما الفرق بين fetchall وfetchone؟

fetchall() تعيد جميع الصفوف الناتجة، بينما fetchone() تعيد صفًا واحدًا فقط أو None إذا لم توجد نتيجة.

لماذا نستخدم علامات الاستفهام ? في SQL؟

لتمرير القيم إلى الاستعلام بصورة منفصلة ومنظمة بدل دمج النصوص المدخلة مباشرة داخل جملة SQL.

هل يمكن إضافة نظام استعارة وإرجاع؟

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

إرسال تعليق

أحدث أقدم