مشروع: نظام إدارة المكتبة

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


متطلبات المشروع

TEXT
1. إدارة الكتب (CRUD)
   ├── إضافة كتاب (العنوان، المؤلف، ISBN، سنة النشر)
   ├── حذف كتاب (بواسطة ISBN)
   ├── تعديل معلومات الكتاب
   └── البحث عن كتب (بحث ضبابي بالعنوان / بحث عن المؤلف)

2. إدارة الاستعارة
   ├── استعارة كتاب (تسجيل المستعير وتاريخ الاستعارة)
   ├── إرجاع كتاب (التحقق من التأخير)
   └── عرض سجلات الاستعارة

3. إحصائيات البيانات
   ├── إجمالي الكتب
   ├── عدد الكتب المتاحة/المستعارة
   └── العد حسب المؤلف

4. استمرارية البيانات (ملف CSV، تحميل تلقائي عند بدء التشغيل)

1. الكود الكامل

مثال: نظام إدارة المكتبة

PYTHON
import csv
import os
from datetime import datetime, timedelta
from dataclasses import dataclass

# ====== نموذج البيانات ======

@dataclass
class Book:
    """فئة بيانات الكتاب"""
    title: str
    author: str
    isbn: str
    year: int
    is_borrowed: bool = False
    borrower: str = ""
    borrow_date: str = ""

    def to_csv_row(self):
        """حوّل إلى صف CSV"""
        return [self.title, self.author, self.isbn, str(self.year),
                str(self.is_borrowed), self.borrower, self.borrow_date]

    @staticmethod
    def from_csv_row(row):
        """أنشئ كائن Book من صف CSV"""
        return Book(
            title=row[0], author=row[1], isbn=row[2],
            year=int(row[3]),
            is_borrowed=row[4] == "True",
            borrower=row[5], borrow_date=row[6]
        )


# ====== طبقة منطق الأعمال ======

class Library:
    """نظام إدارة المكتبة"""
    DATA_FILE = "library_data.csv"

    def __init__(self):
        self.books = []
        self.load_data()

    # ---- الاستمرارية ----

    def load_data(self):
        """حمّل البيانات من CSV"""
        if not os.path.exists(self.DATA_FILE):
            return
        with open(self.DATA_FILE, "r", encoding="utf-8") as f:
            reader = csv.reader(f)
            next(reader, None)
            for row in reader:
                if row:
                    self.books.append(Book.from_csv_row(row))
        print(f"Loaded {len(self.books)} books.")

    def save_data(self):
        """احفظ البيانات إلى CSV"""
        with open(self.DATA_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            writer.writerow(["Title", "Author", "ISBN", "Year",
                            "Borrowed", "Borrower", "Borrow Date"])
            for book in self.books:
                writer.writerow(book.to_csv_row())

    # ---- إدارة الكتب ----

    def add_book(self, title, author, isbn, year):
        """أضف كتاباً"""
        for book in self.books:
            if book.isbn == isbn:
                return False, "ISBN already exists!"
        self.books.append(Book(title, author, isbn, year))
        self.save_data()
        return True, f"Added '{title}'"

    def delete_book(self, isbn):
        """احذف كتاباً"""
        for i, book in enumerate(self.books):
            if book.isbn == isbn:
                removed = self.books.pop(i)
                self.save_data()
                return True, f"Deleted '{removed.title}'"
        return False, "Book with this ISBN not found."

    def search_by_title(self, keyword):
        """بحث ضبابي بالعنوان"""
        return [b for b in self.books if keyword.lower() in b.title.lower()]

    def search_by_author(self, author):
        """بحث عن المؤلف"""
        return [b for b in self.books if author.lower() in b.author.lower()]

    def get_book_by_isbn(self, isbn):
        """ابحث عن كتاب بواسطة ISBN"""
        for book in self.books:
            if book.isbn == isbn:
                return book
        return None

    # ---- إدارة الاستعارة ----

    def borrow_book(self, isbn, borrower):
        """استعِر كتاباً"""
        book = self.get_book_by_isbn(isbn)
        if not book:
            return False, "Book not found."
        if book.is_borrowed:
            return False, f"'{book.title}' is already borrowed by {book.borrower}."
        book.is_borrowed = True
        book.borrower = borrower
        book.borrow_date = datetime.now().strftime("%Y-%m-%d")
        self.save_data()
        return True, f"'{book.title}' has been borrowed by {borrower}."

    def return_book(self, isbn):
        """أرجِع كتاباً"""
        book = self.get_book_by_isbn(isbn)
        if not book:
            return False, "Book not found."
        if not book.is_borrowed:
            return False, f"'{book.title}' is not currently borrowed."
        borrow_date = datetime.strptime(book.borrow_date, "%Y-%m-%d")
        days_borrowed = (datetime.now() - borrow_date).days
        is_overdue = days_borrowed > 30

        book.is_borrowed = False
        book.borrower = ""
        book.borrow_date = ""
        self.save_data()

        if is_overdue:
            return True, f"'{book.title}' returned. Overdue by {days_borrowed - 30} days."
        return True, f"'{book.title}' returned. Borrowed for {days_borrowed} days."

    # ---- الإحصائيات ----

    def get_stats(self):
        """احصل على الإحصائيات"""
        total = len(self.books)
        borrowed = sum(1 for b in self.books if b.is_borrowed)
        available = total - borrowed

        author_count = {}
        for book in self.books:
            author_count[book.author] = author_count.get(book.author, 0) + 1

        return {
            "total": total,
            "available": available,
            "borrowed": borrowed,
            "by_author": dict(sorted(author_count.items(),
                                      key=lambda x: x[1], reverse=True))
        }


# ====== طبقة العرض ======

def show_menu():
    print("\n" + "=" * 40)
    print("Library Management System")
    print("=" * 40)
    print("1. Add Book")
    print("2. Delete Book")
    print("3. Search Books")
    print("4. Display All Books")
    print("5. Borrow Book")
    print("6. Return Book")
    print("7. Statistics")
    print("8. Exit")
    print("=" * 40)


def display_books(books, title="Search Results"):
    """اعرض قائمة من الكتب"""
    if not books:
        print(f"\n{title}: No records.")
        return
    print(f"\n--- {title} ---")
    print(f"{'Title':<20}{'Author':<12}{'ISBN':<15}{'Status':<8}")
    print("-" * 55)
    for b in books:
        status = "Borrowed" if b.is_borrowed else "Available"
        print(f"{b.title:<20}{b.author:<12}{b.isbn:<15}{status:<8}")


def main():
    library = Library()

    while True:
        show_menu()
        choice = input("Select option (1-8): ").strip()

        if choice == "1":
            title = input("Title: ").strip()
            author = input("Author: ").strip()
            isbn = input("ISBN: ").strip()
            year = input("Year: ").strip()
            success, msg = library.add_book(title, author, isbn, int(year))
            print(f"  {'OK' if success else 'Error'}: {msg}")

        elif choice == "2":
            isbn = input("Enter ISBN to delete: ").strip()
            success, msg = library.delete_book(isbn)
            print(f"  {'OK' if success else 'Error'}: {msg}")

        elif choice == "3":
            print("  1. Search by Title")
            print("  2. Search by Author")
            opt = input("Choose: ").strip()
            if opt == "1":
                keyword = input("Enter title keyword: ").strip()
                results = library.search_by_title(keyword)
            elif opt == "2":
                author = input("Enter author name: ").strip()
                results = library.search_by_author(author)
            else:
                print("Invalid choice")
                continue
            display_books(results)

        elif choice == "4":
            display_books(library.books, "All Books")

        elif choice == "5":
            isbn = input("Enter ISBN to borrow: ").strip()
            borrower = input("Borrower name: ").strip()
            success, msg = library.borrow_book(isbn, borrower)
            print(f"  {'OK' if success else 'Error'}: {msg}")

        elif choice == "6":
            isbn = input("Enter ISBN to return: ").strip()
            success, msg = library.return_book(isbn)
            print(f"  {'OK' if success else 'Error'}: {msg}")

        elif choice == "7":
            stats = library.get_stats()
            print(f"\n--- Statistics ---")
            print(f"Total books: {stats['total']}")
            print(f"Available: {stats['available']}  Borrowed: {stats['borrowed']}")
            print(f"\nBy Author:")
            for author, count in stats['by_author'].items():
                print(f"  {author}: {count} books")

        elif choice == "8":
            print("Thank you for using! Goodbye!")
            break

        else:
            print("Invalid choice. Please enter 1-8.")


if __name__ == "__main__":
    main()
▶ جرّب الكود

أبرز ملامح التصميم

العمارة متعددة الطبقات: نموذج البيانات (Book)، منطق الأعمال (Library)، وطبقة العرض (main) منفصلة، بمسؤوليات واضحة لكل طبقة.

استمرارية البيانات: تخزين في ملف CSV مع تحميل تلقائي عند بدء التشغيل — البيانات تبقى بعد إعادة تشغيل البرنامج.

منطق الاستعارة: يحسب تلقائياً مدة الاستعارة؛ يُحذّر من التأخير إذا تجاوز 30 يوماً.

تبسيط @dataclass: فئة Book تستخدم مُزيّن @dataclass، مما يلغي الكثير من الكود الأساسي.


❓ أسئلة شائعة

س ما الفرق بين @dataclass وكتابة __init__ يدوياً؟
ج @dataclass يُنشئ تلقائياً __init__ و __repr__ و __eq__ وطرقاً أخرى، مما يوفر عليك كتابة الكود الأساسي. وظيفياً متطابق، فقط أكثر إيجازاً. عندما تخزّن فئة بشكل أساسي بيانات بدون منطق معقد، فضّل @dataclass. س:لماذا استخدام CSV بدلاً من JSON لتخزين البيانات؟ ج:بيانات الكتب "جدولية" (صف واحد لكل كتاب، حقول ثابتة) — CSV يناسب هذا الهيكل جيداً ويمكن فتحه مباشرة في Excel. JSON أفضل للهياكل المتداخلة (مثل درجات الطلاب مع قواميس فرعية). كلاهما يعمل؛ اختر بناءً على هيكل البيانات. س:هل استخدام سلاسل نصية مثل "مستعار"/"متاح" للحالة قد يسبب مشاكل؟ ج:كافٍ للمشاريع الصغيرة. نهج أكثر دقة هو استخدام Enum لتعريف الحالات، لتجنب الأخطاء الإملائية في السلاسل النصية. لكن التعدادات موضوع متقدم؛ السلاسل النصية تعمل بشكل جيد هنا.

📖 ملخص

  • نظام إدارة المكتبة يستخدم عمارة ثلاثية الطبقات: نموذج بيانات ← منطق أعمال ← طبقة عرض
  • @dataclass يُبسّط تعريفات فئات البيانات؛ to_csv_row() / from_csv_row() تنفذ التسلسل
  • ملفات CSV توفر استمرارية البيانات؛ load_data() تقرأ عند التشغيل، save_data() تحفظ بعد كل تعديل
  • وحدة datetime تحسب مدة الاستعارة وتكتشف التأخير تلقائياً
  • شاملات القوائم وطريقة get() في القاموس تُستخدم للإحصائيات

📝 تمارين

  1. مبتدئ (الصعوبة ⭐): أضف حقل category لفئة Book (رواية، علوم، تاريخ، إلخ.) واسمح للمستخدم باختيار فئة عند إضافة كتاب.

  2. متوسط (الصعوبة ⭐⭐): أضف ميزة "سجل الاستعارة" — في كل مرة يُرجَع فيها كتاب، ألحق سجل الاستعارة بملف history.csv. تنسيق السجل: عنوان الكتاب، المستعير، تاريخ الاستعارة، تاريخ الإرجاع، حالة التأخير.

  3. متقدم (الصعوبة ⭐⭐⭐): بدون الرجوع إلى الكود أعلاه، ابنِ "نظام إدارة العضويات" من الصفر. تشمل الميزات: إضافة عضو (الاسم، الهاتف، النقاط)، استبدال النقاط، عرض قائمة الأعضاء، الترتيب حسب النقاط، حفظ البيانات إلى CSV. نظّم كودك باستخدام الفئات.

100%