المكتبة

الفصل الأخير: بناء مشروع متكامل – تطبيق قائمة المهام

الفصل الأخير: بناء مشروع متكامل – تطبيق قائمة المهام

مقدمة الفصل

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

هذا الفصل هو تتويج لكل ما تعلمته. سنقوم هنا بجمع كل المهارات التي اكتسبتها لبناء مشروع حقيقي ومتكامل من الألف إلى الياء. مشروعنا سيكون تطبيقًا لإدارة المهام (To-Do List) يعمل من سطر الأوامر. من خلال بنائه، لن تتعلم فقط كيف تجمع المفاهيم معًا، بل كيف يفكر المبرمجون المحترفون عند تحويل فكرة إلى منتج عملي.

هيا بنا نبدأ في بناء آخر وأهم مشروع في هذا الكتاب.


1. التخطيط للمشروع: التفكير قبل البرمجة

مقدمة هذا القسم

قبل أن نكتب أي سطر برمجي، أفضل الممارسات دائمًا هي التوقف والتخطيط. التخطيط الجيد يشبه رسم خريطة قبل الانطلاق في رحلة؛ فهو يمنعنا من الضياع ويوفر علينا الكثير من الوقت والجهد لاحقًا. في هذا القسم، سنجيب على سؤالين أساسيين: ماذا نريد أن يفعل برنامجنا؟ وكيف سننظم ملفاتنا لتحقيق ذلك بطريقة احترافية؟

أ. ميزات التطبيق (ماذا سيفعل؟)

أولاً، يجب أن نحدد بوضوح ما هي الوظائف التي سيقدمها تطبيقنا للمستخدم. تطبيقنا البسيط يجب أن يسمح بالوظائف الأساسية التالية:

  1. عرض جميع المهام الحالية.
  2. إضافة مهمة جديدة إلى القائمة.
  3. تحديث مهمة معينة واعتبارها “مُنجزة”.
  4. حذف مهمة لم نعد بحاجتها.
  5. حفظ المهام بشكل دائم في ملف، بحيث لا تُفقد عند إغلاق التطبيق.
  6. الخروج من التطبيق.

ب. هيكل المشروع (كيف سننظمه؟)

لدينا عدة مسؤوليات مختلفة في هذا التطبيق: هناك منطق يخص المهمة نفسها، ومنطق يخص تخزينها، ومنطق يخص التفاعل مع المستخدم. بدلاً من وضع كل هذا في ملف واحد كبير وفوضوي، سنطبق مبدأ “فصل الاهتمامات” (Separation of Concerns).

تخيل أننا نبني مطعمًا:

  • هناك الشيف الذي يملك “وصفة” كل وجبة (task.py). هو يعرف مكونات الوجبة وكيفية تحضيرها.
  • هناك المخزن والثلاجة (storage.py). هذا النظام مسؤول عن تخزين المكونات والوجبات بشكل آمن واسترجاعها عند الحاجة.
  • هناك النادل وقائمة الطعام (main.py). هذا هو الجزء الذي يتحدث مع الزبون، يأخذ الطلبات، ويقدم له الوجبات الجاهزة.

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

todo_project/
│
├── task.py         # (ملف الوصفة): سيحتوي على كلاس Task الذي يصف ما هي "المهمة".
├── storage.py      # (ملف المخزن): سيكون مسؤولاً عن حفظ واسترجاع المهام من ملف.
└── main.py         # (ملف النادل): سيكون مسؤولاً عن الواجهة والتفاعل مع المستخدم.

خاتمة هذا القسم

الآن بعد أن وضعنا خطة واضحة، نحن جاهزون للبدء في البرمجة. سنبدأ ببناء الجزء الأساسي والأكثر أهمية في تطبيقنا: “وصفة” المهمة، أو كلاس Task.


2. بناء “مخطط” المهمة (ملف task.py)

مقدمة هذا القسم

في هذا الجزء، سنركز فقط على تعريف “المهمة” كوحدة مستقلة. ما هي المعلومات التي يجب أن تحتوي عليها كل مهمة؟ (مثل عنوانها وحالتها). وما هي الأفعال التي يمكننا القيام بها على مهمة واحدة؟ (مثل تغيير حالتها إلى “منجزة”). كل هذا المنطق سنضعه في كلاس Task.

الآن، في مجلد مشروعك، أنشئ ملفًا جديدًا باسم task.py واتبع الخطوات التالية:

الخطوة 1: استيراد وحدة datetime

الهدف: نريد أن تسجل كل مهمة وقت وتاريخ إنشائها تلقائيًا. أفضل أداة لهذا هي وحدة datetime من مكتبة بايثون القياسية.

اكتب السطر التالي في بداية ملف task.py:

import datetime

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

الخطوة 2: تعريف كلاس Task

الهدف: إنشاء “المخطط” (Blueprint) الذي سيتم استخدامه لإنشاء كل مهمة في تطبيقنا.

أضف السطر التالي:

class Task:
    """يمثل مهمة واحدة في قائمة المهام لدينا."""

الشرح: نستخدم الكلمة المفتاحية class لتعريف كلاس جديد. أضفنا docstring لشرح الغرض منه، وهي ممارسة جيدة دائمًا.

الخطوة 3: بناء المُنشئ (دالة __init__)

الهدف: تحديد الخصائص الأولية التي يجب أن تمتلكها كل مهمة جديدة عند إنشائها.

أضف الأسطر التالية داخل كلاس Task (تذكر المسافة البادئة):

    def __init__(self, title, is_done=False):
        """يقوم بتهيئة مهمة جديدة عند إنشائها."""
        self.title = title
        self.is_done = is_done
        self.created_at = datetime.datetime.now()

الشرح التفصيلي:

  • __init__ هي دالة “سحرية” خاصة جدًا، تعمل تلقائيًا في كل مرة ننشئ فيها كائن Task جديد.
  • self هو أهم معامل، وهو يشير إلى الكائن (المهمة) نفسه الذي يتم إنشاؤه في تلك اللحظة. بايثون تقوم بتمريره تلقائيًا.
  • title هو عنوان المهمة الذي يجب أن نزوده به عند إنشائها.
  • is_done=False يعني أن أي مهمة جديدة ننشئها ستكون حالتها “غير منجزة” بشكل افتراضي.
  • self.title = title: هذا السطر يعني: “بالنسبة لهذه المهمة تحديدًا (self)، أنشئ خاصية اسمها title وخزن فيها قيمة العنوان title الذي تم تمريره”. وبالمثل لباقي الخصائص.

الخطوة 4: إضافة دالة لتحديث حالة المهمة

الهدف: توفير طريقة لتغيير حالة المهمة من “غير منجزة” إلى “منجزة”.

أضف الأسطر التالية داخل كلاس Task:

    def mark_as_done(self):
        """يضع علامة على المهمة كمنجزة."""
        self.is_done = True

الشرح: mark_as_done هي دالة (method) بسيطة. لأنها تأخذ self كمعامل، فهي تستطيع الوصول إلى خصائص الكائن الحالي وتعديلها. كل ما تفعله هو تغيير قيمة الخاصية self.is_done إلى True.

الخطوة 5: تحديد كيفية طباعة المهمة (دالة __str__)

الهدف: عندما نستخدم print() على كائن المهمة، نريد عرض معلومات مفيدة ومنسقة بدلاً من عنوان الذاكرة الغامض.

أضف الأسطر التالية داخل كلاس Task:

    def __str__(self):
        """تعيد تمثيلاً نصيًا سهل القراءة للمهمة."""
        # نحدد أيقونة الحالة بناءً على قيمة is_done
        status_icon = "✅" if self.is_done else "🔘"
        
        # نقوم بتنسيق التاريخ لعرضه بشكل جميل
        formatted_date = self.created_at.strftime("%Y-%m-%d")
        
        # نعيد النص النهائي المنسق
        return f"[{status_icon}] {self.title} (أضيفت في: {formatted_date})"

الشرح:

  • __str__ هي دالة سحرية أخرى يتم استدعاؤها تلقائيًا عندما نحاول تحويل الكائن إلى نص (مثلاً عند طباعته).
  • نستخدم تعبيرًا ثلاثيًا (اختصار لـ if/else) لاختيار الأيقونة المناسبة.
  • نعيد (return) نصًا منسقًا باستخدام f-string يجمع كل المعلومات معًا بشكل أنيق.

خاتمة هذا القسم

ممتاز! لقد قمنا الآن ببناء “النموذج” (Model) الخاص بتطبيقنا. لدينا مخطط واضح لكيفية إنشاء أي مهمة، وما هي خصائصها، وكيف تتصرف. الخطوة التالية هي بناء “المخزن” الذي سيقوم بحفظ هذه المهام بشكل دائم.


الجزء الثاني: بناء وحدة التخزين (ملف storage.py)

مقدمة هذا القسم

في هذا الجزء، سنبني الوحدة المسؤولة عن الاستمرارية (Persistence). كائنات Task التي ننشئها تعيش في ذاكرة الحاسوب المؤقتة (RAM)، وتختفي عند إغلاق البرنامج. مهمة storage.py هي تحويل هذه الكائنات إلى صيغة نصية يمكن حفظها في ملف، واسترجاعها مرة أخرى عند تشغيل البرنامج. هذه العملية تسمى التحويل التسلسلي (Serialization).

أنشئ ملفًا جديدًا باسم storage.py واتبع الخطوات التالية:

1. استيراد الوحدات اللازمة

الهدف: جلب الأدوات التي نحتاجها: json لتحويل هياكل بيانات بايثون إلى نص والعكس، و datetime للتعامل مع التواريخ، وكلاس Task لنتمكن من إعادة بناء كائنات المهام عند تحميلها.

import json
import datetime
from task import Task

2. بناء دالة save_tasks (التحويل التسلسلي)

الهدف: أخذ قائمة من كائنات Task، تحويلها إلى قائمة من القواميس البسيطة، ثم حفظها في ملف JSON.

# تحديد اسم الملف كثابت لتسهيل تغييره
STORAGE_FILE = "tasks.json"

def save_tasks(tasks):
    """تحول قائمة كائنات المهام إلى قواميس وتحفظها في ملف JSON."""
    list_to_save = []
    # نمر على كل كائن مهمة في القائمة
    for task in tasks:
        # نحول كل كائن إلى قاموس
        task_dict = {
            "title": task.title,
            "is_done": task.is_done,
            "created_at": task.created_at.isoformat() 
        }
        list_to_save.append(task_dict)

    # نفتح الملف ونحفظ القائمة المحولة بداخله
    with open(STORAGE_FILE, "w", encoding="utf-8") as f:
        json.dump(list_to_save, f, indent=4, ensure_ascii=False)

الشرح:

  • لا يمكن لـ JSON فهم كائنات بايثون المعقدة مباشرة، لكنه يفهم القواميس والقوائم. لذلك، نمر على كل كائن task ونستخلص بياناته في قاموس dict بسيط.
  • نحول كائن التاريخ إلى نص باستخدام .isoformat()، وهي صيغة قياسية يمكن تحويلها مرة أخرى بسهولة.
  • نستخدم json.dump لحفظ قائمة القواميس في الملف. indent=4 تجعل الملف منظمًا، وensure_ascii=False ضرورية لحفظ الأحرف العربية.

3. بناء دالة load_tasks (إلغاء التحويل التسلسلي)

الهدف: قراءة البيانات من ملف JSON وتحويلها مرة أخرى إلى قائمة من كائنات Task التي يفهمها برنامجنا.

def load_tasks():
    """تحمل المهام من ملف JSON وتعيد قائمة من كائنات المهام."""
    try:
        with open(STORAGE_FILE, "r", encoding="utf-8") as f:
            list_of_dicts = json.load(f)
            loaded_tasks = []
            for task_dict in list_of_dicts:
                # نعيد بناء كائن Task جديد من بيانات القاموس
                task_obj = Task(task_dict['title'], task_dict['is_done'])
                # نعيد تحويل نص التاريخ إلى كائن datetime
                task_obj.created_at = datetime.datetime.fromisoformat(task_dict['created_at'])
                loaded_tasks.append(task_obj)
            return loaded_tasks
    except (FileNotFoundError, json.JSONDecodeError):
        # إذا لم يكن الملف موجودًا أو كان فارغًا، نعيد قائمة فارغة
        return []

الشرح:

  • نستخدم try...except للتعامل مع حالتين: FileNotFoundError (عند أول تشغيل للبرنامج) و json.JSONDecodeError (إذا كان الملف فارغًا أو تالفًا).
  • json.load يقرأ الملف ويعيد لنا قائمة من القواميس.
  • داخل الحلقة، نمر على كل قاموس ونستخدم بياناته كـ “مكونات” لإنشاء كائن Task جديد، مع تحويل نص التاريخ مرة أخرى إلى كائن datetime حقيقي باستخدام fromisoformat.

خاتمة هذا القسم

لقد قمنا ببناء نظام تخزين قوي وموثوق. أصبح لدينا الآن القدرة على حفظ واسترجاع حالة تطبيقنا بشكل دائم. الخطوة الأخيرة هي بناء الواجهة التي ستمكن المستخدم من التفاعل مع كل هذه الميزات.


الجزء الثالث: بناء الواجهة الرئيسية (ملف main.py)

مقدمة هذا القسم

هذا هو الملف الذي سيقوم المستخدم بتشغيله. إنه “العقل المدبر” أو “لوحة التحكم” للتطبيق. وظيفته هي عرض الخيارات للمستخدم، استقبال أوامره، ثم استدعاء الأدوات المناسبة من الوحدات الأخرى (task.py و storage.py) لتنفيذ تلك الأوامر.

أنشئ ملفًا جديدًا باسم main.py واتبع الخطوات التالية:

1. استيراد الأدوات اللازمة

الهدف: جلب الأدوات من الوحدات التي أنشأناها.

from storage import save_tasks, load_tasks
from task import Task

2. بناء الدوال المساعدة للواجهة

الهدف: تنظيم الكود المتكرر. بدلاً من كتابة كود عرض القائمة وعرض المهام عدة مرات، نضعهما في دوال.

def show_menu():
    """تعرض القائمة الرئيسية للمستخدم."""
    print("\n--- تطبيق قائمة المهام ---")
    print("1. عرض جميع المهام")
    print("2. إضافة مهمة جديدة")
    print("3. تحديث مهمة (إنجاز)")
    print("4. حذف مهمة")
    print("5. الخروج")

def view_tasks(tasks_list):
    """تعرض كل المهام في القائمة مع ترقيمها."""
    print("\n--- مهامك الحالية ---")
    if not tasks_list:
        print("قائمة مهامك فارغة.")
    else:
        for i, task in enumerate(tasks_list, start=1):
            print(f"{i}. {task}")

الشرح:

  • دالة show_menu مسؤولة عن شيء واحد فقط: طباعة الخيارات المتاحة.
  • دالة view_tasks مسؤولة عن عرض قائمة المهام بشكل منسق. نستخدم enumerate لإضافة ترقيم تلقائي يبدأ من 1.

3. بناء الدالة الرئيسية main ومنطق التطبيق

الهدف: احتواء المنطق الرئيسي للتطبيق، بما في ذلك حلقة التشغيل التي تستقبل أوامر المستخدم وتنفذها.

def main():
    """الدالة الرئيسية التي تشغل التطبيق."""
    # أول شيء نفعله هو تحميل أي مهام محفوظة من الجلسات السابقة
    tasks_list = load_tasks()

    # نستخدم حلقة لا نهائية لإبقاء البرنامج يعمل
    while True:
        show_menu()
        choice = input("أدخل اختيارك (1-5): ")

        if choice == '1':
            # عرض كل المهام
            view_tasks(tasks_list)
        
        elif choice == '2':
            # إضافة مهمة جديدة
            title = input("أدخل عنوان المهمة الجديدة: ")
            tasks_list.append(Task(title))
            save_tasks(tasks_list) # نحفظ التغيير فورًا
            print("تمت إضافة المهمة بنجاح.")
            view_tasks(tasks_list) # نعرض القائمة المحدثة

        elif choice == '3':
            # تحديث مهمة
            view_tasks(tasks_list)
            try:
                task_num = int(input("أدخل رقم المهمة التي تريد إنجازها: "))
                if 1 <= task_num <= len(tasks_list):
                    tasks_list[task_num - 1].mark_as_done()
                    save_tasks(tasks_list)
                    print("تم تحديث المهمة بنجاح.")
                else:
                    print("رقم مهمة غير صالح.")
            except ValueError:
                print("الرجاء إدخال رقم صحيح.")

        elif choice == '4':
            # حذف مهمة
            view_tasks(tasks_list)
            try:
                task_num = int(input("أدخل رقم المهمة التي تريد حذفها: "))
                if 1 <= task_num <= len(tasks_list):
                    tasks_list.pop(task_num - 1)
                    save_tasks(tasks_list)
                    print("تم حذف المهمة بنجاح.")
                else:
                    print("رقم مهمة غير صالح.")
            except ValueError:
                print("الرجاء إدخال رقم صحيح.")

        elif choice == '5':
            # الخروج من البرنامج
            print("إلى اللقاء!")
            break
        
        else:
            print("اختيار غير صالح. الرجاء اختيار رقم من 1 إلى 5.")

# هذا السطر يضمن أن دالة main() تعمل فقط عند تشغيل هذا الملف مباشرة
if __name__ == "__main__":
    main()

الشرح:

  • load_tasks(): يتم استدعاؤها مرة واحدة في البداية لاستعادة حالة التطبيق.
  • while True:: هذه الحلقة هي “قلب” التطبيق الذي يستمر في العمل وعرض القائمة.
  • منطق الخيارات: كل كتلة if/elif مسؤولة عن خيار واحد. النمط العام هو: استقبال مدخلات إضافية من المستخدم إذا لزم الأمر، تعديل قائمة tasks_list (بإضافة كائن، أو استدعاء دالة على كائن، أو حذف كائن)، ثم استدعاء save_tasks فورًا لحفظ الحالة الجديدة.

خاتمة هذا القسم

لقد قمنا الآن بتجميع كل الأجزاء معًا. لدينا نموذج بيانات قوي (Task)، ونظام تخزين موثوق (storage)، وواجهة مستخدم تفاعلية (main). كل جزء يعمل بانسجام مع الآخر لبناء تطبيق متكامل.


4. تشغيل التطبيق وخاتمة الكتاب

الآن بعد أن أصبحت كل الملفات (task.py, storage.py, main.py) جاهزة في نفس المجلد، كل ما عليك فعله هو فتح سطر الأوامر في هذا المجلد وتشغيل الملف الرئيسي:

python main.py

جرب إضافة مهام، عرضها، تحديثها، وحذفها. أغلق التطبيق وأعد تشغيله. ستجد أن مهامك لا تزال محفوظة!

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

هذه ليست النهاية، بل هي البداية الحقيقية. عالم البرمجة واسع وممتع، والآن لديك الأساس القوي الذي يمكنك البناء عليه لاستكشاف أي مجال يثير اهتمامك. أتمنى لك كل التوفيق في رحلتك كمبرمج.

×

إعدادات القراءة

الوضع الليلي
حجم الخط 20px
نوع الخط
×

فهرس الكتاب