اذهب إلى المحتوى

بناء مختصر روابط باستخدام إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite


محمد الخضور

يُعد فلاسك إطار عمل لبناء تطبيقات الويب مبني باستخدام لغة بايثون، أما SQLite، فهو محرك قواعد بيانات يُستخدم مع بايثون لتخزين بيانات التطبيقات. سنبني في هذا المقال مختصِرًا للروابط URL shortener ليحوّل أي رابط إلى آخر مُختصَر وأكثر موثوقية بما يشبه مبدأ عمل موقع bit.ly.

سنستخدم في بناء تطبيقنا مكتبة التعمية Hashids لتوليد سلاسل فريدة لمُعرّفات الروابط؛ إذ أن هذه المكتبة مُختصّةٌ بتوليد مُعرّفات مميزة قصيرة unique ID انطلاقًا من أرقام صحيحة، إذ تحوّل مثلاً الرقم "12" إلى سلسلة فريدة مثل "1Xcld".

يمكن استخدام السلاسل الفريدة هذه لتوليد معرّفات لا يمكن التنبؤ بها سواءٌ للفيديوهات على مواقع مشاركة الفيديوهات، أو الصور في خدمة رفع الصور وغيرها. ففي حال وصول المستخدم مثلًا إلى صورةٍ ذات رابط "your_domain/image/J32Fr"، فلا يمكنه التنبؤ بمواقع الصور الأخرى، على عكس حالة استخدام معرفّات مؤلفّة من أرقام صحيحة في مختصر الروابط، فمثلاً لو كان رابط الصورة "your_domain/image/33"، فسيتمكن المستخدم من التنبؤ بمواقع الصور الأخرى بسهولة، وبالتالي يقدِّم استخدام روابط بمعرّفات غير قابلة للتنبؤ نوعًا من الخصوصية للخادم، كونها تمنع المستخدمين من اكتشاف الروابط المختصرة المُستخدمة من قِبل المستخدمين الآخرين.

سنبني إذًا مختصِرًا للروابط باستخدام فلاسك وSQLite ومكتبة Hashids، بحيث يتمكّن المستخدمون من إدخال روابط وتوليد نسخٍ مختصرةٍ منها، إضافةً إلى توفير صفحة إحصاءات يمكن للمستخدمين من خلالها متابعة عدد مرات زيارة الرابط، كما سنستخدم بوتستراب bootstrap لإضافة التنسيقات للتطبيق.

مستلزمات العمل

قبل المتابعة في هذا المقال لا بدّ من:

  • توفُّر بيئة برمجة بايثون 3 محلية، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو flask_shortener.
  • فهم مبادئ فلاسك، مثل بناء الوجهات وإخراج نماذج HTML والاتصال بقواعد بيانات SQLite، ويمكنك الاطلاع على [هذا المقال](رابط المقال الأوّل بعد نشره) لمزيدٍ من المعلومات حول هذه النقاط.

الخطوة الأولى- إعداد المتطلبات

سنفعّل في هذه الخطوة بيئة بايثون ونثبّت فلاسك باستخدام مثبِّت الحزم pip، ثم سننشئ قاعدة بيانات سنستخدمها لتخزين الروابط، وفي حال كون بيئة البرمجة غير مفعّلة بعد، سنستخدم الأمر التالي لتفعيلها:

$ source env/bin/activate

وبعد تفعيل بيئة البرمجة، سنثبّت فلاسك ومكتبة Hashids باستخدام الأمر التالي:

(env)user@localhost:$ pip install flask hashids

ثمّ سننشئ ملف تخطيط قاعدة البيانات باسم schema.sql، ليحتوي أوامر SQL اللازمة لإنشاء جدول للعناوين باسم urls.

لذا، سننشئ الملف schema.sql داخل المجلد flask_shortener كما يلي:

(env)user@localhost:$ nano schema.sql

ونكتب أوامر SQL التالية فيه:

DROP TABLE IF EXISTS urls;

CREATE TABLE urls (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    original_url TEXT NOT NULL,
    clicks INTEGER NOT NULL DEFAULT 0
);

أول سطرٍ من شيفرة SQL السابقة مسؤولٌ عن حذف أي جداول موجودةٍ سابقًا ضمن قاعدة البيانات urls، وبذلك لن يكون هناك أي سلوك متضارب أو عشوائي ناجمٍ عن تشابه الأسماء ضمن قاعدة البيانات، والجدير بالملاحظة أنّ تطبيق هذين الأمر سيؤدي إلى حذف كامل محتوى قاعدة البيانات الحالي، لذا لا تُدخل أي بياناتٍ مهمّة في التطبيق حتى تنتهي من كافّة الخطوات التعليميّة في المقال وتجربة نتائجه النهائية.

بعد ذلك، سننشئ جدولًا يحتوي الأعمدة التالية:

  • "id": ويحتوي بياناتٍ من نوع رقم صحيح ويمثِّل المعرّف الفريد لكل رابطٍ مُدخل، وسنستخدم هذا المعرّف لاحقًا في استنتاج الرابط الأصلي من سلسلة محارف عشوائية معمّاة Hash string.
  • "created": يحتوي على تاريخ ووقت اختصار الرابط.
  • "original_url": يحتوي على الرابط الكامل الأصلي قبل الاختصار والذي سيُعاد توجيه المستخدمين إليه.
  • "clicks": يحتوي على عدد مرات النقر على الرابط. ستكون قيمته الابتدائية 0، وتزداد مع كل إعادة توجيه.

اِحفظ الملف وأغلقه.

الآن لتنفيذ ملف schema.sql المسؤول عن بناء جدول الروابط urls، سننشئ ملفًا باسم int_db.py داخل مجلد flasks_hortener:

(env)user@localhost:$ nano init_db.py

ونضيف الشيفرة التالية إليه:

import sqlite3

connection = sqlite3.connect('database.db')

with open('schema.sql') as f:
    connection.executescript(f.read())

connection.commit()
connection.close()

إذ يحدث في البداية اتصالٌ مع الملف database.db المُنشأ تلقائيًا عند تنفيذ البرنامج السابق، ويمثّل هذا الملف قاعدة البيانات التي ستخزّن كامل بيانات التطبيق، ثم يُفتح بعد ذلك ملف تخطيط قاعدة البيانات schema.sql ويُشغَّل باستخدام التابع ()executscript القادر على تنفيذ عدّة تعليمات SQL معًا، وهذا ما سينشئ بالنتيجة جدول الروابط urls، وفي النهاية نؤكِّد على التغييرات ويُغلق الاتصال مع قاعدة البيانات.

اِحفظ الملف وأغلقه.

الآن، نشغّل البرنامج:

(env)user@localhost:$ python init_db.py

وبعد انتهاء تنفيذه، سيظهر ملفٌ جديدٌ باسم database.db ضمن المجلد flask_shortener.

وبنهاية الخطوة الأولى، نكون قد ثبّتنا فلاسك ومكتبة Hashids، وأنشأنا مخطّط قاعدة البيانات إضافةً لقاعدة بيانات SQLite تحتوي جدولًا باسم "urls" مخصّصًا لتخزين العناوين الأصلية لتلك المُختصرة.

سنستخدم فيما يلي فلاسك لإنشاء الصفحة الرئيسية index والتي سيتمكّن المستخدمون من خلالها من إدخال الروابط المطلوب اختصارها.

الخطوة الثانية - إنشاء الصفحة الرئيسية لتطبيق اختصار الروابط

سننشئ في هذه الخطوة الوجهة اللازم لعمل الصفحة الرئيسية index، والذي يمكّن المستخدمين من إدخال الروابط الأصلية المراد اختصارها؛ إذ ستُخزَّن هذه الروابط في قاعدة البيانات بدايةً، ويستخدم المعرّف الفريد للرابط لتوليد سلسلة محارف قصيرة معمّاة باستخدام مكتبة Hashids، ويبني الرابط المُختصر ويخرجه نتيجةً.

سننشئ الآن الملف الأساسي لتطبيق فلاسك باسم app.py ضمن المجلد flask_shortener:

(env)user@localhost:$ nano app.py

ونضيف الشيفرة التالية ضمنه:

import sqlite3
from hashids import Hashids
from flask import Flask, render_template, request, flash, redirect, url_for


def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn

نبدأ بالشيفرة السابقة باستيراد الوحدة sqlite3، والصنف Hashids من المكتبة hashids، إضافةً للمساعد الخاص بإطار فلاسك، ثم تفتح الدالة ()get_db_connection اتصالًا مع ملف قاعدة البيانات database.db، وتعيّن قيمة السمة row_factory لتكون sqlite3.Row لنتمكّن من الوصول لأعمدة جدول قاعدة البيانات باستخدام أسمائها؛ وهذا يعني أنّ اتصال قاعدة البيانات سيعيد سجلات يمكننا التعامل معها كما هو الحال مع قواميس بايثون Dictionaries الاعتيادية (وحدات تخزين القيم المنظمّة في بايثون)، وفي النهاية تعيد الدالة كائن الاتصال conn، الذي سيُستخدم للوصول إلى قاعدة البيانات.

سنضيف الآن الشيفرة التالية:

. . .
app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'

hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])

التي تنشئ كائن تطبيق فلاسك وتحدِّد مفتاح الأمان لجعل الجلسات آمنة، وبما أنّ مفتاح الأمان هو أيضًا سلسلة محارف عشوائية وسريّة، فسنستخدمها مصدرًا لمحارف الإغفال salt لمكتبة التعمية Hashids بما يضمن كون السلاسل المعمّاة الناتجة غير قابلة للتنبؤ كونها ستتغير بتغيّر محارف الإغفال المتغيرة أصلًا باستمرار مع تغيُّر مفتاح الأمان.

اقتباس

ملاحظة: محارف الإغفال salt هي سلاسل عشوائية تُزوَّد بها دالة التعمية ()hashids.encode، بحيث تكون السلسلة المعمّاة الناتجة مبنيةً باستخدام محارف الإغفال هذه، بما يضمن كون السلاسل الناتجة فريدةً وغير قابلةٍ للتخمين، مثل مبدأ كلمات المرور السرية والتي لا يستخدمها سوى مالكها لتشفير وفك تشفير السلاسل المعمّاة.

احرص على إبقاء محارف الإغفال سريةً حفاظًا على الأمان، ولهذا السبب ارتأينا أنّه من الأفضل استخدام مفتاح الأمان الخاص بالتطبيق على هيئة محارف إغفال.

وبذلك نكون أنشأنا كائن تعمية hashids يضمن كون كل سلسلة معمّاة مؤلفّة من 4 محارف على الأقل عبر تمرير هذه القيمة إلى المعامل min_length من هذا الكائن، واستخدمنا مفتاح أمان التطبيق مثل محرف إغفال.

سنضيف الآن الشيفرة التالية إلى نهاية الملف:

. . .
@app.route('/', methods=('GET', 'POST'))
def index():
    conn = get_db_connection()

    if request.method == 'POST':
        url = request.form['url']

        if not url:
            flash('The URL is required!')
            return redirect(url_for('index'))

        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                (url,))
        conn.commit()
        conn.close()

        url_id = url_data.lastrowid
        hashid = hashids.encode(url_id)
        short_url = request.host_url + hashid

        return render_template('index.html', short_url=short_url)

    return render_template('index.html')

يعدِّل المزخرف app.route@ دوال بايثون المألوفة لتصبح دوالًا عاملةً في فلاسك مثل الدالة ()index، التي تُحوَّل القيمة المعادة منها إلى استجابةٍ من نوع HTTP تُعرض لدى عميل HTTP الذي قد يكون متصفحًا مثلًا.

وضمن بناء دالة فلاسك ()index، سنمرّر السطر التالي:

methods=('GET', 'POST')

الحاوي على طرائق الطلبات المقبولة إلى وسيط الطرائق methods في المزخرف ()app.route، ما يضمن قبول الطلبات سواءً بطريقة GET أو POST.

الآن سيُفتح اتصالٌ مع قاعدة البيانات، فإذا كان الطلب بطريقة GET، نتجاوز الشرط:

if request.method == 'POST'

وصولًا إلى آخر سطر في الشيفرة والمسؤول عن إخراج قالب HTML باسم index.html، الذي سيحتوي على نموذج إدخال الروابط المُراد اختصارها؛ أما في حال تحقّق الشرط السابق، فهذا يعني أنّه قد أُدخل رابطٌ من قِبل المستخدم لاختصاره، فيُخزَّن هذا الرابط ضمن المتغير url؛ أمّا في حال إرسال المستخدم النموذج دون إدخال رابط، فستظهر له رسالةً تخبره بأنّ حقل الرابط مطلوب "!The URL is required" ويُعاد توجيهه إلى الصفحة الرئيسية index مجدّدًا.

وعندما يرسل المستخدم نموذجًا حاويًا على رابط، يُخزّن هذا الرابط في جدول urls من قاعدة البيانات باستخدام تعليمة INSERT INTO في SQL، كما يُضمَّن الموضع المؤقت ? (الذي سيُستبدل لاحقًا برقمٍ ما) في الدالة ()execute، ومن ثمّ يُمرّر السجل الحاوي على الرابط المطلوب اختصاره ليُحفظ في قاعدة البيانات بأمان، وفي النهاية تؤكَّد العملية ويُقطع الاتصال مع قاعدة البيانات.

بعد ذلك، يُخزَّن المعرّف الخاص بالرابط المُدخل آنفًا في قاعدة البيانات ضمن متغيّر باسم url_id، و يمكننا الوصول إلى معرّف الرابط باستخدام السمة lastrowid التي تعيد معرّف آخر سجل مُدخلٍ في قاعدة البيانات.

وتُبنى السلسلة المعمّاة بتمرير معرّف الرابط إلى التابع ()hashids.encode، لتخزَّن في المتغير hashid، فمثلًا ستكون القيمة المعادة لدى استدعاء التابع (hashids.encode(1 هي السلسلة المُعمّاة KJ34 وذلك اعتمادًا على محارف الإغفال المُستخدمة.

ننتقل الآن إلى مرحلة بناء الرابط المُختصر باستخدام السمة request.host_url، التي يوفرّها عادةً كائن فلاسك request للوصول إلى رابط مضيف التطبيق، والذي سيكون /http://127.0.0.1:5000 في بيئة التطوير، ويصبح "your_domain" لدى نشر التطبيق؛ فمثلًا ستكون قيمة المتغير short_url مساويةً http://127.0.0.1:5000/KJ34، والتي تمثّل الرابط المختصر الذي سيعيد توجيه المُستخدم إلى الرابط الأصلي ذي المعرّف الموافق للسلسلة المعمّاة KJ34 والمحفوظ في قاعدة البيانات .

نهايةً، يُخرج قالب HTML المسمّى index.html بتمرير المتغير short_url إليه. وستبدو الشيفرة كما يلي بعد الإضافات السابقة:

import sqlite3
from hashids import Hashids
from flask import Flask, render_template, request, flash, redirect, url_for


def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn


app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'

hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])


@app.route('/', methods=('GET', 'POST'))
def index():
    conn = get_db_connection()

    if request.method == 'POST':
        url = request.form['url']

        if not url:
            flash('The URL is required!')
            return redirect(url_for('index'))

        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                (url,))
        conn.commit()
        conn.close()

        url_id = url_data.lastrowid
        hashid = hashids.encode(url_id)
        short_url = request.host_url + hashid

        return render_template('index.html', short_url=short_url)

    return render_template('index.html')

اِحفظ الملف وأغلقه.

الآن سننشئ ملف القالب الأساسي وملف قالب HTML المسمّى index.html، لذا سنضيف مجلدًا جديدًا للقوالب باسم templates ضمن المجلد flask_shortener، وسنفتح ملفًا باسم base.html ضمنه:

(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html

والآن سنكتب شيفرة بوتستراب bootstrap التالية ضمن الملف base.html، وإن لم تكن قوالب HTML في فلاسك مألوفةً بالنسبة لك، يمكنك قراءة الخطوة الثالثة من المقال كيفية إنشاء تطبيق ويب باستخدام إطار فلاسك في لغة بايثون 3:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %} {% endblock %}</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-light bg-light">
        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskShortener</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
            <li class="nav-item active">
                <a class="nav-link" href="#">About</a>
            </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        {% for message in get_flashed_messages() %}
            <div class="alert alert-danger">{{ message }}</div>
        {% endfor %}
        {% block content %} {% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

الجزء الأكبر من الشيفرة السابقة هو تعليمات HTML معيارية وشيفرات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <meta> متصفح الويب بالمعلومات، في حين ينشئ الوسم <link> ارتباطًا إلى ملفات CSS الخاصة ببوتستراب، ويضمِّن الوسم <script> شيفرة جافاسكربت مسؤولة عن بعض ميزات بوتستراب الإضافية.

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

<title>{% block title %} {% endblock %}</title>

وتُستخدم الحلقة:

for message in get_flashed_messages()

لعرض الرسائل الخاطفة (من تحذيرات وتنبيهات وغيرها)، في حين يُضمَّن محتوى القوالب الموروثة في الموضع المؤقت {% block content %} {% endblock %}، بما يضمن وصول كافّة القوالب إلى القالب الأساسي وهذا يمنع تكرار الشيفرات.

اِحفظ الملف وأغلقه.

الآن سننشئ الملف index.html، الذي سيرث الملف base.html على النحو التالي:

(env)user@localhost:$ nano templates/index.html

وسنكتب ضمنه الشيفرة التالية:

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Welcome to FlaskShortener {% endblock %}</h1>
    <form method="post">
    <div class="form-group">
        <label for="url">URL</label>
        <input type="text" name="url"
               placeholder="URL to shorten" class="form-control"
               value="{{ request.form['url'] }}" autofocus></input>
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
    </form>

    {% if short_url %}
    <hr>
    <span>{{ short_url }}</span>
    {% endif %}
{% endblock %}

ورِث الملف index.html في الشيفرة السابقة شيفرة القالب base.html، وعرّفنا عنوانًا خاصًّا به، ثمّ أنشأنا نموذجًا ذا حقل إدخال باسم url، والذي سيسمح للمستخدمين بإدخال الروابط المُراد اختصارها، وتُخزَّن القيمة ضمن متغير الكائن ['request.form['url لتجنّب فقدان المعلومات في حال حدوث أخطاء، كأن يُرسل المستخدم النموذج دون إدخال رابط. كما يحتوي النموذج على زرٍ لتأكيد النموذج وإرساله.

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

الآن، سنضبط متغيرات البيئة التي يحتاجها فلاسك لتشغيل التطبيق كما يلي:

(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
(env)user@localhost:$ flask run

يحدّد متغير البيئة FLASK_APP في الشيفرة المُبينة أعلاه التطبيق المراد تشغيله (وهو في حالتنا الملف app.py)؛ بينما يحدّد المتغير FLASK_ENV وضع التشغيل وهو وضع التطوير development في حالتنا مع تشغيل منقّح الأخطاء، ولا يجب اختيار وضع التطوير في مرحلة التشغيل الفعلي للتطبيق (نشر المُنتج)؛ أمّا الأمر flask run فهو المسؤول عن تشغيل التطبيق.

الآن، نفتح المتصفح ونذهب إلى الرابط "/http://127.0.0.1:5000"، فتظهر الصفحة الرئيسية الحاوية على العبارة "Welcome to FlaskShortener" كما هو موضح في الشكل التالي:

first_step2a.png

وبإدخال رابط URL ما، نحصل على الرابط المختصر كما هو موضح في الشكل التالي:

second_step2b.png

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

الخطوة الثالثة - إنشاء وجهة لإعادة التوجيه إلى الرابط الأصلي

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

لذا، بدايةً سنفتح الملف app.py لإضافة الوجهة الجديدة:

(env)user@localhost:$ nano app.py

ثم سنضيف الشيفرة التالية إلى نهاية الملف:

. . .

@app.route('/<id>')
def url_redirect(id):
    conn = get_db_connection()

    original_id = hashids.decode(id)
    if original_id:
        original_id = original_id[0]
        url_data = conn.execute('SELECT original_url, clicks FROM urls'
                                ' WHERE id = (?)', (original_id,)
                                ).fetchone()
        original_url = url_data['original_url']
        clicks = url_data['clicks']

        conn.execute('UPDATE urls SET clicks = ? WHERE id = ?',
                     (clicks+1, original_id))

        conn.commit()
        conn.close()
        return redirect(original_url)
    else:
        flash('Invalid URL')
        return redirect(url_for('index'))

تستقبل الوجهة في الشيفرة السابقة القيمة id من الرابط ويمررها وسيطًا لدالة فلاسك ()url_redirect، فإذا كان الرابط http://127.0.0.1:5000/KJ34 مثلًا، ستُمرّر السلسلة KJ34 للمعامل id.

وضمن دالة عرض فلاسك نفتح اتصالًا مع قاعدة البيانات، ثمّ نحوّل السلسلة المعمّاة إلى القيمة العددية الصحيحة الأصلية لها باستخدام التابع ()decode للكائن hashids، لتُخزّن في المتغير original_id، فإذا لم يكن هذا المتغير فارغًا، يُستخلص المعرّف ID منه.

وبما أنّ التابع ()decode يُعيد سجلًا كاملًا، فسنجلب الجزء الأوّل منه، الذي يمثّل المعرّف الأصلي باستخدام التابع [original_id[0، ثمّ سنستخدم التعليمة SELECT في SQL لجلب الرابط الأصلي الذي يتوافق معرّفه في قاعدة البيانات مع المعرّف المُستخلص من السلسلة المعمّاة، إضافةً لجلب عدد الزيارات لهذا الرابط من الجدول urls؛ أما بيانات الرابط فستُجلب باستخدام التابع ()fetchone، ثم سنخزّن هذه البيانات في المتغيرين original_url و clicks.

الآن وبعد زيارة هذا الرابط، سنزيد عدد الزيارات له باستخدام تعليمة UPDATE في SQL، ونهايةً نؤكّد العملية ونغلق الاتصال مع قاعدة البيانات، وسنعيد التوجيه إلى الرابط الأصلي باستخدام دالة فلاسك المُساعدة ()redirect؛ وفي حال فشل فك تشفير السلسلة المُعمّاة، ستُعرض رسالةٌ لإعلام المستخدم بأن الرابط الذي أدخله غير صالح، ثمّ يعاد توجيهه إلى الصفحة الرئيسية index.

اِحفظ الملف وأغلقه.

الآن، سنشغل خادم التطوير:

(env)user@localhost:$ flask run

وبالذهاب إلى الرابط "/http://127.0.0.1:5000" باستخدام المتصفح، وبإدخال رابطٍ ما وزيارة الرابط المُختصَر الناتج، سيعيد التطبيق توجيه المستخدم إلى الرابط الأصلي.

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

الخطوة الرابعة - إضافة صفحة إحصائيات

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

لنفتح الملف app.py لإضافة الوجهة الجديدة الخاص بصفحة الإحصائيات:

(env)user@localhost:$ nano app.py

ونضيف الشيفرات التالية إلى نهاية الملف:

. . .

@app.route('/stats')
def stats():
    conn = get_db_connection()
    db_urls = conn.execute('SELECT id, created, original_url, clicks FROM urls'
                           ).fetchall()
    conn.close()

    urls = []
    for url in db_urls:
        url = dict(url)
        url['short_url'] = request.host_url + hashids.encode(url['id'])
        urls.append(url)

    return render_template('stats.html', urls=urls)

سنبدأ في دالة عرض فلاسك هذه بفتح اتصالٍ مع قاعدة البيانات، ثمّ نجلب كل من معرّف الرابط وتاريخ إنشائه والرابط الأصلي وعدد مرات زيارته باستخدام التابع ()fetchall لكامل مُدخلات الجدول urls في قاعدة البيانات، وذلك من أجل تنظيم كامل هذه السجلات ضمن قائمة، لتُحفظ في المتغير db_urls، وفي النهاية نغلق الاتصال مع قاعدة البيانات.

الآن ولعرض الرابط المُختصَر لكل مُدخل، لا بُدّ من بنائه وإضافته إلى كل عنصر من القائمة السابقة db_urls المجلوبة من قاعدة البيانات، لذا سننشئ قائمةً فارغةً باسم urls لنخزّن بها حقل الروابط فقط من القائمة db_urls باستخدام الحلقة التكرارية for url in db_urls.

سنحوّل الكائن sqlite3.Row إلى قاموس dictionary قابل لإسناد القيم إليه باستخدام دالة بايثون ()dict، ثمّ سنضيف إليه مفتاحًا جديدًا باسم short_url ليحتوي القيمة التالية:

request.host_url + hashids.encode(url['id'])

المُستخدمة سابقًا في دالة عرض فلاسك ()index لبناء الروابط المُختصرة، ونهايةً سنضيف القاموس الذي يحتوي على الروابط المُختصَرة إلى القائمة urls.

نهايةً سنخرج قالب HTML باسم stats.html ممررين قائمة urls إليه.

اِحفظ الملف وأغلقه.

سننشئ الآن ملف القالب stats.html:

(env)user@localhost:$ nano templates/stats.html

وسنكتب الشيفرات التالية فيه:

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} FlaskShortener Statistics {% endblock %}</h1>
    <table class="table">
        <thead>
            <tr>
            <th scope="col">#</th>
            <th scope="col">Short</th>
            <th scope="col">Original</th>
            <th scope="col">Clicks</th>
            <th scope="col">Creation Date</th>
            </tr>
        </thead>
        <tbody>
            {% for url in urls %}
                <tr>
                    <th scope="row">{{ url['id'] }}</th>
                    <td>{{ url['short_url'] }}</td>
                    <td>{{ url['original_url'] }}</td>
                    <td>{{ url['clicks'] }}</td>
                    <td>{{ url['created'] }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

{% endblock %}

في الشفرة السابقة، ورثنا شيفرة قالب HTML الرئيسي base.html وحدّدنا عنوانًا، وعرّفنا جدولًا يضم الأعمدة التالية:

  • #: معرّف الرابط.
  • Short: الرابط المُختصَر.
  • Original: الرابط الأصلي.
  • Clicks: عدد مرات زيارة الرابط المُختصَر.
  • Creation Date: تاريخ إنشاء الرابط المُختصَر.

وجرت عملية تعبئة سجلات الجدول باستخدام حلقة for تكرارية تجلب البيانات من القائمة urls لتعرض القيم الموافقة لكل من الأعمدة السابقة لكل رابط.

الآن سنشغّل خادم التطوير:

(env)user@localhost:$ flask run

وباستخدام المتصفح، وبالذهاب إلى الرابط "http://127.0.0.1:5000/stats" ستظهر كافّة الروابط في جدول كما في الشكل التالي:

third_step4a.png

الآن سنضيف زر أوامر باسم Stats في شريط التصفّح للوصول إلى صفحة الإحصائيات، لذا سنفتح الملف base.html:

(env)user@localhost:$ nano templates/base.html

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

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %} {% endblock %}</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-light bg-light">
        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskTodo</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
            <li class="nav-item active">
                <a class="nav-link" href="#">About</a>
            </li>

            <li class="nav-item active">
                <a class="nav-link" href="{{ url_for('stats')}}">Stats</a>
            </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        {% for message in get_flashed_messages() %}
            <div class="alert alert-danger">{{ message }}</div>
        {% endfor %}
        {% block content %} {% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

ضمنّا في الشفرة السابقة عنصر قائمة <li> في شريط التصفّح، وربطناه مع دالة فلاسك ()stats باستخدام دالة ()url_for، وبذلك يصبح بالإمكان الوصول إلى صفحة الإحصائيات من شريط التصفّح، التي ستعرض معلومات عن كل رابط، تتضمّن الرابط المُختصر المكافئ وعدد مرات زيارته.

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

نهايةً يمكنك الاطلاع على شيفرة تطبيق اختصار الروابط كاملةً.

الخاتمة

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

يمكنك الاطلاع على مزيدٍ من المقالات التعليمية حول فلاسك من خلال الروابط التالية:

  • روابط المقال الأوّل والثاني والثالث بعد النشر في الأكاديمية.
  • كيفية إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask في لغة بايثون 3.
  • كيفية استخدام علاقات قاعدة البيانات من نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite.
  • الثالث.

ترجمة -وبتصرف- للمقال How To Make a URL Shortener with Flask and SQLite لصاحبه Abdelhadi Dyouri.

اقرأ أيضًا


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

أفضل التعليقات

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



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...