في هذا الدرس سنجعل التطبيق احترافيًا بحيث:
- كل مستخدم يرى ملاحظاته فقط
- لا يمكن لمستخدم فتح أو تعديل أو حذف ملاحظات شخص آخر
- حماية الروابط URL بحيث لا يستطيع أحد العبث بها
- تطبيق Authorization بعد أن أضفنا Authentication في الدرس السابق
هذا هو ما يجعل التطبيق جاهزًا ليصبح منتجًا حقيقيًا 💯✨
🎯 ما ستتعلمه اليوم
- إضافة عمود user_id داخل notes لربط كل ملاحظة بصاحبها
- حماية مسارات (Routes) تعديل وحذف الملاحظات
- منع الوصول غير المصرح به Unauthorized Access
- تطبيق دالة حماية Decorator للتأكد من تسجيل الدخول
- جعل كل عمليات CRUD خاصة بالمستخدم الحالي فقط
🧱 الخطوة 1: ربط الملاحظات بالمستخدم — تعديل جدول Notes
لو لم يكن لدينا حقل user_id بالفعل في جدول الملاحظات… نضيفه هكذا:
migration (manual update)
استخدم SQLite Browser أو نفذ الأمر:
ALTER TABLE notes ADD COLUMN user_id TEXT;
🧱 الخطوة 2: عند إضافة ملاحظة جديدة نضع user_id
في add_note داخل app.py:
@app.route('/add', methods=['POST'])
def add_note():
if 'user' not in session:
flash("Please login first", "warning")
return redirect(url_for('login'))
title = request.form['title']
content = request.form['content']
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
c.execute("INSERT INTO notes (title, content, user_id) VALUES (?, ?, ?)",
(title, content, user_email))
conn.commit()
conn.close()
flash("Note added successfully!", "success")
return redirect(url_for('notes'))
🧱 الخطوة 3: عرض الملاحظات الخاصة بالمستخدم فقط
في صفحة notes:
@app.route('/notes')
def notes():
if 'user' not in session:
return redirect(url_for('login'))
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
c.execute("SELECT id, title, content FROM notes WHERE user_id = ?", (user_email,))
notes_list = c.fetchall()
conn.close()
return render_template('notes.html', notes=notes_list)
🧱 الخطوة 4: حماية تعديل الملاحظات (Edit)
قبل تعديل الملاحظة يجب التأكد:
✔ أن الملاحظة تخص المستخدم
❌ وإلا → نطرده برسالة Access Denied
@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
if 'user' not in session:
return redirect(url_for('login'))
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
# التأكد أن الملاحظة ملك للمستخدم
c.execute("SELECT title, content, user_id FROM notes WHERE id = ?", (note_id,))
note = c.fetchone()
if not note or note[2] != user_email:
flash("Access Denied!", "danger")
return redirect(url_for('notes'))
if request.method == 'POST':
new_title = request.form['title']
new_content = request.form['content']
c.execute("UPDATE notes SET title=?, content=? WHERE id=?",
(new_title, new_content, note_id))
conn.commit()
conn.close()
flash("Note updated successfully!", "success")
return redirect(url_for('notes'))
conn.close()
return render_template('edit.html', note_id=note_id, title=note[0], content=note[1])
🧱 الخطوة 5: حماية حذف الملاحظات (Delete)
@app.route('/delete/<int:note_id>')
def delete_note(note_id):
if 'user' not in session:
return redirect(url_for('login'))
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
# التأكد من ملكية الملاحظة
c.execute("SELECT user_id FROM notes WHERE id = ?", (note_id,))
owner = c.fetchone()
if not owner or owner[0] != user_email:
flash("Access Denied!", "danger")
return redirect(url_for('notes'))
c.execute("DELETE FROM notes WHERE id = ?", (note_id,))
conn.commit()
conn.close()
flash("Note deleted successfully!", "success")
return redirect(url_for('notes'))
🧱 الخطوة 6: إضافة Decorator بسيط لحماية الصفحات
بدل ما نكرر كود "لو المستخدم مش مسجل دخول":
في أعلى app.py:
from functools import wraps
def login_required(f):
@wraps(f)
def protected(*args, **kwargs):
if 'user' not in session:
flash("Please login first", "warning")
return redirect(url_for('login'))
return f(*args, **kwargs)
return protected
ثم نستخدمه هكذا:
@app.route('/notes')
@login_required
def notes():
...
🎯 ما الذي حققناه في هذا الدرس؟
✔ كل مستخدم يرى ملاحظاته فقط
✔ لا يمكن فتح / تعديل / حذف ملاحظات مستخدم آخر
✔ حماية روابط URL
✔ حماية الصفحات باستخدام Decorator
✔ التطبيق أصبح آمن واحترافي ومناسب للسيرفرات
🧪 تمرين الدرس 17: حماية ملاحظات كل مستخدم بنظام صلاحيات كامل
🔸 المطلوب
قم بتطوير النظام بحيث:
1- يظهر زر تعديل وحذف الملاحظة فقط لصاحب الملاحظة الحقيقي(أي مستخدم آخر يدخل على صفحة notes لن يرى هذه الأزرار)
2- إذا حاول شخص الدخول إلى رابط مباشر مثل:
/edit/5
وهو لا يملك الملاحظة 5 → يجب أن تظهر له رسالة:
Access Denied3- عدّل صفحة عرض الملاحظات بحيث يظهر أسفل كل ملاحظة:
تمت الإضافة بواسطة: البريد الإلكتروني للمستخدم
ولكن لا تسمح بالتعديل إلا لصاحب الملاحظة.
هدف التمرين:
فهم كيف يعمل Authorization في مشروع Flask Notes App
(وهذا فرق مهم عن Authentication)
✅ الحل الكامل للتمرين
🔹 الخطوة 1: تعديل صفحة notes.html لإخفاء الأزرار لغير المالك
افتح templates/notes.html
وأضف داخل كل بطاقة ملاحظة الشرط التالي:
<div class="card mb-3">
<div class="card-body">
<h5>{{ note[1] }}</h5>
<p>{{ note[2] }}</p>
<p class="text-muted" style="font-size: 14px;">
تمت الإضافة بواسطة: {{ note[3] }}
</p>
{% if session['user']['email'] == note[3] %}
<a href="/edit/{{ note[0] }}" class="btn btn-warning btn-sm">تعديل</a>
<a href="/delete/{{ note[0] }}" class="btn btn-danger btn-sm">حذف</a>
{% endif %}
</div>
</div>
لاحظ أننا عرضنا email المستخدم الذي أضاف الملاحظة
واستخدمنا شرطًا لإظهار الأزرار لصاحبها فقط.
🔹 الخطوة 2: تعديل Route جلب الملاحظات لإرجاع user_id
في app.py داخل route صفحة notes:
@app.route('/notes')
@login_required
def notes():
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
c.execute("""
SELECT id, title, content, user_id
FROM notes
WHERE user_id = ?
""", (user_email,))
notes_list = c.fetchall()
conn.close()
return render_template('notes.html', notes=notes_list)
الآن نرسل الـ user_id إلى الصفحة لعرض اسم منشئ الملاحظة.
🔹 الخطوة 3: حماية صفحة تعديل الملاحظات
في Route التعديل:
@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
@login_required
def edit_note(note_id):
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
c.execute("SELECT id, title, content, user_id FROM notes WHERE id = ?", (note_id,))
note = c.fetchone()
if not note:
flash("Note not found!", "danger")
return redirect(url_for('notes'))
# حماية ملكية الملاحظة
if note[3] != user_email:
flash("Access Denied!", "danger")
return redirect(url_for('notes'))
if request.method == 'POST':
new_title = request.form['title']
new_content = request.form['content']
c.execute("UPDATE notes SET title=?, content=? WHERE id=?",
(new_title, new_content, note_id))
conn.commit()
conn.close()
flash("Note updated successfully!", "success")
return redirect(url_for('notes'))
conn.close()
return render_template('edit.html', note=note)
🔹 الخطوة 4: حماية صفحة حذف الملاحظات
@app.route('/delete/<int:note_id>')
@login_required
def delete_note(note_id):
user_email = session['user']['email']
conn = sqlite3.connect('notes.db')
c = conn.cursor()
c.execute("SELECT user_id FROM notes WHERE id = ?", (note_id,))
owner = c.fetchone()
if not owner or owner[0] != user_email:
flash("Access Denied!", "danger")
return redirect(url_for('notes'))
c.execute("DELETE FROM notes WHERE id = ?", (note_id,))
conn.commit()
conn.close()
flash("Note deleted successfully!", "success")
return redirect(url_for('notes'))
🔥 هكذا نكون طبقنا نظام أذونات كامل:
✔ ملاحظات كل مستخدم تخصه فقط
✔ ممنوع تعديل أو حذف ملاحظات غيرك
✔ URL Protected حتى لو كتب شخص رابط ملاحظة ليست له
✔ إظهار أزرار التحكم لصاحب الملاحظة فقط
✔ تعزيز مستوى الأمان والخصوصية في التطبيق

