بناء تطبيق لاختصار الرّوابط باستعمال لغة بايثون وإطار العمل Flask - الجزء الأوّل


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

مُقدّمة:

بعد أن تعلّمنا في الدّروس السّابقة كيفيّة استخدام إطار العمل Flask الخاص بلغة بايثون لإنشاء نظام تدوين بسيط، سنكمل مغامرتنا مع هذا الإطار بالمضي قُدما وتعلّم بعض التّقنيات الأكثر تقدّما وسننشئ تطبيقا بسيطا لاختصار الرّوابط في طريقنا، يُمكنك مُعاينة التّطبيق الذي سنبنيه من هذا الرّابط.

flask-shortner-01.png

التّطبيق سيكون عبارة عن خدمة لاختصار الرّوابط، فمثلا عوض كتابة الرّابط على الشّكل:
https://mostaql.com/u/abdelhadidyouri
يُمكن اختصاره لما يلي:  bit.ly/OdrLKju
ومن المرجح أنّك صادفت هذا النوع من الرّوابط على مواقع التّواصل الاجتماعي. سنقوم في هذا الدّرس والدّرس الموالي بتبسيط فكرة اختصار الرّوابط وبرمجة تطبيق خدمي يُمكنك نشره على الأنترنت وربطه بنطاق من اختيارك وطرحه للجميع للاستفادة منه.

فكرة التّطبيق

مهمّة التّطبيق هي تحويل رابط طويل إلى رابط قصير ما يعني بأنّنا سنحتاج إلى حفظ الرّابط في قاعدة بيانات ومنحه مُعرّفا خاصّا به، في هذه الحالة سيكون رقم مُعرّف أو id الرّابط، فمثلا لنقل بأنّ المُستخدم أدخل الرّابط academy.hsoub.com إلى قاعدة البيانات عبر نموذج HTML في الصّفحة الرّئيسيّة، عندما يدخل الرّابط إلى قاعدة البيانات سيحمل رقم المُعرّف 1 والرّابط التّالي سيحمل رقم المعرّف 2 وهكذا دواليك.ما يعني بأنّ الرّابط عندما يدخل إلى قاعدة البيانات يصبح بإمكاننا استدعاؤه باستخدام رقم المُعرّف الخاصّ به، وبالتّالي يُصبح الرّابط الأول كالتّالي:

127.0.0.1:5000/1

عند ربطه باسم نطاق مخصّص سيصبح كالتّالي:

example.com/1

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

إعداد البيئة الوهميّة وتنصيب الحزم

سنقوم مُجدّدا باستخدام أداة virtualenv في هذا المشروع، أنشئ مجلّدا باسم url_shortener وقم بتنفيذ الأمر التّالي داخله:

$ virtualenv venv

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

$ . venv/bin/activate
$ pip install flask Flask-SQLAlchemy hashids

تشفير الأعداد الصّحيحة باستخدام مكتبة Hashids

رغم أنّ الطّريقة أعلاه تعمل بشكل جيّد وتؤدّي الغرض الذي نريده (اختصار الرّابط) لا يزال هناك مُشكلة سهولة التّنبؤ بالرّوابط التّي اختصرها المُستخدمون السّابقون، مثلا لو كان المُعرّف الذي حصلت عليه يحمل الرّقم 421 فمن السّهل عليك أن تحصل على جميع الرّوابط التّي كانت قد اختُصِرَت من قبل، وبكتابة اسم النّطاق مع رقم 420 ستحصل على الرّابط الذي اختصره المُستخدم الذي سبقك.
لحل هذه المُشكلة يُمكننا تشفير الأعداد وتحويلها إلى أحرف عشوائيّة مثلا العدد 1 سيكون JshO والعدد 2 سيكون QhIk ما يعني بأنّه من المُستحيل التنبؤ بالرّوابط التي اختُصِرَت مُسبقا.
سنستخدم في هذا الدّرس مكتبة Hashids لتحويل الأعداد إلى سلسلة نصيّة عشوائيّة. ويُمكن استخدامها كالتّالي:

from hashids import Hashids
hashids = Hashids(salt='SALT KEY',min_length=4)

hashid = hashids.encode(123) # 'Y975'

number = hashids.decode('Y975') # (123,)

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

كما ترى نستخدم التّابع encode لتشفير عدد ما، ونقوم بفكّ التّشفير عن طريق التّابع decode.
لاحظ بأنّ فك التّشفير يرجع مجموعة بداخلها العدد كأول عنصر، ما يعني بأنّك يجب أن تقوم بالوصول إليه لاستخدامه في التّطبيق كما يلي:

number = hashids.decode('Y975') # (123,)

ints = hashids.decode('Y975')[0] # 123

تجهيز قاعدة البيانات باستعمال SQLAlchemy

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

بدلا من استخدام جمل SQL للقيام بالأمر، سنعتمد على أداة SQLAlchemy للقيام بالعمليّات المُتعلّقة بقاعدة البيانات، الميّزة هنا هي أنّك تستطيع التّعامل مع قاعدة البيانات مُباشرة بلغة بايثون، والتّالي مثال على جملة SQL ومقابلتها المكتوبة بلغة بايثون بالاستعانة بـSQLAlchemy:

SELECT * FROM users

users = User.query.all()

يُوفّر لنا SQLAlchemy كذلك طريقة بسيطة للتّعامل مع مُختلف أنواع قواعد البيانات فبمجرّد تغيير سطر واحد يُمكنك الانتقال من SQLite إلى Postgresql أو Mysql والعكس، وهذا الأمر مُفيد في حالة كنت تمتلك أكثر من بيئة فبيئة التّطوير (على حاسوبك) يُمكن أن تستخدم SQLite أمّا في بيئة الإنتاج (على منصّة PythonAnywhere أو Heroku) فيُمكن استخدام MySQL أو Postgresql. كل هذا دون عناء التّعامل مع مكتبة خاصّة بكل واحدة منها، فأداة SQLAlchemy تقوم بالأشياء المعقّدة وراء الكواليس.

سنقوم الآن بإعداد قاعدة بيانات التّطبيق والتّي تتكون من جدول واحد للرّوابط باسم urls، يحتوي الجدول على عمودين عمود خاص برقم مُعرّف الرّابط والعمود الآخر يخصّ الرّابط بحد ذاته، ما يعني بأنّ الرّوابط ستُحفظ في قاعدة البيانات كالتّالي:

id | url
-------------------------------------------------------
1  |  http://academy.hsoub.com/
-------------------------------------------------------
2  |  https://mostaql.com/u/abdelhadidyouri
-------------------------------------------------------
3  |  https://academy.hsoub.com/programming/python/flask

كل ما عليك فعله هو فتح ملفّ باسم app.py وقم باستيراد المكتبات والأدوات التي سنحتاج إليها وقم بإعداد الاتّصال بقاعدة بيانات SQLite باسم app.db عبر نسخ الشّيفرة التّالية ولصقها في بداية الملفّ.

# Imports

from flask import Flask, redirect, request, render_template
from flask_sqlalchemy import SQLAlchemy
from hashids import Hashids

# Config

app = Flask(__name__)
db = SQLAlchemy(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
SALT = 'Hashids SALT'

الأسطر الثلاثة الأولى مسؤولة عن استيراد الأدوات التي سنعتمد عليها، أما سطر تعريف المُتغيّر db فهو ضروري لربط التّطبيق مع أداة SQLAlchemy أما السّطر الذي يليه فهو عبارة عن مسار قاعدة البيانات والمُتغيّر SALT يحمل قيمة مفتاح Hashids الذي تحدّثنا عنه سابقا.

الشّيفرة التّي سنحتاج إليها لإنشاء جدول الرّوابط هي كالتّالي (انسخها وألصقها بعد الشيفرة السّابقة):

class Url(db.Model):

    # Create table `urls` |id|url|

    __tablename__ = "urls"
    id = db.Column(db.Integer, primary_key=True) 
    url = db.Column(db.Text)

    def __init__(self, url):
        self.url = url

نبدأ بصنف يحمل نفس اسم الجدول (من المُفضّل أن يكون اسما مُفردا) هذا الصّنف يرثُ من الكائن db.Model. بعدها نسمّي الجدول ونقوم بتحديد الأعمدة، لاحظ بأنّ أنشأنا رقم المُعرّف عبارة عن عدد صحيح (Integer) وقمنا بتمريرالقيمة True للمُعامل primary_key وذلك لزيادة العدد بشكل آلي كلّما أضفنا رابطا جديدا، أما السطر الموالي فهو لإنشاء عمود الرّابط من نوع نصيّ. والدّالة init متواجدة للتمكين من إضافة الرّابط إلى قاعدة البيانات.

ولا تنس وضع الشيفرة المسؤولة عن تشغيل الخادوم ومُصحّح الأخطاء في آخر الملف:

if __name__ == '__main__':
    app.run(debug=True)

إنشاء قاعدة البيانات

بعد أن أعددنا قاعدة البيانات بقي علينا إنشاؤها ويُمكن ذلك عبر كتابة ملفّ بايثون صغير يقوم بالعمليّة، افتح ملفّا باسم create_db.py وضع به ما يلي:

from app import db

db.create_all()

نفّذ الملفّ من سطر الأوامر، بعدها ستُلاحظ ملفّا جديدا باسم app.db (هذا الاسم قد سبق وأن وضعناه كقيمة لـSQLALCHEMY_DATABASE_URI).
إذا أنشِئ الملف الجديد بنجاح، فهذا يعني بأنّنا انتهينا من مهمّة إنشاء قاعدة البيانات.

إعداد موجّه اختصار الرّوابط

سيكون موجّه اختصار الرّوابط باسم shorten وهي كلمة تعني “اختصر”، وسيقوم هذا الموجّه بإضافة الرّابط الذي سنحصل عليه من طلب HTTP من نوع GET إلى قاعدة البيانات ومن ثم تشفير رقم مُعرّفه باستخدام مكتبة Hashids وبعدها إرجاع مُعرّف الرّابط بعد تشفيره.

وسيكون المُوجّه كالتّالي:

# Controllers
@app.route('/shorten')
def shorten():
    # الحصول على الرّابط
    link = request.args.get('link')
    # التّحقق من أنّ الرّابط صالح
    if link and link != "" and not link.count(' ') >= 1 and not link.count('.') == 0:
        # إضافة بادئة http إذا لم تتواجد
        if link[:4].lower() != 'http':
            link = 'http://{}'.format(link)
        # إضافة الرّابط إلى قاعدة البيانات
        db.session.add(Url(url=link)) 
        db.session.commit()
        # استخراج رقم مُعرّف آخر رابط مُضاف (أي الرّابط المُضاف في هذا الموجّه)
        url_id = db.session.query(Url).order_by(Url.id.desc()).first().id
        # تشفير رقم المُعرّف
        id_code = Hashids(salt=SALT, min_length=4).encode(url_id)
        # إضافة علامة / إلى بداية المعرّف الخاص بالرّابط ('/HFdK')
        short_link = '/' + id_code  
        # إرجاع المعرّف
        return short_link 
    # في حالة عدم صلاحيّة الرّابط، عد إلى الصّفحة الرّئيسيّة
    else:
        return render_template("index.html")

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

مثال لتشفير بعض الرّوابط:

http://127.0.0.1:5000/shorten?link=http://google.com

=> /QxvP

http://127.0.0.1:5000/shorten?link=http://mostaql.com

=> /wrJw

http://127.0.0.1:5000/shorten?link=http://academy.hsoub.com

=> /QB5w

موجّه إعادة التّوجيه إلى الرّابط الأصلي

بعد أن تمكّنا من إضافة الرّابط والحصول على مُعرّفه، يجب أن يتمكّن المُستخدم من إعادة التّوجه إلى الرّابط الأصلي عبر المُعرّف.
مثلا إذا كتب الرّابط كالآتي:

127.0.0.1:5000/QB5w

فيجب على التطبيق أن يعيد توجيهه إلى الرّابط الأصلي المرتبط بالمُعرّف QB5w.
سيكون المُوجّه كالتّالي:

@app.route('/<id>')
def url(id):
    # تحويل المُعرّف إلى عدد  (فك تشفيره)
    original_id = Hashids(salt=SALT, min_length=4).decode(id)
    # إذا كان المعرف مرتبطا بمجموعة فاحصل على العنصر الأول
    if original_id:
        original_id = original_id[0]
        # الحصول على الرّابط من خلال رقم مُعرّفه
        original_url = Url.query.filter_by(id=original_id).first().url

        # إعادة توجيه المُستخدم إلى الرّابط الأصلي
        return redirect(original_url , code=302)
    else:
        return render_template('index.html')

هنا قُمنا بفك تشفير المُعرّف ومن ثمّ الحصول عليه من قاعدة البيانات عبر السّطر التّالي:

original_url = Url.query.filter_by(id=original_id).first().url

وهذا شبيه بما فعلناه سابقا في درس ربط تطبيق Flask بقاعدة بيانات SQLite ولو أردنا كتابة الاستعلام بلغة SQL كنّا سنكتب شيئا شبيها لما يلي:

select url from urls where id=original_id

لاحظ كذلك بأنّنا نُقدّم ملفّا باسم index.html إذا لم يكن المعرّف مُرتبطا بعدد معيّن، لا يهم محتوى هذا الملف، يُمكنك كذلك أن تُرجع ملفّا يُخبر المُستخدم بأنّ المُعرّف غير صحيح أو ببساطة إرجاع خطأ 404 من ملفّ HTML بسيط.

خاتمة:

تعرّفنا في هذا الدّرس على كل ما سنحتاج إليه لاختصار الرّوابط، لكنّنا لم نُقدّم أي صفحة مرئية للمُستخدم العادي، كل ما قمنا به هو إعداد النّظام الخلفي أو ما يُسمى بالـBack-end وهو الجزء المسؤول عن إدخال الرّوابط إلى قاعدة البيانات وتوليد مُعرّف نصي لكل رابط انطلاقا من رقم مُعرّفه، وكذلك تحويل الرّابط المُختصر إلى الرّابط الأصلي. ما يلزمنا الآن هو النّظام الأمامي أو Front-end وهو كل ما يخص ما يراه المُستخدم العادي وما يحدث في جهته (تُسمى كذلك جهة العميل Client Side).
في الدّرس القادم سنقوم بإعداد ملفّ HTML مع حقل يُمكن للمُستخدم أن يلصق به رابطه ويضغط على زر ENTER من لوحة المفاتيح لاختصار الرّابط، وكذا زر يُمكّنه من نسخ الرّابط المُختصر (باستخدام لغة جافاسكربت).
سنقوم كذلك بنشر تطبيقنا على منصّة Heroku للتّطبيقات السّحابيّة.





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


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



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

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

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


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

تسجيل الدخول

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


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