flask_cms استعمال التابع filter في مكتبة SQLAlchemy بجانب بقية التوابع والتعامل مع التواريخ


عبدالهادي الديوري

بعد أن تعرفنا على كيفيّة استعمال التّابع filter في مكتبة SQLAlchemy لترشيح السجلات عند الحصول عليها من قاعدة البيانات باستعمال شروط منطقيّة متقدمة في الدّرس السّابق، سنستمر في استكشاف ميّزات SQLAlchemy لبناء تطبيقات ويب أفضل، إذ سنتعرف في هذا الدّرس على كيفيّة استعمال التابع filter مع بقيّة التّوابع والدّوال المتوفّرة في المكتبة، إضافة إلى مقدمة بسيطة إلى كيفيّة التّعامل مع التّاريخ والوقت في بايثون للحصول على سجلّات من قاعدة البيانات حسب وقت وتاريخ محددين.

مزج التابع filter ببقية التوابع، والدوال التي توفرها مكتبة SQLAlchemy

يمكن مزج التّابع filter ببقيّة التّوابع، كما في حالة مزجه مع التّابع limit لتحديد عدد معيّن من النّتائج بعد ترشيحها وفق شرط أو شروط معيّنة، أو حتى مزج filter مع filter_by إن أردت ذلك (لكن مزجهما لا فائدة منه).

لنحصل مثلًا على جميع المُستخدمين الذين تبدأ أسماءهم بالمقطع user ونحدّد عدد النّتائج في نتيجتين فقط:

>>> User.query.filter(User.name.startswith('user')).limit(2).all()

[<username: user4 | email: user4@example.com >,

<username: user5 | email: user5@example.com >]

كذلك يمكنك استعمال order_by بعد التابع filter لترتيب النّتائج التي تُحقّق الشروط المطروحة:

>>> User.query.filter(User.name.in_(['user4', 'khalid', 'abdelhadi'])).order_by(User.name).all()


[<username: abdelhadi | email: abdelhadi@example.com >,

<username: khalid | email: khalid@example.com >, 

<username: user4 | email: user4@example.com >]

وهذا مثال آخر على كيفيّة مزج كل من filter و order_by ثمّ limit في استعلام واحد:

>>> User.query.filter(User.name.in_(['user4', 'khalid', 'abdelhadi'])).order_by(User.name).limit(2).all()

[<username: abdelhadi | email: abdelhadi@example.com >,

<username: khalid | email: khalid@example.com >]

استعمال filter للحصول على نتائج حسب تاريخ إضافتها

قد يحتاج التّعامل مع التّاريخ والوقت في بايثون إلى درس مُستقل لما فيه من التّعقيد والحالات المُختلفة، ورغم أنّني سأحاول ذكر معظم الأشياء المهمّة في هذه الفقرة، إلّا أنّني لن أذكر كل شيء، بل أهيب بك أن تجد درسا يشرح هذا الجزء بتفصيل أكبر.

الهدف

هدفنا من هذه الفقرة هو تقديم فكرة عن كيفيّة التّعامل مع السّجلات حسب تاريخ إضافتها (أي قيمة العمود created_date)، فبما أنّنا نمتلك معلومة مهمّة عن السّجل وهو وقت إضافته فسنتمكّن من الحصول على سجلّات حسب يوم إضافتها والسّجلات التّي أضيفت أثناء مقطع زمني معيّن (مثلا بين 5 أيام و10 أيام الماضيّة)، وعندما تفهم أساسيّات التّعامل مع التّاريخ والوقت فستتمكّن من إضافة ميّزات أخرى إلى تطبيقاتك كتحديد تاريخ آخر تعديل وكذلك أرشفة السّجلات حسب تاريخ إضافتها والعديد من الأفكار الأخرى.

وحدة datetime

الوحدة datetime هي وحدة مبنيّة مُسبقا بلغة بايثون، ويكفي للوصول إليها أن تقوم باستيرادها مُباشرة. تحتوي هذه الوحدة على العديد من الدّوال المُساعدة والوحدات الأخرى داخلها، وسنهتم حاليا بكل من الوحدة البنت datetime والدّالة timedelta فقط، لذا سنقوم باستيرادهما كما يلي:

>>> from datetime import datetime, timedelta

هنا نستدعي كلا من datetime وtimedelta من الوحدة datetime وبالتّالي فبغض النّظر عن التّشابه في الاسم فإن الوحدة التّي نستدعي منها هي الأصل والوحدة datetime الأخرى مجرّد فرع من الوحدة الأصليّة.هذا يعني بأنّنا إن أردنا الوصول إلى الوحدة الفرعيّة datetime عند استدعاء الوحدة الأصليّة datetime مُباشرة فعلينا إخبار بايثون بالأمر صراحة.

بالمثال يتضح المقال:

>>> import datetime
>>> datetime
<module 'datetime' (built-in)>

>>> datetime.datetime
<type 'datetime.datetime'>

قارن الخرج بما يلي:

>>> from datetime import datetime, timedelta
>>> datetime
<type 'datetime.datetime'>

الاستنتاج هنا هو أنّ datetime هي الوحدة الأصلية وdatetime.datetime هي وحدة فرعيّة، وبينهما اختلاف واضح. والآن بعد أن حدّدنا الفرق بين كل من الوحدة datetime والوحدة البنت datetime، وبما أنّنا نستدعي هذه الأخيرة فسأشير إليها بـdatetime مُباشرة.

إليك تذكيرا بسطر الاستيراد:

>>> from datetime import datetime, timedelta

لنحدّد الآن الفرق بين كل من datetime وtimedelta، وأبسط طريقة لشرح كل واحدة هي كالآتي:

datetime تُمكّننا من التّعامل مع التاريخ (date) والوقت (time).timedelta تُحدّد الفرق بين مُدّتين زمنيّتين.`

هذا يعني أنّك إن أردت الحصول على مدّة زمنّية معيّنة (الوقت الحالي حسب توقيتك المحلي أو توقيت UTC مثلا) فسيتوجّب عليك استخدام datetime مع مُختلف التّوابع التّي تُوفّرها الوحدة. وإن أردت الحصول على مقدار الوقت بين مدّتين زمنيّتين فسيتوجّب عليك استخدام timedelta، أمّا لو أردت الحصول مثلا على الفرق بين الوقت الحالي والوقت قبل 3 أيام فستستعمل كلا من datetime وtimedelta .

سأشرح لك بعض الأمثلة على كيفيّة استخدام كل من datetime و timedelta للحصول على سجلات من قاعدة البيانات حسب تاريخ إضافتها وشروط أخرى بفضل التّابع filter الذي تُوفّره لنا مكتبة SQLAlchemy، وسنتعرّف في الفقرات التّاليّة على بضعة أساليب لترشيح السّجلات حسب النقطة الزمنيّة التي أضيفت فيها، وذلك عبر استخدام التّابع filter وخصائص الصّنف datetime.

الحصول على سجلات حسب سنة إضافتها

سنبدأ أولا بالتّعرف على كيفيّة الحصول على السجلات التي أضيفت في سنة مُعيّنة، كالمستخدمين الذين سجّلوا في الموقع في سنة 2018 أو المقالات التي أضيفت في سنة 2017 وغير ذلك من الإمكانيّات الأخرى التي يُمكنك التفكير فيها عند إنشاء تطبيقات تعتمد على ترتيب المحتوى حسب السنوات أو عرض بيانات خاصّة بسنة مُعيّنة.

للحصول مثلا على جميع المُستخدمين الذين سجّلوا في التّطبيق في سنة 2017، سنستعمل دالّة في SQLAlchemy باسم extract لاستخراج السنة من قيمة التاريخ لمُقارنتها مع السنة التي نرغب في الحصول على السجلات المرتبطة بها، أي أنّنا سنستعمل التّابع filter للحصول على السّجلات التي يحتوي تاريخ إضافتها على السّنة 2017، والمثال التّالي توضيح على ما سبق:

from sqlalchemy import extract
from project.models import User

users2017 = User.query.filter(extract('year', User.created_date) == 2017).all()

في الشّيفرة أعلاه، نستورد الدّالة extract أولًا من حزمة sqlalchemy (لاحظ أنّ هذه الحزمة مُتعلّقة بمكتبة SQLAlchemy وليس بإضافة Flask-SQLAlchemy)، ثمّ نقوم باستيراد الصّنف User، بعدها نُنشئ المُتغيّر users2017 الذي سيحمل قائمة تحتوي على جميع المُستخدمين الذين تمّت إضافتهم إلى قاعدة البيانات في أي وقت من سنة 2017.

الشّرط الذي نُمرّره للتّابع filter أعلاه هو كما يلي:

extract('year', User.created_date) == 2017

لاحظ بأنّ السّطر عبارة عن مُقارنة بسيطة إذ نُقارن القيمة النّاتجة عن استدعاء الدّالة extract مع تمرير سلسة نصيّة ’year’ كمعامل أول وUser.created_date كمُعامل ثانِِ مع العدد الصّحيح 2017، ما يعني بأنّك لو أردت الحصول على المُستخدمين الذين أُضيفوا في سنة 2018 فكل ما عليك فعله هو تغيير 2017 بـ 2018 كما يلي:

extract('year', User.created_date) == 2018

الحصول على سجلات أضيفت في شهر معين

نتّبع نفس المنهج عند الرّغبة في الحصول على سجلّات أُضيفت في شهر مُعيّن من السّنة، علينا فقط استخراج الشّهر (month) من التاريخ عوضا عن السّنة، فللحصول على السّجلات التّي أضيفت في الشّهر الأول من كلّ سنة سنستعمل الشّرط التّالي:

extract('month', User.created_date) == 1

ما يعني بأنّ الشيفرة الكليّة ستُصبح كما يلي:

from sqlalchemy import extract
from project.models import User

jan_users = User.query.filter(extract('month', User.created_date) == 1).all()

في هذه الحالة، سيحمل المُتغيّر jan_users قائمة بجميع المُستخدمين الذين سجّلوا في الشّهر الأول. لاحظ أنّنا لم نُحدّد السّنة هنا، ما يعني بأنّ النتيجة ستكون عبارة عن المُستخدمين الذين سجّلوا في الشّهر الأول من أي سنة كيفما كانت.

الحصول على سجلات أضيفت في يوم معين من الشهر

للحصول على سجلّ حسب اليوم من الشّهر الذي تمّت إضافته فيه إلى قاعدة البيانات، فكلّ ما عليك فعله هو استخدام الخيار day عوضا عن year للسّنة أو month للشّهر.

مثال على كيفيّة الحصول على جميع المُستخدمين الذين سجّلوا في اليوم الأول من الشّهر:

from sqlalchemy import extract
from project.models import User

users = User.query.filter(extract('day', User.created_date) == 1).all()

الشّهر والسّنة لا يُهمّان هنا، المُهمّ أن يُضاف السّجل في اليوم الأول من أي شهر كان بغضّ النظر عن السّنة.

الحصول على سجلات أضيفت في تاريخ معين (سنة، شهر، يوم)

يُمكن جمع المعلومات السّابقة والدّالَّة db.and_ التي تعرّفنا عليها في درس سابق للحصول على سجلّات من قاعدة البيانات حسب تاريخ مُحدّد، أي في يوم وشهر مُحدّدين وسنة مُعيّنة. للحصول مثلا على جميع المُستخدمين الذين سجّلوا في اليوم الأول من الشّهر الأول من سنة 2018:

from sqlalchemy import extract
from project.models import User
from project import db

users2018 = extract('year', User.created_date) == 2018
first_jan_users = db.and_(extract('month', User.created_date) == 1,
                          extract('day', User.created_date) == 1
                         )

users = User.query.filter(db.and_(users2018, first_jan_users)).all()

هنا يحمل المُتغيّر users2018 شرطا يحدّد المُستخدمين الذين تمّت إضافتهم إلى قاعدة البيانات في سنة 2018، والمُتغيّر first_jan_users يحمل شرطا يُحدّد بأن على المستخدم أن يُسجَّل في الشهر الأول وكذا في أول يوم، ما يعني بأنّنا جمعنا شرطين في شرط واحد.

بعدها نستعمل الشّرطين users2018 وfirst_jan_users للحصول على المستخدمين الذين يُحقّقون كلا الشّرطين في آن واحد، أي أن يُسجّلوا في سنة 2018 واليوم الأول من الشّهر الأول.

يُمكنك تغيير القيم كيفما تشاء، وكذلك استعمال خصائص الوقت (السّاعة والدّقيقة على سبيل المثال) للحصول على سجلات حسب مُددِِ زمنيّة أكثر دقّة، وهذا ما سنتعرّف عليه في الفقرات التّاليّة من هذا الدّرس.

الحصول على سجلات أضيفت في ساعة معينة من اليوم

سنتّبع نفس المنهج الذي كنّا نستعمله عند التّعامل مع السنوات والأشهر والأيام للحصول على سجلّات حسب السّاعة من اليوم، أي الدّالة extract، وسنستخرج القيمة hour للعمل مع ساعات اليوم كما يلي (مع استبدال HOUR بالساعة في النظام الأربع والعشرينيّ 24h):

extract('hour', User.created_date) == HOUR

إن أردنا الحصول على جميع المُستخدمين الذين سجّلوا في الخامسة مساءً:

from sqlalchemy import extract
from project.models import User

five_pm_users = extract('hour', User.created_date) == 17

users = User.query.filter(five_pm_users).all()

الحصول على سجلات أضيفت في دقيقة معينة

للحصول على سجلّات حسب الدقيقة التي أُضيفت فيها، استعمل minute مع الدّالة extract. وللحصول على المُستخدمين الذين سجّلوا في الدّقيقة الأولى من كلّ ساعة:

from sqlalchemy import extract
from project.models import User

first_minute_users = extract('minute', User.created_date) == 1

users = User.query.filter(first_minute_users).all()

الحصول على سجلات حسب وقت وتاريخ إضافتها

لنجمع جميع المعلومات أعلاه للحصول على سجلّات حسب كلّ من تاريخ إضافتها (سنة، شهر، يوم) ووقت إضافتها (السّاعة والدّقيقة)، نحصل في المثال التّالي على أي مُستخدم سجّل في اليوم الأول من الشّهر الأول من سنة 2018 في الدّقيقة الأولى من السّاعة الخامسة مساءً:

from sqlalchemy import extract
from project.models import User
from project import db

users2018 = extract('year', User.created_date) == 2018
first_jan_users = db.and_(extract('month', User.created_date) == 1,
                          extract('day', User.created_date) == 1
                         )
five_pm_first_minute_users = db.and_(extract('hour', User.created_date) == 17,
                                     extract('minute', User.created_date) == 1
                                    )
first_jan_five_pm_first_minute = db.and_(first_jan_users,
                                         five_pm_first_minute_users
                                        )

users = User.query.filter(db.and_(users2018,
                                  first_jan_five_pm_first_minute
                                 )
                         ).all()

لاحظ الشّروط التّي استخدمناها:

  • users2018: المستخدمون الذين سجّلوا في سنة 2018
  • first_jan_users: المستخدمون الذين سجّلوا في اليوم الأول من الشّهر الأول
  • five_pm_first_minute_users: المستخدمون الذين سجّلوا في الدّقيقة الأولى من السّاعة الخامسة
  • first_jan_five_pm_first_minute: المستخدمون الذين سجّلوا في الدّقيقة الأولى من السّاعة الخامسة في 1 يناير.

يُمكن كذلك مزج الشّروط الواحد داخل الآخر دون حفظها في مُتغيّرات خاصّة، لكنّ يفضل تسجيل الشّروط في مُتغيّرات لتسهيل قراءة الشّيفرة.

التعامل مع الفروقات الزمنية

يُمكن استعمال الدّالة timedelta من وحدة datetime للتّعامل مع الفروقات الزّمنية، إذ تُرجع timedelta فرقا زمنيّا بين نقطتين زمنيّتين مُحدّدتين بمقدار زمنيّ مُعيّن. وللحصول على فرق زمني مقدار يوم واحد، سنُمرّر عدد الأيام (1 في هذه الحالة) إلى المُعامل days كما يلي:

>>> from datetime import timedelta
>>> one_day = timedelta(days=1)
>>> one_day
datetime.timedelta(1)

للحصول على فرق زمني مقداره 30 ثانيّة سنُمرّر القيمة إلى المُعامل seconds:

>>> thirty_seconds = timedelta(seconds=30)
>>> thirty_seconds
datetime.timedelta(0, 30)

للحصول على فرق زمني مقداره يوم وثلاثون ثانيّة:

>>> timedelta(days=1, seconds=30)
datetime.timedelta(1, 30)

يُمكن كذلك الحصول على الفرق الزمني بين قيمتي datetime مُختلفتين، فللحصول على الفرق الزّمني بين سنتي 2017 و2018 مثلًا:

>>> from datetime import datetime, timedelta
>>> date1 = datetime(year=2017, month=1, day=1)
>>> date2 = datetime(year=2018, month=1, day=1)
>>> print(date1)
2017-01-01 00:00:00
>>> print(date2)
2018-01-01 00:00:00
>>> delta = date2 - date1
>>> delta
datetime.timedelta(365)
>>> print(delta)
365 days, 0:00:00

لاحظ بأنّ كل ما قمنا به هو عمليّة فرق بسيطة date2 – date1 وأسندنا القيمة إلى المتغيّر delta، والذي يُعطينا القيمة 365 days, 0:00:00 عند طباعته، أي أن الفرق الزمني بين السّنتين هو 365 يوما، صفر ساعة، صفر دقيقة وصفر ثانيّة.

نستطيع استعمال قيمة الفرق الزمني في SQLAlchemy للحصول على سجلّات بشروط مثل أن تكون قد سُجّلت قبل شهر واحد، قبل سنة، ما بين الشّهر والشّهرين، إلى غير ذلك.

يمكنّنا المثال التّالي من الحصول على جميع المُستخدمين الذين سجّلوا بعد أسبوعين مُنصرمين وقبل أسبوع واحد من الآن:

from datetime import datetime, timedelta

from project import db
from project.models import User, Post

# Get Users who signed up between
# two weeks and one week ago


datetime_now = datetime.utcnow()

seven_days = timedelta(days=7)
one_week_ago = datetime_now - seven_days

fifteen_days = timedelta(days=15)
two_weeks_ago = datetime_now - fifteen_days

users = User.query.filter(
        db.and_(User.created_date >= two_weeks_ago,
                User.created_date <= one_week_ago)
        ).all()

خاتمة

تعرّفنا في هذا الدّرس على كيفيّة استعمال التّابع filter لإجراء عمليّات متقدّمة، ومزجه ببقيّة التوابع المتوفرة في SQLAlchemy، كما ألقينا نظرة على خصائص التّاريخ والوقت وكيفيّة الاعتماد عليها للحصول على سجلّات من قاعدة البيانات حسب شروط زمنيّة مُعيّنة.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن