مقدمة عن Django ORM وهدف دالة filter
Django ORM هو اختصار لـ Object-Relational Mapping، وهي طبقة وسيطة في إطار عمل Django تتيح للمطورين التعامل مع قاعدة البيانات باستخدام الكائنات (Objects) بدلاً من كتابة استعلامات SQL مباشرة. هذه الطبقة توفر واجهة برمجية واضحة ومألوفة لمطوري بايثون، وتساعد على تجنب الأخطاء الشائعة في كتابة SQL، كما تجعل الكود أسهل في القراءة والصيانة.
من بين أهم الأدوات التي يوفرها Django ORM نجد الدالة filter()
، وهي المسؤولة عن استرجاع مجموعة من السجلات (rows) من قاعدة البيانات التي تطابق شروطًا معينة. أي أنها تسمح لك بتحديد معايير بحث دقيقة لاستخلاص السجلات ذات العلاقة فقط، بدلاً من جلب كافة البيانات.
بمعنى آخر، filter()
تعمل كـ WHERE clause في SQL. فعلى سبيل المثال، بدلًا من كتابة:
SELECT * FROM users WHERE is_active = true;
يمكنك في Django ORM ببساطة كتابة:
User.objects.filter(is_active=True)
دالة filter()
ترجع دائمًا كائن من نوع QuerySet
، أي أنه يمكن التلاعب بالنتائج، دمجها، تصفيتها أكثر، أو ترتيبها بسهولة. هذا يجعل filter()
حجر الأساس في التعامل مع البيانات باستخدام Django ORM، خاصة عند بناء استعلامات معقدة أو تنفيذ عمليات بحث دقيقة داخل قواعد البيانات.
الصيغة العامة لدالة filter
دالة filter()
تُستخدم على مستوى مدير النماذج (model manager) في Django، وتحديدًا عبر Model.objects
، حيث يتم تمرير شروط التصفية كوسائط للدالة بصيغة كلمات مفتاحية (keyword arguments).
الشكل الأساسي:
Model.objects.filter(field=value)
في هذا الشكل، يقوم Django بتحويل هذا الاستدعاء إلى استعلام SQL يعادل:
SELECT * FROM model WHERE field = value;
مثال عملي:
لنفترض أن لدينا نموذجًا باسم Book
يحتوي على الحقل title
، يمكننا استرجاع الكتب التي عنوانها "Python Basics" بهذه الطريقة:
Book.objects.filter(title="Python Basics")
النتيجة ستكون QuerySet
يحتوي على جميع الكائنات (الكتب) التي تطابق هذا الشرط. لاحظ أن filter()
لا ترجع كائنًا واحدًا، بل دائمًا تعيد قائمة (قابلة للتكرار) حتى لو كانت تحتوي على عنصر واحد أو لا تحتوي على أي عنصر.
أمثلة على دمج شروط متعددة:
يمكن تمرير أكثر من شرط داخل نفس الدالة، وسيتم ربطها باستخدام AND:
Book.objects.filter(title="Python Basics", author="Ahmed")
هذا يعادل:
SELECT * FROM book WHERE title = 'Python Basics' AND author = 'Ahmed';
ملاحظات مهمة:
-
الحقول يجب أن تطابق تمامًا أسماء الحقول المعرفة داخل نموذج Django.
-
القيم يمكن أن تكون من نفس نوع الحقل (نص، رقم، تاريخ...).
-
filter()
يمكن استدعاؤها عدة مرات متتالية، وسيتم دمج الشروط كلها:Book.objects.filter(title="Python Basics").filter(author="Ahmed")
هذا يعطي نفس نتيجة المثال السابق.
ببساطة، دالة filter()
هي أداة قوية لكتابة استعلامات دقيقة بطريقة سهلة وقابلة للقراءة والصيانة.
أنواع المقارنات التي يمكن استخدامها
دالة filter()
لا تقتصر فقط على المقارنة البسيطة من نوع field=value
، بل تتيح مجموعة واسعة من المعاملات (lookup expressions) التي تسمح بتنفيذ استعلامات أكثر مرونة ودقة، وكلها تعتمد على صيغة field__lookup=value
.
إليك أهم أنواع المقارنات التي يمكن استخدامها:
1. المقارنة البسيطة (المساواة)
User.objects.filter(age=30)
تعني: إرجاع المستخدمين الذين عمرهم 30.
2. المقارنات العددية والمنطقية
- أصغر من: __lt
Product.objects.filter(price__lt=100)
يعني: السعر أقل من 100.
- أصغر من أو يساوي: __lte
Product.objects.filter(price__lte=100)
- أكبر من: __gt
Product.objects.filter(price__gt=100)
- أكبر من أو يساوي: __gte
Product.objects.filter(price__gte=100)
3. المطابقة التامة أو الجزئية للنصوص
- مطابقة تامة: __exact
Book.objects.filter(title__exact="Python Basics")
- مطابقة تامة بدون تمييز حالة الأحرف: __iexact
Book.objects.filter(title__iexact="python basics")
- يحتوي على: __contains
Article.objects.filter(content__contains="Django")
- يحتوي على (بدون تمييز حالة الأحرف): __icontains
Article.objects.filter(content__icontains="django")
4. يبدأ بـ أو ينتهي بـ
- يبدأ بـ: __startswith
User.objects.filter(name__startswith="A")
- ينتهي بـ: __endswith
User.objects.filter(name__endswith="n")
- كلاهما مع تجاهل حالة الأحرف: __istartswith
و __iendswith
5. المقارنة بين الحقول
يمكنك المقارنة بين قيمتين من نفس السجل باستخدام F()
من django.db.models
:
from django.db.models import F
Product.objects.filter(discount__lt=F('price'))
هذه المقارنات تجعل filter()
أداة قوية لإنشاء استعلامات معقدة دون الحاجة إلى كتابة SQL يدويًا. وهي تغطي معظم حالات الاستخدام في التطبيقات الواقعية، مثل البحث، التصنيف، التحقق من القيم، وغيرها.
التصفية باستخدام العلاقات ForeignKey
واحدة من أقوى ميزات Django ORM هي القدرة على التصفية عبر الجداول المرتبطة باستخدام العلاقات، خصوصًا العلاقات من نوع ForeignKey. بدلاً من كتابة استعلامات JOIN المعقدة في SQL، يمكنك ببساطة استخدام العلاقة كما لو كانت خاصية في النموذج.
مثال عملي على العلاقة:
نفترض أن لدينا النموذجين التاليين:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
تصفية الكتب حسب اسم المؤلف:
Book.objects.filter(author__name="Ahmed")
هذا الاستعلام سيعيد جميع الكتب التي كتبها مؤلف اسمه "Ahmed".
لاحظ استخدام __
للوصول إلى الحقول داخل العلاقة.
أمثلة إضافية:
تصفية حسب أكثر من شرط داخل العلاقة:
Book.objects.filter(author__name="Ahmed", author__age__gte=40)
تصفية حسب علاقة مكونة من أكثر من مستوى:
Book.objects.filter(author__country__name="Egypt")
إذا كان Author
مرتبط بـ Country
، يمكن الاستمرار في التصفية عبر عدة مستويات.
ملاحظات مهمة:
-
Django ينفذ استعلام SQL داخلي يحتوي على JOIN تلقائيًا.
-
لا حاجة لكتابة الكود الخاص بالربط بين الجداول.
-
الأداء جيد جدًا إذا تم استخدام
select_related()
لتقليل عدد الاستعلامات (سنعود لها لاحقًا).
باختصار، Django ORM يسمح بالوصول إلى الحقول المرتبطة من خلال ForeignKey
بكل سهولة، وكأنها امتداد طبيعي للنموذج الأساسي. وهذا يجعل الاستعلامات أكثر وضوحًا وأقل عرضة للأخطاء.
التصفية باستخدام القيم المنطقية (AND و OR)
بشكل افتراضي، عند تمرير عدة شروط إلى دالة filter()
، فإن Django ORM يربطها باستخدام AND، أي أن جميع الشروط يجب أن تتحقق في نفس السجل حتى يظهر في النتائج.
مثال على التصفية باستخدام AND:
User.objects.filter(is_active=True, age__gte=18)
هذا الاستعلام يعيد جميع المستخدمين الذين تنطبق عليهم الشرطان معًا: نشطون وعمرهم 18 سنة أو أكثر.
لكن في بعض الحالات، تحتاج إلى تنفيذ تصفية باستخدام OR أو بناء شروط منطقية أكثر تعقيدًا. وهنا يأتي دور الكائن Q
.
استخدام Q
للتصفية باستخدام OR
Q
هو كائن من django.db.models
يسمح ببناء شروط معقدة تتضمن OR، AND، NOT، أو حتى شروط متداخلة.
مثال على OR:
from django.db.models import Q
User.objects.filter(Q(is_staff=True) | Q(is_superuser=True))
هذا الاستعلام يرجع المستخدمين الذين هم موظفون أو مشرفون.
مثال على NOT:
User.objects.filter(~Q(is_active=True))
يرجع جميع المستخدمين غير النشطين.
دمج شروط Q مع شروط عادية:
يمكن الجمع بين Q()
والوسائط العادية في filter()
:
User.objects.filter(Q(age__lt=18) | Q(age__gt=60), is_active=True)
هنا: يرجع المستخدمين النشطين الذين عمرهم أقل من 18 أو أكثر من 60.
ملاحظات مهمة:
-
يمكن استخدام
&
بدلًا من,
لربط شروطQ
باستخدام AND. -
الأقواس ضرورية لضمان الترتيب الصحيح للعمليات المنطقية.
-
Q
مفيد جدًا في بناء استعلامات ديناميكية، خاصة عند وجود متغيرات تأتي من واجهة المستخدم أو API.
باستخدام Q
، تصبح دالة filter()
أداة أكثر مرونة، حيث يمكنك بناء استعلامات منطقية معقدة بطريقة واضحة ومنظمة دون اللجوء إلى SQL اليدوي.
التصفية باستخدام القيم الفارغة أو null
في قواعد البيانات، بعض الحقول قد تحتوي على قيمة فارغة (NULL)، وهي ليست مثل القيم الخالية أو الصفر، بل تعني "لا توجد قيمة". Django ORM يوفر طريقة واضحة للتعامل مع هذه الحالات من خلال معامل __isnull
.
استخدام __isnull=True
أو False
يمكنك تصفية السجلات بناءً على ما إذا كانت القيمة في حقل معين فارغة (NULL) أو لا:
مثال – إرجاع السجلات التي تحتوي على قيمة فارغة:
Employee.objects.filter(manager__isnull=True)
هذا الاستعلام يعيد جميع الموظفين الذين لا يوجد لديهم مدير مخصص (أي الحقل manager
فارغ).
مثال – إرجاع السجلات التي تحتوي على قيمة موجودة:
Employee.objects.filter(manager__isnull=False)
يعيد الموظفين الذين لديهم مدير معين.
متى نستخدم هذه التصفية؟
-
عندما يكون هناك حقل اختياري (
null=True
) مثل العلاقاتForeignKey
أو التواريخ التي قد لا تُحدد دائمًا. -
عند التعامل مع بيانات غير مكتملة، أو عند الحاجة لاكتشاف سجلات ناقصة.
-
في نماذج تحتوي على حالات غير إلزامية أو مراحل لم تُملأ بعد.
ملاحظات مهمة:
-
Django يترجم
__isnull=True
إلى SQL يعادل:WHERE field IS NULL
-
لا تخلط بين NULL والقيم مثل
0
أو''
(سلسلة فارغة)، فهذه لا تُعتبر NULL. -
يمكنك دمج
__isnull
مع شروط أخرى:Task.objects.filter(due_date__isnull=True, completed=False)
التعامل مع القيم الفارغة بطريقة صحيحة أمر أساسي في أي تطبيق يعمل على بيانات حقيقية، خاصة في الأنظمة التي تحتوي على مراحل إدخال أو اعتماد جزئي للمعلومات.
التصفية باستخدام القوائم (IN)
في كثير من الحالات، تحتاج إلى استرجاع السجلات التي تنتمي قيمها إلى مجموعة محددة من العناصر. Django ORM يوفّر لذلك المعامل __in
الذي يسمح بتصفية السجلات إذا كانت قيمة الحقل موجودة داخل قائمة أو مجموعة.
الشكل العام:
Model.objects.filter(field__in=[value1, value2, value3])
مثال عملي:
تصفية المنتجات التي تنتمي إلى فئات محددة:
Product.objects.filter(category__in=["books", "electronics", "clothing"])
يرجع جميع المنتجات التي تنتمي إلى أي من الفئات الثلاث المحددة.
تصفية المستخدمين الذين لديهم معرف (ID) ضمن قائمة معينة:
User.objects.filter(id__in=[1, 5, 9, 14])
تمرير QuerySet إلى in
يمكن أيضًا تمرير QuerySet
بدلاً من قائمة صريحة، ما يجعل __in
مفيدًا جدًا في العلاقات:
مثال على ذلك:
vip_users = User.objects.filter(is_vip=True)
Order.objects.filter(user__in=vip_users)
يرجع جميع الطلبات التي قام بها مستخدمون مميزون (VIP).
ملاحظات مهمة:
-
القيمة التي تمررها إلى
__in
يمكن أن تكونlist
,tuple
, أوQuerySet
. -
Django يُحوّل هذا الاستعلام إلى SQL باستخدام
IN (...)
. -
إذا كانت القائمة فارغة، فإن
filter(field__in=[])
لن يُرجع أي نتائج (نفس تأثير شرط غير ممكن في SQL). -
__in
مفيد بشكل خاص عند التعامل مع قوائم مختارة مسبقًا، مثل الفلاتر في واجهات المستخدم.
معامل __in
يجعل الاستعلامات أكثر قوة ومرونة، ويختصر الكثير من الأكواد المعقدة التي قد تحتاجها لو كنت تكتب SQL يدويًا.
التصفية حسب التواريخ والأوقات
عند العمل مع الحقول الزمنية مثل DateField
أو DateTimeField
، تحتاج كثيرًا إلى تصفية السجلات بناءً على السنة أو الشهر أو اليوم أو حتى الساعة والدقيقة. Django ORM يوفّر مجموعة من المعاملات الخاصة بالتواريخ تجعل هذا النوع من الاستعلامات بسيطًا وواضحًا.
المعاملات المتوفرة للتواريخ:
- __date
: لاستخلاص التاريخ فقط من حقل DateTime
Event.objects.filter(started_at__date="2025-07-17")
- __year
: لتصفية السجلات حسب السنة
Order.objects.filter(created_at__year=2025)
- __month
: لتصفية حسب الشهر
Order.objects.filter(created_at__month=7)
- __day
: لتصفية حسب اليوم
Order.objects.filter(created_at__day=17)
- __week
: لتصفية حسب رقم الأسبوع في السنة
Event.objects.filter(date__week=29)
- __week_day
: لتصفية حسب يوم الأسبوع (الأحد = 1)
Meeting.objects.filter(date__week_day=5) # الخميس
- __hour
, __minute
, __second
: لحقل DateTime فقط
Log.objects.filter(timestamp__hour=9)
الجمع بين أكثر من جزء من التاريخ:
يمكنك دمج أكثر من شرط زمني للوصول إلى استعلام دقيق:
Invoice.objects.filter(created_at__year=2025, created_at__month=7)
ملاحظات مهمة:
-
هذه المعاملات تعمل على الحقول من نوع
DateField
أوDateTimeField
. -
لا تحتاج إلى استدعاء توابع إضافية أو مكتبات خارجية.
-
في حال كنت تستخدم مناطق زمنية (timezones)، تأكد من التعامل الصحيح مع
timezone-aware datetime
.
باستخدام هذه المعاملات، يمكنك إنشاء استعلامات دقيقة جدًا حسب التواريخ والمواعيد، وهو أمر ضروري في تطبيقات تحتوي على تقارير، حجوزات، أحداث، أو أرشفة زمنية.
ترتيب النتائج بعد التصفية
بعد استخدام filter()
لتحديد السجلات المطلوبة، قد تحتاج إلى ترتيب النتائج حسب قيمة معينة مثل التاريخ، الاسم، السعر... إلخ. Django ORM يوفّر لذلك الدالة order_by()
التي تُستخدم مباشرة بعد filter()
أو حتى بدونها.
الشكل العام:
Model.objects.filter(...).order_by('field_name')
أمثلة عملية:
ترتيب تصاعدي (الافتراضي):
Post.objects.filter(published=True).order_by('created_at')
يعيد جميع التدوينات المنشورة، مرتبة من الأقدم إلى الأحدث حسب تاريخ الإنشاء.
ترتيب تنازلي:
Post.objects.filter(published=True).order_by('-created_at')
بإضافة علامة -
قبل اسم الحقل، يتم الترتيب من الأحدث إلى الأقدم.
ترتيب حسب أكثر من حقل:
User.objects.filter(is_active=True).order_by('last_name', 'first_name')
يرتب المستخدمين أولاً حسب الاسم الأخير، وإذا تساوى، يتم الترتيب حسب الاسم الأول.
ترتيب باستخدام علاقة ForeignKey:
Book.objects.filter(available=True).order_by('author__name')
يمكنك الترتيب حسب حقل موجود في نموذج مرتبط مثل اسم المؤلف.
ملاحظات مهمة:
-
order_by()
يعيد ترتيب العناصر فقط، ولا يُغير في التصفية أو البيانات. -
يمكنك استخدام
order_by()
بدونfilter()
إذا كنت فقط بحاجة لترتيب كل السجلات:Product.objects.order_by('price')
-
في حال لم تستخدم
order_by()
، فإن الترتيب الذي يرجع من قاعدة البيانات غير مضمون.
القدرة على ترتيب النتائج تجعل عرض البيانات للمستخدم أكثر تنظيمًا، وتُستخدم بشكل أساسي في الواجهات، التصنيفات، جداول البيانات، أو التقارير الزمنية والمالية.
تطبيق filter داخل التكرار أو استعلامات مركبة
يمكن استخدام دالة filter()
ليس فقط لاسترجاع بيانات بسيطة، بل يمكن تضمينها داخل استعلامات أكثر تعقيدًا، مثل استخدام الدوال التجميعية (aggregations)، التعليقات التوضيحية (annotations)، أو في عمليات prefetch_related
لجلب البيانات المرتبطة بكفاءة.
استخدام filter مع annotate
في بعض الحالات، نرغب في إضافة حقل محسوب إلى كل كائن، ثم نستخدم filter()
لتصفية النتائج بناءً على هذا الحقل.
مثال:
from django.db.models import Count
Author.objects.annotate(num_books=Count('book')).filter(num_books__gte=5)
هذا الاستعلام يعيد المؤلفين الذين لديهم خمسة كتب أو أكثر.
استخدام filter مع prefetch_related
عند جلب بيانات مرتبطة (مثلاً كتب مؤلف معين)، يمكن استخدام filter()
داخل Prefetch
لجلب البيانات المرتبطة بشكل مخصص.
مثال:
from django.db.models import Prefetch
books_qs = Book.objects.filter(published=True)
authors = Author.objects.prefetch_related(Prefetch('book_set', queryset=books_qs))
هنا نستخدم filter()
داخل Prefetch
لجلب الكتب المنشورة فقط مع المؤلفين.
استخدام filter داخل حلقات التكرار
يمكن أحيانًا استخدام filter()
داخل حلقة لتصفية بيانات فرعية لكل كائن، لكن يجب الحذر لأن هذا قد يؤدي إلى زيادة عدد الاستعلامات (N+1 problem).
مثال غير مرغوب:
for author in Author.objects.all():
books = Book.objects.filter(author=author, published=True)
يفضل استخدام prefetch_related
أو استراتيجيات أخرى لتقليل الاستعلامات.
ملاحظات عامة:
-
filter()
يمكن استخدامها لبناء استعلامات مركبة بتسلسل منطقي. -
الاستعلامات في Django تنفذ فعليًا عند محاولة الوصول إلى البيانات (lazy evaluation).
-
استخدام
filter()
داخلannotate
أوprefetch_related
يعزز الأداء والمرونة في التعامل مع قواعد البيانات.
باختصار، filter()
ليست فقط أداة لتصفية بسيطة بل يمكن إدخالها في استعلامات مركبة تتيح بناء بيانات معقدة وفعالة عبر Django ORM.
الأداء والنصائح
دالة filter()
في Django ORM قوية ومرنة، لكنها تعتمد على كيفية استخدامها وتأثيرها على أداء التطبيق، خاصة عند التعامل مع قواعد بيانات كبيرة. إليك أهم النقاط المتعلقة بالأداء والنصائح لتحسينه عند استخدام filter()
:
1. التنفيذ الكسول (Lazy Evaluation)
-
استعلامات Django ORM، بما في ذلك التي تستخدم
filter()
، لا تُنفذ مباشرة عند كتابتها، بل تُنفذ فقط عند الحاجة إلى الوصول للبيانات (مثل التكرار علىQuerySet
أو تحويله إلى قائمة). -
هذا يسمح ببناء استعلامات مركبة وتعديلها قبل التنفيذ، مما يوفر موارد النظام.
2. دمج عدة استدعاءات filter()
-
يمكنك استدعاء
filter()
عدة مرات متتالية، وسيتم دمج الشروط كلها في استعلام واحد يُنفذ لاحقًا:qs = Book.objects.filter(published=True) qs = qs.filter(author__name="Ahmed")
-
هذا يجعل كتابة الشروط أكثر تنظيمًا ومرونة.
3. تقليل عدد الاستعلامات باستخدام select_related و prefetch_related
-
عند التصفية على حقول مرتبطة (ForeignKey أو ManyToMany)، يجب استخدام
select_related()
أوprefetch_related()
لتقليل عدد استعلامات قاعدة البيانات وتحسين الأداء. -
مثال:
books = Book.objects.filter(published=True).select_related('author')
-
هذا يقلل من مشكلة الاستعلامات المتكررة (N+1 problem).
4. استخدام الفهارس (Indexes)
-
تأكد من أن الحقول التي تستخدمها كثيرًا في
filter()
عليها فهارس في قاعدة البيانات لتحسين سرعة البحث. -
Django يسمح بإضافة الفهارس عبر تعريف
indexes
في نموذجك أو باستخدام خاصيةdb_index=True
في الحقل.
5. تجنب التصفية على حقول نصية غير مفهرسة بكثرة
-
عمليات البحث باستخدام
__contains
أو__icontains
قد تكون بطيئة إذا كانت على حقول نصية كبيرة وغير مفهرسة. -
قد تحتاج لاستخدام حلول بحث متخصصة مثل Elasticsearch أو PostgreSQL Full-Text Search.
6. الحذر مع القوائم الكبيرة في __in
-
تمرير قوائم طويلة جدًا إلى
field__in
قد يؤدي إلى استعلامات ثقيلة على قاعدة البيانات. -
يمكن تقسيم القوائم أو إعادة التفكير في الاستراتيجية عند الحاجة.
7. تحليل استعلامات SQL الصادرة
-
استخدم أدوات مثل Django Debug Toolbar أو تشغيل الاستعلامات يدويًا لتحليل أداء استعلامات
filter()
.
باتباع هذه النصائح، يمكنك استخدام filter()
بكفاءة عالية في تطبيقات Django، مع ضمان أداء جيد حتى مع قواعد بيانات كبيرة ومعقدة.
خاتمة
دالة filter()
في Django ORM تمثل حجر الزاوية في التعامل مع قواعد البيانات داخل إطار العمل. توفر هذه الدالة وسيلة مرنة وقوية لاسترجاع البيانات وفق شروط متعددة ومتنوعة، من مقارنات بسيطة إلى شروط معقدة تشمل العلاقات والمنطقية.
باستخدام filter()
يمكن للمطورين:
-
كتابة استعلامات واضحة وسهلة القراءة، بعيدًا عن تعقيدات SQL التقليدية.
-
التصفية عبر العلاقات بين الجداول بكل بساطة وفعالية.
-
بناء شروط منطقية باستخدام
Q
objects لتنفيذ استعلامات معقدة. -
التعامل مع القيم الفارغة، قوائم القيم، وتواريخ وأوقات بطريقة مريحة.
-
دمج التصفية مع ترتيب النتائج والاستعلامات المركبة لتعزيز التحكم في البيانات.
-
تحسين الأداء عن طريق تنفيذ استعلامات متكاملة واستخدام الفهارس وأدوات Django المساعدة.
مع فهم عميق لكيفية عمل filter()
والاستفادة من خصائصها المتعددة، يمكن بناء تطبيقات Django أكثر قوة ومرونة، مع تحسين تجربة المستخدم من خلال تقديم بيانات دقيقة وسريعة الاسترجاع.