في هذا المشروع سنبني نظام إدارة مكتبة باستخدام بايثون و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.
تعديل كتاب باستخدام 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 يدير الاتصال وقاعدة البيانات.
تشغيل المشروع
افتح الطرفية داخل مجلد المشروع:
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.
هل يمكن إضافة نظام استعارة وإرجاع؟
نعم، لكنه يحتاج جدولًا للأعضاء وجدولًا لعمليات الاستعارة وربطها بالكتب، ولذلك من الأفضل جعله مرحلة مستقلة بعد إتقان النسخة الحالية.



