البرمجة

بناء تطبيق مهام كامل بلغة Python — مع قاعدة بيانات

📅 2026-09-25⏱ 13 دقائق قراءة
بعد أن بنينا آلة حاسبة احترافية، حان الوقت لمشروع أكبر: **تطبيق إدارة مهام كامل**. هذا المشروع سيعلمك: - التعامل مع قاعدة بيانات حقيقية (SQLite) - بناء واجهة رسومية متعددة النوافذ - إدارة البيانات (إضافة، تعديل، حذف) - تصدير البيانات إلى ملفات - تنظيم الكود في ملفات متعددة بنهاية هذا المقال، سيكون لديك تطبيق مهام كامل يمكنك استخدامه يومياً. ## 1. التخطيط للمشروع ### المتطلبات قبل كتابة أي كود، دعنا نحدد ماذا نريد: <table> <thead> <tr> <th>الميزة</th> <th>الوصف</th> </tr> </thead> <tbody> <tr> <td><strong>إضافة مهمة</strong></td> <td>عنوان، وصف، أولوية، تاريخ استحقاق</td> </tr> <tr> <td><strong>عرض المهام</strong></td> <td>قائمة بكل المهام مع حالتها</td> </tr> <tr> <td><strong>تعديل مهمة</strong></td> <td>تعديل أي حقل من حقول المهمة</td> </tr> <tr> <td><strong>حذف مهمة</strong></td> <td>حذف مهمة نهائياً</td> </tr> <tr> <td><strong>إكمال مهمة</strong></td> <td>وضع علامة "مكتملة"</td> </tr> <tr> <td><strong>فلترة</strong></td> <td>الكل، النشطة، المكتملة</td> </tr> <tr> <td><strong>تصدير</strong></td> <td>تصدير المهام إلى CSV</td> </tr> <tr> <td><strong>قاعدة بيانات</strong></td> <td>حفظ دائم للبيانات</td> </tr> </tbody> </table> ### هيكل قاعدة البيانات سنحتاج جدول واحد فقط للمهام: <table> <thead> <tr> <th>العمود</th> <th>النوع</th> <th>الوصف</th> </tr> </thead> <tbody> <tr> <td><code>id</code></td> <td>INTEGER</td> <td>معرّف فريد (تلقائي)</td> </tr> <tr> <td><code>title</code></td> <td>TEXT</td> <td>عنوان المهمة</td> </tr> <tr> <td><code>description</code></td> <td>TEXT</td> <td>وصف تفصيلي</td> </tr> <tr> <td><code>priority</code></td> <td>TEXT</td> <td>الأولوية (عالية/متوسطة/منخفضة)</td> </tr> <tr> <td><code>due_date</code></td> <td>TEXT</td> <td>تاريخ الاستحقاق</td> </tr> <tr> <td><code>completed</code></td> <td>INTEGER</td> <td>0 أو 1 (منجزة أم لا)</td> </tr> <tr> <td><code>created_at</code></td> <td>TEXT</td> <td>تاريخ الإنشاء</td> </tr> </tbody> </table> ### هيكل المشروع سنقسم المشروع إلى ملفات: ``` task_manager/ ├── main.py # نقطة البداية ├── database.py # إدارة قاعدة البيانات ├── app.py # واجهة التطبيق └── utils.py # دوال مساعدة ``` ## 2. ملف قاعدة البيانات — `database.py` هذا الملف يتعامل مع قاعدة بيانات SQLite: ```python """ database.py إدارة قاعدة بيانات تطبيق المهام """ import sqlite3 from datetime import datetime from typing import List, Dict, Optional class TaskDatabase: """فئة للتعامل مع قاعدة بيانات المهام""" def __init__(self, db_path: str = "tasks.db"): """تهيئة قاعدة البيانات""" self.db_path = db_path self.init_database() def get_connection(self): """الحصول على اتصال بقاعدة البيانات""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row # للوصول للأعمدة بالأسماء return conn def init_database(self): """إنشاء الجداول إذا لم تكن موجودة""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT DEFAULT '', priority TEXT DEFAULT 'متوسطة', due_date TEXT DEFAULT '', completed INTEGER DEFAULT 0, created_at TEXT NOT NULL ) """) conn.commit() conn.close() def add_task( self, title: str, description: str = "", priority: str = "متوسطة", due_date: str = "" ) -> int: """إضافة مهمة جديدة وإرجاع ID""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(""" INSERT INTO tasks (title, description, priority, due_date, created_at) VALUES (?, ?, ?, ?, ?) """, ( title, description, priority, due_date, datetime.now().strftime("%Y-%m-%d %H:%M") )) task_id = cursor.lastrowid conn.commit() conn.close() return task_id def get_all_tasks(self) -> List[Dict]: """الحصول على كل المهام""" conn = self.get_connection() cursor = conn.cursor() cursor.execute("SELECT * FROM tasks ORDER BY completed ASC, id DESC") rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def get_task(self, task_id: int) -> Optional[Dict]: """الحصول على مهمة واحدة""" conn = self.get_connection() cursor = conn.cursor() cursor.execute("SELECT * FROM tasks WHERE id = ?", (task_id,)) row = cursor.fetchone() conn.close() return dict(row) if row else None def update_task(self, task_id: int, **kwargs) -> bool: """تحديث مهمة""" if not kwargs: return False # بناء استعلام التحديث fields = [] values = [] for key, value in kwargs.items(): fields.append(f"{key} = ?") values.append(value) values.append(task_id) query = f"UPDATE tasks SET {', '.join(fields)} WHERE id = ?" conn = self.get_connection() cursor = conn.cursor() cursor.execute(query, values) affected = cursor.rowcount conn.commit() conn.close() return affected > 0 def delete_task(self, task_id: int) -> bool: """حذف مهمة""" conn = self.get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM tasks WHERE id = ?", (task_id,)) affected = cursor.rowcount conn.commit() conn.close() return affected > 0 def toggle_complete(self, task_id: int) -> bool: """تبديل حالة الإكمال""" task = self.get_task(task_id) if not task: return False new_status = 0 if task["completed"] else 1 return self.update_task(task_id, completed=new_status) def get_statistics(self) -> Dict: """إحصائيات المهام""" conn = self.get_connection() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM tasks") total = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM tasks WHERE completed = 1") completed = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM tasks WHERE completed = 0") active = total - completed cursor.execute("SELECT COUNT(*) FROM tasks WHERE priority = 'عالية'") high_priority = cursor.fetchone()[0] conn.close() return { "total": total, "completed": completed, "active": active, "high_priority": high_priority, } def export_to_csv(self, file_path: str) -> bool: """تصدير المهام إلى ملف CSV""" import csv tasks = self.get_all_tasks() if not tasks: return False try: with open(file_path, "w", encoding="utf-8-sig", newline="") as f: writer = csv.DictWriter(f, fieldnames=tasks[0].keys()) writer.writeheader() writer.writerows(tasks) return True except Exception as e: print(f"خطأ في التصدير: {e}") return False ``` ### شرح الأجزاء الأساسية <table> <thead> <tr> <th>الدالة</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>init_database()</code></td> <td>إنشاء جدول المهام إذا لم يكن موجوداً</td> </tr> <tr> <td><code>add_task()</code></td> <td>إضافة مهمة جديدة وإرجاع رقمها</td> </tr> <tr> <td><code>get_all_tasks()</code></td> <td>جلب كل المهام مرتبة</td> </tr> <tr> <td><code>update_task()</code></td> <td>تحديث أي حقل في المهمة</td> </tr> <tr> <td><code>delete_task()</code></td> <td>حذف مهمة نهائياً</td> </tr> <tr> <td><code>toggle_complete()</code></td> <td>تبديل حالة "منجزة"</td> </tr> <tr> <td><code>get_statistics()</code></td> <td>إحصائيات للعرض في الواجهة</td> </tr> <tr> <td><code>export_to_csv()</code></td> <td>تصدير المهام لملف Excel</td> </tr> </tbody> </table> ## 3. الواجهة الرسومية — `app.py` هذا الملف يحتوي على واجهة Tkinter: ```python """ app.py الواجهة الرسومية لتطبيق المهام """ import tkinter as tk from tkinter import ttk, messagebox, filedialog from database import TaskDatabase class TaskManagerApp: """تطبيق إدارة المهام""" def __init__(self, root): """تهيئة التطبيق""" self.root = root self.root.title("📝 مدير المهام | NahdaAI") self.root.geometry("900x650") self.root.configure(bg="#f0f2f5") self.db = TaskDatabase() self.current_filter = "الكل" self.selected_task_id = None # تحسين المظهر self.setup_styles() # بناء الواجهة self.create_header() self.create_toolbar() self.create_tasks_area() self.create_footer() # تحميل المهام self.load_tasks() def setup_styles(self): """إعداد الأنماط""" style = ttk.Style() style.theme_use("clam") style.configure( "Task.Treeview", background="white", fieldbackground="white", rowheight=35, font=("Arial", 11), ) style.configure( "Task.Treeview.Heading", background="#2563eb", foreground="white", font=("Arial", 12, "bold"), borderwidth=0, ) style.map( "Task.Treeview.Heading", background=[("active", "#1e40af")] ) def create_header(self): """إنشاء الهيدر""" header = tk.Frame(self.root, bg="#2563eb", height=80) header.pack(fill="x") header.pack_propagate(False) title = tk.Label( header, text="📝 مدير المهام", font=("Arial", 24, "bold"), bg="#2563eb", fg="white", ) title.pack(side="right", padx=30, pady=20) self.stats_label = tk.Label( header, text="", font=("Arial", 12), bg="#2563eb", fg="white", ) self.stats_label.pack(side="left", padx=30, pady=20) def create_toolbar(self): """إنشاء شريط الأدوات""" toolbar = tk.Frame(self.root, bg="#f0f2f5", pady=15) toolbar.pack(fill="x", padx=20) # زر إضافة مهمة add_btn = tk.Button( toolbar, text="➕ مهمة جديدة", font=("Arial", 12, "bold"), bg="#10b981", fg="white", activebackground="#059669", activeforeground="white", border=0, padx=20, pady=10, cursor="hand2", command=self.open_add_dialog, ) add_btn.pack(side="right", padx=5) # زر تصدير export_btn = tk.Button( toolbar, text="📥 تصدير CSV", font=("Arial", 12), bg="#6366f1", fg="white", activebackground="#4f46e5", activeforeground="white", border=0, padx=20, pady=10, cursor="hand2", command=self.export_tasks, ) export_btn.pack(side="right", padx=5) # فلتر filter_frame = tk.Frame(toolbar, bg="#f0f2f5") filter_frame.pack(side="left") tk.Label( filter_frame, text="عرض:", font=("Arial", 11), bg="#f0f2f5", ).pack(side="right", padx=5) self.filter_var = tk.StringVar(value="الكل") filter_menu = ttk.Combobox( filter_frame, textvariable=self.filter_var, values=["الكل", "النشطة", "المكتملة"], state="readonly", font=("Arial", 11), width=12, ) filter_menu.pack(side="right") filter_menu.bind("<<ComboboxSelected>>", lambda e: self.load_tasks()) def create_tasks_area(self): """إنشاء منطقة عرض المهام""" container = tk.Frame(self.root, bg="#f0f2f5") container.pack(fill="both", expand=True, padx=20, pady=10) # Treeview لعرض المهام columns = ("id", "title", "priority", "due_date", "status") self.tree = ttk.Treeview( container, columns=columns, show="headings", style="Task.Treeview", selectmode="browse", ) # رؤوس الأعمدة self.tree.heading("id", text="ID") self.tree.heading("title", text="المهمة") self.tree.heading("priority", text="الأولوية") self.tree.heading("due_date", text="تاريخ الاستحقاق") self.tree.heading("status", text="الحالة") # عرض الأعمدة self.tree.column("id", width=50, anchor="center") self.tree.column("title", width=350, anchor="e") self.tree.column("priority", width=100, anchor="center") self.tree.column("due_date", width=150, anchor="center") self.tree.column("status", width=100, anchor="center") # شريط التمرير scrollbar = ttk.Scrollbar(container, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side="right", fill="both", expand=True) scrollbar.pack(side="left", fill="y") # أزرار التحكم controls = tk.Frame(self.root, bg="#f0f2f5", pady=10) controls.pack(fill="x", padx=20) buttons = [ ("✏️ تعديل", "#f59e0b", self.edit_task), ("✅ تبديل الحالة", "#10b981", self.toggle_complete), ("🗑️ حذف", "#ef4444", self.delete_task), ] for text, color, command in buttons: btn = tk.Button( controls, text=text, font=("Arial", 11), bg=color, fg="white", activebackground=color, activeforeground="white", border=0, padx=20, pady=8, cursor="hand2", command=command, ) btn.pack(side="right", padx=5) # ربط النقر المزدوج self.tree.bind("<Double-1>", lambda e: self.edit_task()) def create_footer(self): """إنشاء الفوتر""" footer = tk.Frame(self.root, bg="#1a1a1a", height=40) footer.pack(fill="x", side="bottom") footer.pack_propagate(False) tk.Label( footer, text="© 2026 NahdaAI — مدير المهام بلغة Python", font=("Arial", 10), bg="#1a1a1a", fg="#888", ).pack(pady=10) def load_tasks(self): """تحميل المهام وعرضها""" # حذف القديم for item in self.tree.get_children(): self.tree.delete(item) # جلب المهام tasks = self.db.get_all_tasks() # الفلترة filter_type = self.filter_var.get() if filter_type == "النشطة": tasks = [t for t in tasks if not t["completed"]] elif filter_type == "المكتملة": tasks = [t for t in tasks if t["completed"]] # عرض المهام for task in tasks: status = "✅ مكتملة" if task["completed"] else "⏳ نشطة" priority_symbol = { "عالية": "🔴 عالية", "متوسطة": "🟡 متوسطة", "منخفضة": "🟢 منخفضة", }.get(task["priority"], task["priority"]) self.tree.insert( "", "end", values=( task["id"], task["title"], priority_symbol, task["due_date"] or "—", status, ), ) # تحديث الإحصائيات self.update_stats() def update_stats(self): """تحديث الإحصائيات""" stats = self.db.get_statistics() text = f"📊 المجموع: {stats['total']} | نشطة: {stats['active']} | مكتملة: {stats['completed']}" self.stats_label.config(text=text) def get_selected_task_id(self): """الحصول على ID المهمة المحددة""" selection = self.tree.selection() if not selection: messagebox.showwarning("تنبيه", "الرجاء اختيار مهمة أولاً") return None values = self.tree.item(selection[0], "values") return int(values[0]) def open_add_dialog(self): """فتح نافذة إضافة مهمة""" TaskDialog(self.root, self.db, on_save=self.load_tasks) def edit_task(self): """تعديل مهمة""" task_id = self.get_selected_task_id() if not task_id: return task = self.db.get_task(task_id) if task: TaskDialog(self.root, self.db, task=task, on_save=self.load_tasks) def toggle_complete(self): """تبديل حالة الإكمال""" task_id = self.get_selected_task_id() if not task_id: return if self.db.toggle_complete(task_id): self.load_tasks() def delete_task(self): """حذف مهمة""" task_id = self.get_selected_task_id() if not task_id: return task = self.db.get_task(task_id) if not task: return answer = messagebox.askyesno( "تأكيد الحذف", f"هل أنت متأكد من حذف المهمة:\n\n'{task['title']}'؟" ) if answer: if self.db.delete_task(task_id): self.load_tasks() def export_tasks(self): """تصدير المهام""" file_path = filedialog.asksaveasfilename( defaultextension=".csv", filetypes=[("CSV Files", "*.csv")], initialfile="tasks.csv", ) if file_path: if self.db.export_to_csv(file_path): messagebox.showinfo("نجاح", f"تم التصدير بنجاح إلى:\n{file_path}") else: messagebox.showerror("خطأ", "لا توجد مهام للتصدير") class TaskDialog: """نافذة إضافة/تعديل مهمة""" def __init__(self, parent, db, task=None, on_save=None): self.db = db self.task = task self.on_save = on_save self.dialog = tk.Toplevel(parent) self.dialog.title("تعديل مهمة" if task else "مهمة جديدة") self.dialog.geometry("500x450") self.dialog.configure(bg="#f0f2f5") self.dialog.transient(parent) self.dialog.grab_set() self.create_widgets() if task: self.load_task_data() def create_widgets(self): """إنشاء عناصر النافذة""" # العنوان tk.Label( self.dialog, text="عنوان المهمة *", font=("Arial", 11, "bold"), bg="#f0f2f5", ).pack(anchor="e", padx=30, pady=(20, 5)) self.title_entry = tk.Entry( self.dialog, font=("Arial", 12), justify="right", ) self.title_entry.pack(fill="x", padx=30, ipady=8) # الوصف tk.Label( self.dialog, text="الوصف", font=("Arial", 11, "bold"), bg="#f0f2f5", ).pack(anchor="e", padx=30, pady=(15, 5)) self.desc_text = tk.Text( self.dialog, font=("Arial", 11), height=5, wrap="word", ) self.desc_text.pack(fill="x", padx=30) # الأولوية tk.Label( self.dialog, text="الأولوية", font=("Arial", 11, "bold"), bg="#f0f2f5", ).pack(anchor="e", padx=30, pady=(15, 5)) self.priority_var = tk.StringVar(value="متوسطة") priority_frame = tk.Frame(self.dialog, bg="#f0f2f5") priority_frame.pack(fill="x", padx=30) for priority in ["عالية", "متوسطة", "منخفضة"]: tk.Radiobutton( priority_frame, text=priority, variable=self.priority_var, value=priority, font=("Arial", 11), bg="#f0f2f5", activebackground="#f0f2f5", ).pack(side="right", padx=10) # تاريخ الاستحقاق tk.Label( self.dialog, text="تاريخ الاستحقاق (YYYY-MM-DD)", font=("Arial", 11, "bold"), bg="#f0f2f5", ).pack(anchor="e", padx=30, pady=(15, 5)) self.date_entry = tk.Entry( self.dialog, font=("Arial", 11), justify="right", ) self.date_entry.pack(fill="x", padx=30, ipady=6) # الأزرار buttons = tk.Frame(self.dialog, bg="#f0f2f5") buttons.pack(fill="x", padx=30, pady=25) tk.Button( buttons, text="حفظ", font=("Arial", 12, "bold"), bg="#10b981", fg="white", border=0, padx=30, pady=10, cursor="hand2", command=self.save, ).pack(side="right", padx=5) tk.Button( buttons, text="إلغاء", font=("Arial", 12), bg="#94a3b8", fg="white", border=0, padx=30, pady=10, cursor="hand2", command=self.dialog.destroy, ).pack(side="right", padx=5) def load_task_data(self): """تحميل بيانات المهمة للتعديل""" self.title_entry.insert(0, self.task["title"]) self.desc_text.insert("1.0", self.task["description"]) self.priority_var.set(self.task["priority"]) self.date_entry.insert(0, self.task["due_date"]) def save(self): """حفظ المهمة""" title = self.title_entry.get().strip() if not title: messagebox.showwarning("تنبيه", "عنوان المهمة مطلوب") return description = self.desc_text.get("1.0", "end-1c").strip() priority = self.priority_var.get() due_date = self.date_entry.get().strip() if self.task: # تعديل self.db.update_task( self.task["id"], title=title, description=description, priority=priority, due_date=due_date, ) else: # إضافة جديدة self.db.add_task(title, description, priority, due_date) if self.on_save: self.on_save() self.dialog.destroy() ``` ## 4. نقطة البداية — `main.py` هذا الملف يشغّل التطبيق: ```python """ main.py نقطة بداية تطبيق المهام """ import tkinter as tk from app import TaskManagerApp def main(): """تشغيل التطبيق""" root = tk.Tk() app = TaskManagerApp(root) root.mainloop() if __name__ == "__main__": main() ``` ## 5. تشغيل التطبيق ### الخطوة 1: احفظ الملفات احفظ الملفات الأربعة في مجلد `task_manager/`: ``` task_manager/ ├── main.py ├── database.py ├── app.py └── utils.py (اختياري) ``` ### الخطوة 2: شغّل التطبيق ```bash cd task_manager python main.py ``` ### الخطوة 3: الاستخدام <table> <thead> <tr> <th>الإجراء</th> <th>الطريقة</th> </tr> </thead> <tbody> <tr> <td><strong>إضافة مهمة</strong></td> <td>اضغط "➕ مهمة جديدة"</td> </tr> <tr> <td><strong>عرض المهام</strong></td> <td>تظهر تلقائياً في الجدول</td> </tr> <tr> <td><strong>تعديل مهمة</strong></td> <td>اختر المهمة + اضغط "✏️ تعديل"</td> </tr> <tr> <td><strong>إكمال مهمة</strong></td> <td>اختر المهمة + اضغط "✅ تبديل الحالة"</td> </tr> <tr> <td><strong>حذف مهمة</strong></td> <td>اختر المهمة + اضغط "🗑️ حذف"</td> </tr> <tr> <td><strong>فلترة</strong></td> <td>اختر من قائمة "عرض"</td> </tr> <tr> <td><strong>تصدير</strong></td> <td>اضغط "📥 تصدير CSV"</td> </tr> </tbody> </table> ## 6. شرح المفاهيم المتقدمة ### 1. لماذا فصلنا الكود في ملفات؟ | السبب | الشرح | |-------|-------| | **التنظيم** | كل ملف له مسؤولية واحدة | | **الاختبار** | يمكن اختبار database.py منفصلاً | | **إعادة الاستخدام** | يمكن استخدام database.py في تطبيق آخر | | **الصيانة** | تعديل الواجهة لا يؤثر على البيانات | ### 2. مفهوم Context Manager في SQLite ```python conn = self.get_connection() cursor = conn.cursor() # ... عمليات conn.commit() conn.close() ``` **الأفضل:** استخدام `with`: ```python with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() # ... عمليات # commit و close تلقائيان ``` ### 3. معامل `**kwargs` في `update_task` يسمح بتحديث أي حقل دون كتابة دوال منفصلة: ```python # كل هذه تعمل بنفس الدالة db.update_task(1, title="عنوان جديد") db.update_task(1, priority="عالية") db.update_task(1, title="جديد", priority="عالية", completed=1) ``` ### 4. استخدام `sqlite3.Row` ```python conn.row_factory = sqlite3.Row ``` **الفائدة:** الوصول للأعمدة بالأسماء بدلاً من الأرقام: ```python # بدون Row task[1] # غامض # مع Row task["title"] # واضح ``` ## 7. التوسعات المقترحة ### 1. إضافة تصنيفات ```python # في قاعدة البيانات cursor.execute(""" CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, color TEXT DEFAULT '#2563eb' ) """) # إضافة عمود للمهام cursor.execute("ALTER TABLE tasks ADD COLUMN category_id INTEGER") ``` ### 2. تكرار المهام ```python def create_recurring_task(task_id, pattern): """إنشاء مهمة متكررة""" patterns = { "daily": 1, "weekly": 7, "monthly": 30, } # ... ``` ### 3. الإشعارات ```python import plyer def send_notification(title, message): """إرسال إشعار""" plyer.notification.notify( title=title, message=message, timeout=10 ) ``` ### 4. الوضع الليلي ```python def toggle_dark_mode(self): """تبديل الوضع الليلي""" if self.dark_mode: bg = "#1a1a1a" fg = "#ffffff" else: bg = "#f0f2f5" fg = "#000000" self.root.configure(bg=bg) # تحديث باقي المكونات... ``` ## 8. تحويل التطبيق إلى .exe ### باستخدام PyInstaller ```bash pip install pyinstaller pyinstaller --onefile --windowed --name "TaskManager" main.py ``` ### النتيجة ستجد ملف `TaskManager.exe` في مجلد `dist/`. **⚠️ ملاحظة:** الملف سيكون بحجم ~10MB بسبب مكتبات Python المضمنة. ## 9. أخطاء شائعة ### الخطأ 1: نسيان commit ```python # خطأ — التعديلات لم تُحفظ cursor.execute("INSERT INTO tasks ...") # نسيت conn.commit() # صحيح cursor.execute("INSERT INTO tasks ...") conn.commit() ``` ### الخطأ 2: عدم إغلاق الاتصال ```python # خطأ — تسرب في الذاكرة conn = sqlite3.connect("tasks.db") # ... عمليات # نسيت conn.close() # صحيح — استخدم try/finally try: conn = sqlite3.connect("tasks.db") # ... finally: conn.close() ``` ### الخطأ 3: SQL Injection ```python # خطأ — خطر أمني cursor.execute(f"SELECT * FROM tasks WHERE id = {user_id}") # صحيح — استخدام placeholders cursor.execute("SELECT * FROM tasks WHERE id = ?", (user_id,)) ``` ## 10. تمارين عملية ### التمرين 1: إضافة البحث أضف مربع بحث في الأعلى يفلتر المهام بالعنوان. ### التمرين 2: الألوان حسب الأولوية اجعل كل مهمة بلون مختلف حسب أولويتها: - عالية → خلفية حمراء فاتحة - متوسطة → خلفية صفراء فاتحة - منخفضة → خلفية خضراء فاتحة ### التمرين 3: عداد تنازلي أضف عموداً يعرض عدد الأيام المتبقية حتى تاريخ الاستحقاق. ### التمرين 4: إشعارات سطح المكتب عند إضافة مهمة بتاريخ اليوم، اعرض إشعاراً. ### التمرين 5: نسخ احتياطي أضف زر لإنشاء نسخة احتياطية من قاعدة البيانات. ## قائمة تحقق — هل بنيت التطبيق؟ <table> <thead> <tr> <th>المهارة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>فهم SQLite وقواعد البيانات</td> <td>⬜</td> </tr> <tr> <td>إنشاء جدول وعمود</td> <td>⬜</td> </tr> <tr> <td>عمليات CRUD (إضافة، قراءة، تعديل، حذف)</td> <td>⬜</td> </tr> <tr> <td>واجهة Tkinter متعددة النوافذ</td> <td>⬜</td> </tr> <tr> <td>Treeview لعرض البيانات</td> <td>⬜</td> </tr> <tr> <td>تنظيم الكود في ملفات</td> <td>⬜</td> </tr> <tr> <td>تصدير البيانات إلى CSV</td> <td>⬜</td> </tr> <tr> <td>تحويل التطبيق إلى .exe</td> <td>⬜</td> </tr> </tbody> </table> ## الخلاصة في هذا المقال، بنيت **تطبيق مهام احترافياً كاملاً**: 1. ✅ **التخطيط** — قبل كتابة أي كود 2. ✅ **قاعدة بيانات SQLite** — حفظ دائم للبيانات 3. ✅ **واجهة رسومية متقدمة** — Tkinter 4. ✅ **فصل الكود** — database.py و app.py و main.py 5. ✅ **عمليات CRUD كاملة** — إضافة، عرض، تعديل، حذف 6. ✅ **الفلاتر والإحصائيات** — تجربة أفضل 7. ✅ **تصدير CSV** — لمشاركة البيانات 8. ✅ **تحويل لـ .exe** — تطبيق مستقل **هذا المشروع يمكنك إضافته لمعرض أعمالك كتطبيق حقيقي!** **في المقال الأخير من السلسلة:** سنتعلم **Git و GitHub** — كيف تحفظ كودك وتشاركه مع العالم! **ما الميزة التي ستضيفها لتطبيقك؟ شاركنا في التعليقات!**