(5) ورشة تطوير تطبيقات الويب باستخدام Django: التعامل مع Django ORM


#1

أهلا بالجميع

فهرس موضوعات الورشة

فهرس موضوعات ورشة تطوير تطبيقات الويب باستخدام Django

في الدرس السابق تعلمنا كيف ننشئ app جديد ونضيف model له في هذا الدرس سنتعلم كيف نتلاعب بال objects الموجودة في قاعدة البيانات.

ماهو (Object-relational mapper) Django ORM؟

جانقو يأتي معها orm افتراضي يمكننا من التفاعل مع قاعدة بيانات علائقية مثل SQLite, MySQL, PostgreSQL حيث يمكننا انشاء, ارجاع , تعديل , حذف object.

كيف يمكنني اختبار ال models ؟

عند عمل model جديد يجب عليك اختباره هل هو يعمل بشكل جيد او لا في هذه الحاله يمكننا استعمال ال shell الذي يأتي مع django وهو في الأصل python shell مهيء لمشروعك.

للدخول على ال shell نستعمل هذا الأمر

python manage.py shell

كيف يمكنني اضافة بيانات الى ال db من ال shell

اكتب هذه الأوامر في ال shell

>>> from shop.models import Category, Product
>>> 
>>> c1 = Category(
...         name="Mobile Devices",
...         slug="mobile-devices",
...         description="This category will contains mobile devices.")
>>> c1.save()
>>>
>>> p1 = Product(
...          name="Google Pixel 2",
...          price=649.00,
...          stock=5,
...          description="The unlocked Pixel 2 provides a clean, bloat-free experience with no unwanted apps, one of the highest rated smartphone cameras, with free unlimited storage.",
...          slug="google-pixel-2",
...          category=c1)
>>> p1.save()
>>>
>>> p2 = Product(
...           name="Sony Xperia XZ2",
...           price=778.20,
...           stock=3,
...           description="The Xperia XZ2 is packed with the latest Sony technologies to deliver an entertainment experience that touches your senses in a whole new way – whether you're lost in a HDR movie or capturing hidden details with the new advanced Motion Eye™ camera.",
...           slug="sony-xperia-xz2",
...           category=c1)
>>> p2.save()
>>> 
>>> p3 = Product(
...           name="Apple iPhone X",
...           price=1149.99,
...           stock=4,
...           description="iPhone X features a new all-screen design. Face ID, which makes your face your password. And the most powerful and smartest chip ever in a smartphone.",
...           slug="apple-iphone-x",
...           category=c1)
>>> p3.save()

هذه الأوامر تضيف بيانات لقاعدة البيانات دعنا نلقي نظرة على ماذا يحدث هنا

  • في البداية قمنا بعمل import لل Product و Category حتى يمكننا التلاعب بهم من ال shell.

  • قمنا بإنشاء Product و Category و إعطاء قيم للحقول.

لاحظ عند انشاء object جديدة نسند قيم للحقول عل شكل keyword args.

  • ,وفي الأخير لحفظ الكائن في قاعدة البيانات يجب علينا استدعاء الدالة save()

هذه الطريقة مملة بعض الشيء كيف يمكني انشاء الكائن في خطوة واحدة ؟

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

c1 = Category.objects.create(name="Mobile Devices", slug="mobile-devices", description="This category will contains mobile devices.")

و

c1 = Category(name="Mobile Devices", slug="mobile-devices", description="This category will contains mobile devices.")
c1.save()

يقومان بعمل نفس الشيء

كيف يمكنني الوصول الى الحقول الخاصة بالكائن؟

للوصول الى اي حقل نستعمل ال Dot notation بهذه الطريقة

<object>.<name-of-field>

مثال لمعرفة كم لدينا قطعة من Google Pixel 2 في المخزن

>>> p1.stock
5

هل يمكن القيام بإرجاع بيانات من قاعدة البيانات بواسطة Django ؟

بالتأكيد نعم , جانقو لديها api غني جدااا يمكنك عمل الكثير من الأشياء به بدون كتابة كود SQL صافي. الآن سنتعلم الدوال التالية all() و get() و filter() و exclude().

ماهي all()؟

تقوم هذه الدالة بارجاع كل الكائنات لل modle لنأخذ مثال

>>> Product.objects.all()
<QuerySet [<Product: Apple iPhone X>, <Product: Google Pixel 2>, <Product: Sony Xperia XZ2>]>
>>> 

لاحظ ان هذه الدالة قامت بارجاع QuerySet بها كل المنتجات.

ماهو QuerySet ؟

ال QuerySet هو قائمة من الكائنات التي يمكننا التلاعب بها تستطيع القيام بأي شي تقوم به في ال list من ارجاع عنصر بال index الخاص به الى ارجاع QuerySet مجزئة من الأولى بواسطة ال slicing.

ماهي get()؟

هذه الدالة ترجع عنصر واحد واذا حدث واعطيتها lookup يرجع اكثر من عنصر فا سترجع خطأ MultipleObjectsReturned واذا ال lookup لايرجع اي شيء في سترجع خطأ DoesNotExist.

لنأخذ مثال على get()

>>> Product.objects.get(id=1)
<Product: Google Pixel 2>
>>>
>>> Product.objects.get(slug__icontains="x")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/ahmedalrifai/Documents/Playground/python-playground/coretabs-django-tutorial/venv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ahmedalrifai/Documents/Playground/python-playground/coretabs-django-tutorial/venv/lib/python3.6/site-packages/django/db/models/query.py", line 407, in get
    (self.model._meta.object_name, num)
shop.models.MultipleObjectsReturned: get() returned more than one Product -- it returned 3!
>>>
>>> Product.objects.get(id=55)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/ahmedalrifai/Documents/Playground/python-playground/coretabs-django-tutorial/venv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ahmedalrifai/Documents/Playground/python-playground/coretabs-django-tutorial/venv/lib/python3.6/site-packages/django/db/models/query.py", line 403, in get
    self.model._meta.object_name
shop.models.DoesNotExist: Product matching query does not exist.
>>> 
  • في المثال الأول اعطينا الدالة id موجود فا قامت بترجيع العنصر المطلوب.

  • في المثال الثاني اعطيناها lookup يرجع اكثر من عنصر فا ما قامت به get() هو ارجاع خطأ.

  • في المثال الثالث اعطيناها id غير موجود فا قامت بإرجاع خطأ

ماهو ال loockup؟

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

صيغة ال lookup

<field>__<lookup-type>=<value>

لاحظ ان بين field و lookup-type علامتين شرطة سفلية

المعاملات الشائع استخدامها

هنا سنقوم بشرح المعاملات الشائع استخدامها , لو تريد شرح المعاملات كلها يمكنك الرجوع الى مستندات جانقو الرسمية عليهم من هنا

المعاملات exact و iexact

المعامل exact:

هذا المعامل سيجلب الكائنات التي field يطابق تماما lookup type مع مراعاة حالة الأحرف .

مثال

>>> Product.objects.get(name__exact="Google Pixel 2")
<Product: Google Pixel 2>
>>>
>>> Product.objects.get(name__exact="google pixel 2")
#  This will raise "DoesNotExist" error

ملاحظة

في حال لم تقم بكتابة lookup type جانقو ستنفذ exact افتراضيا

>>> Product.objects.get(name="Google Pixel 2")
<Product: Google Pixel 2>

المعامل iexact:

ايضا في حال لا تريد مراعاة حالة الأحرف يمكنك استعمال iexact

مثال

>>> Product.objects.get(name__iexact="Google Pixel 2")
<Product: Google Pixel 2>
>>>
>>> Product.objects.get(name__iexact="google pixel 2")
<Product: Google Pixel 2>

المعامل contains

هذا المعامل سيجلب الكائنات التي الحقل المعطى في ال lookup يحتوي على القيمة المعطى مع مراعاة حالة الأحرف

>>> Product.objects.filter(description__contains="you")
<QuerySet [<Product: Apple iPhone X>, <Product: Sony Xperia XZ2>]>

هل هناك طريقة لعرض جميع المعاملات في ال shell ؟

بالتأكيد نعم , يمكنك عرض المعاملات على شكل set فقط قم بكتابة هذا الكود

>>> from django.db.models.sql.constants import QUERY_TERMS
>>> QUERY_TERMS
{'search', 'icontains', 'iexact', 'contains', 'range', 'istartswith', 'iregex', 'day', 'second', 'exact', 'year', 'week_day', 'minute', 'gte', 'iendswith', 'regex', 'startswith', 'in', 'gt', 'lt', 'lte', 'isnull', 'month', 'hour', 'endswith'}

ماهي filter()؟

هذه الدال ترجع QuerySet تحتوي على الكائنات المطابقة لل lookup المعطى, لنأخذ مثااال على هذه الدالة.

>>> Product.objects.filter(description__contains="The")
<QuerySet [<Product: Apple iPhone X>, <Product: Google Pixel 2>, <Product: Sony Xperia XZ2>]>
>>> 
>>> Product.objects.filter(stock__gte=4)
<QuerySet [<Product: Apple iPhone X>, <Product: Google Pixel 2>]>
>>>
>>> import datetime
>>> Product.objects.filter(created_at__date__gte=datetime.date(2018, 1, 1))
<QuerySet [<Product: Apple iPhone X>, <Product: Google Pixel 2>, <Product: Sony Xperia XZ2>]>
>>>
>>> Product.objects.filter(created_at__date=datetime.date(2018, 1, 1))
<QuerySet []>
  • المثال الأول قمنا بإرجاع الكائنات التي ال description الخاص بها يحتوي على النص “The”

  • المثال الثاني قمنا بإرجاع الكائنات التي stock الخاص بها أكبر من أو يساوي 4

  • المثال الثالث قمنا بعمل import لل datetime ثم قمنا بارجاع الكائنات التي created_at اكبر من التاريخ 2018 / 1 / 1

  • المثال الرابع قمنا بعمل lookup تقوم بارجاع الكئنات التي الحقل created_at الخاص بها يساوي 2018 / 1 / 1 ولكن في حالتنا لم ترجع اي كائن لأنه ليس لدينا كائن بهذا التاريخ

ماهي exclude()؟

هذه الدالة هي عكس filter() فاهي تقوم بارجاع QuerySet بالكائنات التي لا تنطبق مع ال lookup المعطى.

لنأخذ الأمثلة السابقة ونغير filter ب exclude

>>> Product.objects.exclude(description__contains="The")
<QuerySet []>
>>> 
>>> Product.objects.exclude(stock__gte=4)
<QuerySet [<Product: Sony Xperia XZ2>]>
>>>
>>> import datetime
>>> Product.objects.exclude(created_at__date__gte=datetime.date(2018, 1, 1))
<QuerySet []>
>>>
>>> Product.objects.exclude(created_at__date=datetime.date(2018, 1, 1))
<QuerySet [<Product: Apple iPhone X>, <Product: Google Pixel 2>, <Product: Sony Xperia XZ2>]>

كيف يمكنني القيام بعمل تعديل على الكائن؟

جانقو لديها طريقتين للتعديل

  1. التعديل على كائن واحد
  2. التعديل على مجموعة كائنات

التعديل على كائن واحد

هذه الطريقة هي شبيهة لإضافة كائن جديد الإختلاف ان جانقو ستعرف بأن هذا الكود يريد ان يعمل UPDATE وليس INSERT لنأخذ مثال

>>> p2
<Product: Google Pixel 2>
>>> p2.stock
5
>>> p2.stock -= 1
>>> p2.stock
4
>>> p2.save()

في هذا المثال قمنا بإنقاص 1 من الحقل stock ثم استدعينا الدالة save().

انتبه

بعد انتهائك من تعديل الحقول يجب عليك ان تستدعي الدالة save() اذا لم تستدعيها لن يتم حقظ تعديلاتك في قاعدة البيانات.

كيف تعرف جانقو ان العملية المطلوبة هي UPDATE أو INSERT ؟

اذا كان المفتاح الأساسي للكائن يحتوي على قيمة تساوي True (قيمة ليست None أو نص فارغ) جانقو ستقوم بتنفيذ العملية UPDATE , اذا كان المفتاح الأساسي للكائن لم تسند اليه قيمة اوعملية ال UPDATE لم تعدل على شيء , جانقو ستنفذ العملية INSERT.

المصدر

التعديل على مجموعة من الكائنات

لنأخذ مثال لاداعي للشرح العنوان يشرح نفسه :smile:

>>> Product.objects.all().update(stock=0)
3

في هذا المثال قمنا بتعديل ال stock وجعل قيمته 0 لكل المنتجات, لاحظ ان القيمة المرجعه 3 وهي تعني الكائنات التي تم التعديل عليها

ملاحظة

الدالة update() معرفة في QuerySet يعني يمكنك استخدامها مع حتى filter() و exclude() و اي دالة ترجع QuerySet وليس all() فقط.

تحذير

يجب ان تحذر من استعمال update() لأنها ستقوم بتعديل القيمة نهائيا ولن يمكنك الرجوع عن ذلك

كيف يمكنني حذف كائن؟

الدالة delete() تقوم بحذف الكائن مباشرة وترجع tuple يحتوي على عنصرين. العنصر الأول هو عدد الكائنات التي تم حذفها والعنصر الثاني هو dictionary ب عدد عمليات الحذف في كل نوع من الكائنات

>>> p1
<Product: Apple iPhone X>
>>> p1.delete()
(1, {'shop.Product': 1})

هنا قمنا بحذف الكائن p1 لاحظ القيمة التي تم ارجاعها مثل الشرح الذي بالأعلى.

تستطيع حذف اكثر من كائن فا ال QuerySet تحتوي على دالة delete() تحذف جميع عناصرها.

مثال

>>> Product.objects.filter(stock=0).delete()
(2, {'shop.Product': 2})

هنا قمنا بحذف جميع الكائنات التي الحقل stock الخاص بها قيمته 0.

اريد ان اعدل على طريقة حفظ الكائن , كيف يمكنني فعلها؟

في هذه الحالة يمكنك override save method

حسنا كيف يمكنني فعلها؟ :thinking:

اضف هذا الكود في ال model الخاصة بك

def save(self, *args, **kwargs):
    
    # Your Code Here

    super().save(*args, **kwargs)

لنقم بعمل مثال نستفيد منه في هذا المشروع , لاحظ اننا عند انشاء Product أو Category نقوم باسناد قيمة حتى لل slug لكن نحن لا نريد هذا مانريده هو ان يتم توليد ال slug من ال name يمكننا فعلها بالتعديل على الدالة save() والاستعانة بالدالة slugify().

ماهي الدالة slugify() ؟

كما قلنا جانقو تختصر الأشياء التي يفعلها المبرمجون بكثرة فاقامو بعمل الدالة slugify() لكي ترتاح انت المبرمج من عملها بنفسك :smile:. ماتقوم به هذا الدالة هو تحويل النص المعطى كابارامتر الى slug يعني ستجعل الأحرف small وتبدل المسافات ب dash “-” واشياء اخرى.

كيف يمكنني اخذ ال slug من ال name في Product و Category؟

اضف هذا الكود الى ال Product و Category

def save(self, *args, **kwargs):
    
    if not self.id:
        self.slug = slugify(self.name)

    super().save(*args, **kwargs)

لاتنسى ان تقوم بعمل import للدالة slugfiy() في بداية الملف بهذا الشكل:

from django.utils.text import slugify

لنقم بتفصيل الكود

  • if not self.id:

هذا السطر يتحقق من ان ال id هو قيمة تساوي True اي ليس None او 0 يعني سينفذ الكود عندما يتم انشاء كائن جديد وليس حتى عند التعديل

  • self.slug = slugify(self.name)

هذا السطر يسند القيمة المرجوعة من الدالة slugify الى الحقل slug لاحظ ان بارامتر الدالة هوا الحقل name

  • super().save(*args, **kwargs)

هذا السطر سيستدعي الدالة save الإفتراضية

الأن لاداعي لإسناد قيمة لحقل ال slug عند انشاء Product أو Categorey سيتم انشائها تلقائيا عند حفظ ال object

ما هي المهمة المطلوبة؟

Categories

name
Mobile Devices
Computers

Products

name price stock description categorey
Apple iPhone X 1100.00 12 iPhone X features a new all-screen design. Face ID, which makes your face your password. And the most powerful and smartest chip ever in a smartphone. Mobile Devices
Google Pixel 2 860.20 14 The unlocked Pixel 2 provides a clean, bloat-free experience with no unwanted apps, one of the highest rated smartphone cameras, with free unlimited storage. Mobile Devices
Sony Xperia ZX2 920.49 9 The Xperia XZ2 is packed with the latest Sony technologies to deliver an entertainment experience that touches your senses in a whole new way – whether you’re lost in a HDR movie or capturing hidden details with the new advanced Motion Eye™ camera. Mobile Devices
Dell Inspiron 17 5000 799.99 11 17-inch laptop with an anti-glare, backlit display. Add options like an FHD screen with discrete graphics to create a PC that reflects what matters to you. Computers
MacBook Pro 2,999.00 6 It’s razor thin, feather light, and even faster and more powerful than before. It has the brightest, most colorful Mac notebook display ever. And it features the Touch Bar — a Multi-Touch enabled strip of glass built into the keyboard for instant access to the tools you want, right when you want them. MacBook Pro is built on groundbreaking ideas. And it’s ready for yours. Computers

  • قم باضافة الدالة save() كما فعلنا في الدرس

  • قم بإنشاء هذه الجداول من ال shell

  • قم بارجاع كل ال Product

  • قم بجلب Product ذو ال id = 0

  • قم بجلب كل ال Product التي لديها stock اكبر من 10

  • قم بتعديل وصف كل ال Product التي لديها stock اقل من 10 الى “Will be deleted”

  • قم بحذف كل ال Product التي وصفها يساوي “Will be deleted”

  • قم بعرض كل ال Product التي لا تحقق هذا الشرط price__gte=900

الخاتمة

تعرفنا في هذا الدرس على بعض ميزات Django ORM طبعا تعرفنا على الدوال المستخدمة بكثرة هناك الكثير من الدوال الأخرى يمكنك القراءة عنها من مستندات جانقو الرسمية تجدها هنا

ملحق

Field Lookup

Django ORM and QuerySets

URL Slugs: What They Are and How to Use Them Effectively

تسليم الحلول

(6) ورشة تطوير تطبيقات الويب باستخدام Django: مشاركة حلول التعامل مع Django ORM


#2

مشكور على المعلومات الوافية :+1: اخ احمد @ahmedalrifai , لدي استفسار :


#3

لا تعدل على شيء هذا خطأ مني انا سأعدله الآن شكرا على تنبيهك :smile:


#4

جزاك الله خيرا علي الشرح , هل اضافة خاصية جديدة “description” امر صعب ام انه من غير المفضل اضافة خواص جديدة بعد عمل ال migration ؟


#5

في وقت ال development لابأس بإضافة حقول جديدة لكن في ال production يجب ان يكون كل شيء جاهز ومفكر في كل الحقول التي تريدها
طبعا عند قيامك بعمل مشروع الأفضل ان تقوم بعمل رسم تخطيطي لهيكلية قاعدة البيانات بكل الجداول المرتبطة مع بعض والحقول التي تحتويها


#6

عند تنفيذ الاوامر في اول الدرس اعطتني نتائج ولكن ليست كما هي معروضة في الشرح كالتالي :

p2.name
‘Sony Xperia XZ2’

p2
<Product: Product object (2)>

Product.objects.all()
<QuerySet [<Product: Product object (3)>, <Product: Product object (1)>, <Product: Product object (2)>]>

Product.objects.get(id=1)
<Product: Product object (1)>

Product.objects.get(name = “Google pixel 2”)
<Product: Product object (1)>

Product.objects.filter(description__contains=“you”)
<QuerySet [<Product: Product object (3)>, <Product: Product object (2)>]>


#7

اخ احمد , بصراحة المطلوب من السؤال الثالث الى الاخير لم افهمه


#8

هذا السطر غير مفهوم للتحقق من ان قيمة Self.id ساوي True نكتب:
if self.id:
or
if self.id is not None:
برجاء التوضيح و شكرا


#9

المطلوب هو الحصول جميع الـ products من قاعدة البيانات… كيف ستقوم بذلك ؟


#10

أعتقد أنك ستفهم الفرق من هنا:

>>> x = 0
>>> y = None

>>> x is not None
True
>>> y is not None
False

>>> if x is not None:
	print("Hello")
Hello

>>> if y is not None:
	print("Hello")

>>> if x:
	print("Hello")
	
>>> if y:
	print("Hello")

#11

يعني يا استاذ @YaserAlnajjar اقوم يالتلاعب بقاعدة البيانات لوحدي …فقط لاطبق ما فعله احمد وارى عمل كل دالة؟
الن اقوم بشيء يجب ان يلاحظ عند تسليم العمل


#12

ما طلب منا القيام بفعله من عمليات مثل اضافة اصناف ومنتجات والتعديل على الوصف وغيرها ,يمكن ملاحظته في قاعدة البيانات التي سترفع على الجيت عند تسليم الحل . :+1:


#13

اشكرك استاذ ياسر , لقد استوعبت الامثلة التي وضعتها ولكن المكتوب في الدالة اننا نريد ان نتحقق ان self.id لها قيمة
عند وجود قيمة فعليه ل self.id ستعطيني True وبالتالي عند كتابة not قبلها لن يتحقق الشرط و لن يتم تنفيذ السطر التالي ل if هل هذا هو المقصود


#14

قم بعمل الدالة __str__لعرض نص مقروء للكائن


#15

بعد انشاء المنتجات المطلوبة اريدك ان تقوم بارجاع كل المنتجات التي في قاعدة البيانات راجع الدرس لتعرف ماهي الدالة التي تقوم بهذا العمل :slight_smile:


#16

قمنا بعمل هذا if not self.id لكي نتحقق بأن الكود يعمل في وقت انشاء كائن جديد وليس حتى في وقت التعديل نحن لانريد ان نغير ال slug في كل مرة نقوم بها بتعديل الكائن


#17

نعم صحيح


#18

بالإضافة إلى ما قاله أحمد, لاحظ أن

if x:

لا تتحقق فقط من None وإنما من القيمة صفر (في نفس الوقت)…
يعني لازم x تكون ليست None وليست صفر, هذا ما أردت توضيحه بالمثال في الأعلى :smile:


#19

اخوتي @ahmedalrifai @YaserAlnajjar جزاكم الله خيرا علي ردودكم ولكن اعذروني ما زالت هناك نقطة غير مفهومة ربما لم اصغ سؤالي جيدا , ما فهمته ان الدالة تتحقق من ان قيمة ال id ليست صفر او None و هذا واضح من الامثلة و ايضا فهمت ان المطلوب الا يتم تنفيذها عند التعديل حتي لا يتم اعادة كتابة ال slug عند حفظ كل تعديل وهذا تم استيعابه ايضا.
وحيث اني لا اعرف كيف و متي يضيف django ال id للمنتج اذا السؤال يصبح ما هي قيمة ال id عند الانشاء و التي ستجعل الدالة تعمل هل هي False و هي الحالة التي اظن انها ستجعل if تنتقل لكتابة ال slug ام انها True كما ذكرتم في الشرح و بالتالي اريد ان افهم كيف سيتم تنفيذ السطر التالي في هذه الحالة.

if

شكرا علي صبركم


#20

أنا حاليا على الهاتف، لما أدخل الى االيت سأحاول شرحها بالأمثلة