بناء تطبيق دردشة باستخدام Django Channels

شرح بناء تطبيق دردشة باستخدام Django Channels

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

رغم أن إطار العمل Django يُعد من أكثر أطر Python شهرةً واعتمادية في بناء تطبيقات الويب، إلا أن بنيته التقليدية المبنية على WSGI لم تكن مهيأة في الأصل للتعامل مع الاتصالات المفتوحة طويلة الأمد (long-lived connections) أو البث اللحظي (real-time streaming)، وهو ما يجعل من دعم بروتوكولات مثل WebSocket تحديًا تقنيًا لا يمكن تجاوزه دون إضافة أدوات متخصصة.

هنا يأتي دور Django Channels، وهي حزمة مكملة لـ Django تُعيد تعريف طريقة معالجة الطلبات، من خلال دعم واجهة ASGI التي تسمح بتعدد البروتوكولات وفتح قنوات اتصال دائمة بين العميل والخادم. بفضل هذه الحزمة، يمكن لتطبيقات Django الحديثة أن تدير اتصالات WebSocket بكفاءة، وتتعامل مع الاتصالات غير المتزامنة بنفس مرونة وسلاسة التطبيقات المصممة من البداية لهذا الغرض.

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

الخطوة الأولى: تجهيز بيئة العمل

قبل البدء في بناء أي تطبيق يعتمد على Django Channels، من الضروري إعداد بيئة العمل بشكل صحيح لضمان استقرار المشروع وتفادي المشكلات أثناء التطوير أو التشغيل. سنبدأ بإنشاء مشروع Django جديد، تثبيت الحزم المطلوبة، وضبط إعدادات ASGI التي تعد بمثابة بوابة التطبيق للتعامل مع WebSockets والاتصالات غير المتزامنة.

إنشاء مشروع Django جديد

نبدأ أولًا بإنشاء بيئة افتراضية معزولة لتنصيب الحزم الخاصة بالمشروع. يُفضل دائمًا استخدام بيئة افتراضية لفصل إعدادات وتبعيات هذا المشروع عن بقية مشروعات النظام:

python -m venv env
source env/bin/activate  # على أنظمة لينكس و macOS
env\Scripts\activate     # على نظام ويندوز

بعد ذلك، نثبت حزمة Django وننشئ مشروعًا جديدًا:

pip install django
django-admin startproject chat_project
cd chat_project

تثبيت Django Channels و Daphne

حتى يتمكن مشروع Django من التعامل مع الاتصالات اللحظية وWebSockets، نحتاج إلى تثبيت مكتبة Django Channels ومخدم ASGI المعروف باسم Daphne:

pip install channels daphne

تُعد Daphne بمثابة خادم ASGI الأساسي الذي سيتعامل مع الطلبات في تطبيقنا، بينما Django Channels توفر الأدوات اللازمة لإدارة تلك الاتصالات.

إعداد ملف asgi.py

بمجرد تثبيت الحزم، يجب تعديل ملف asgi.py الخاص بالمشروع ليستخدم Channels بدلًا من WSGI التقليدي. افتح ملف chat_project/asgi.py واستبدل محتواه بالتالي:

import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat_project.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # WebSocket routing سيتم إضافته لاحقًا
})

في هذه المرحلة، أنشأنا بوابة ASGI الأساسية التي يمكنها استقبال الطلبات عبر بروتوكول HTTP، وسنضيف مسارات WebSocket لاحقًا أثناء إعداد التطبيق.

تحديث إعدادات المشروع

أخيرًا، علينا إعلام Django بوجود Channels ضمن التطبيقات المثبتة، وتحديد التطبيق الافتراضي للتعامل مع ASGI.

افتح ملف settings.py وأضف Channels إلى قائمة INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
]

ثم أضف إعداد ASGI_APPLICATION ليشير إلى ملف asgi.py الذي عدلناه:

ASGI_APPLICATION = 'chat_project.asgi.application'

بهذا نكون قد انتهينا من إعداد بيئة العمل الخاصة بمشروع الدردشة اللحظية، وجعلنا مشروع Django جاهزًا لاستقبال اتصالات WebSocket عبر ASGI.

الخطوة الثانية: إنشاء تطبيق الدردشة داخل المشروع

بعد أن أصبح مشروع Django مُجهزًا لدعم الاتصالات غير المتزامنة من خلال ASGI وDjango Channels، يمكننا الآن الانتقال إلى المرحلة التالية، وهي إنشاء التطبيق المسؤول عن إدارة منطق الدردشة داخل المشروع. في Django، يُنظّم المشروع إلى تطبيقات فرعية (Apps)، كل منها يتعامل مع جزء محدد من الوظائف، وفي حالتنا سيكون لدينا تطبيق باسم chat مخصص للتعامل مع غرف الدردشة والرسائل اللحظية.

إنشاء تطبيق جديد باسم chat

من داخل مجلد المشروع الرئيسي chat_project/، ننفذ الأمر التالي لإنشاء تطبيق جديد:

python manage.py startapp chat

بمجرد تنفيذ هذا الأمر، سينشئ Django مجلدًا باسم chat يحتوي على الملفات الأساسية الخاصة بالتطبيق، مثل models.py, views.py, apps.py وغيرها.

تسجيل التطبيق داخل إعدادات المشروع

بعد إنشاء التطبيق، لا بد من إضافته إلى إعدادات المشروع حتى يتعرف عليه Django عند تشغيل الخادم أو تنفيذ أوامر الإدارة.

افتح ملف settings.py وأضف 'chat', إلى قائمة INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'chat',
]

(اختياري) إعداد نموذج لحفظ الرسائل

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

كمثال:

from django.db import models

class Message(models.Model):
    room_name = models.CharField(max_length=255)
    content = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)

بعد إنشاء هذا النموذج، يمكن تنفيذ أوامر makemigrations و migrate لاحقًا لتحديث قاعدة البيانات، إن أردنا دعم خاصية الأرشفة.

إعداد ملف routing.py الخاص بالتطبيق

من المتطلبات الأساسية لتطبيق يعتمد على WebSocket أن يحدد مسارات الاتصالات (Routes) الخاصة به. في Django Channels، يتم ذلك من خلال ملف routing.py داخل كل تطبيق معني بالتعامل مع WebSocket.

داخل مجلد chat/، أنشئ ملفًا جديدًا باسم routing.py، وسنضيف فيه مسارات WebSocket لاحقًا عند إعداد المستهلك (Consumer).

في هذه المرحلة، لا حاجة لإضافة شيء في هذا الملف، لكن من المهم تجهيز هيكله مبكرًا حتى يتكامل بسلاسة مع إعدادات المشروع لاحقًا.

الخطوة الثالثة: إعداد ASGI Routing

بعد أن أنشأنا تطبيق chat داخل مشروع Django، وحضّرنا ملف routing.py الخاص به، تأتي مهمة الربط بين طلبات WebSocket الواردة من العملاء وعمليات المستهلك (Consumer) التي ستتعامل مع هذه الاتصالات. في نظام Django Channels، تتم هذه العملية عبر إعداد ASGI routing الذي يوجه البروتوكولات المختلفة (مثل HTTP و WebSocket) إلى نقاط التعامل المناسبة.

إعداد ملف routing.py الخاص بالتطبيق

في البداية، نحتاج لتعريف مسارات WebSocket في تطبيق chat. نفتح ملف chat/routing.py ونكتب فيه كالتالي:

from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
  • هنا استخدمنا تعبيرًا نظاميًا (regex) لتحديد مسار WebSocket، بحيث يربط الطلبات القادمة إلى المسار /ws/chat/<room_name>/ مع المستهلك ChatConsumer.

  • نستخدم as_asgi() لأن المستهلك عبارة عن ASGI application يُمكن استدعاؤه بهذه الطريقة.

إعداد ملف routing.py الرئيسي للمشروع

بعد إعداد مسارات WebSocket الخاصة بالتطبيق، يجب ربطها مع إعدادات المشروع العامة عبر ملف routing.py في مجلد المشروع الرئيسي chat_project/.

ننشئ ملفًا جديدًا chat_project/routing.py (إذا لم يكن موجودًا مسبقًا) ونكتب فيه:

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing

application = ProtocolTypeRouter({
    # التعامل مع طلبات HTTP التقليدية عبر Django
    "http": get_asgi_application(),

    # التعامل مع طلبات WebSocket وتمريرها عبر Middleware للمصادقة
    "websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})
  • هنا نستخدم ProtocolTypeRouter لتوجيه الطلبات بحسب نوع البروتوكول.

  • AuthMiddlewareStack يضيف دعمًا للمصادقة على اتصالات WebSocket بحيث يمكن التحقق من المستخدمين.

  • URLRouter يقوم بتوجيه طلبات WebSocket إلى قائمة المسارات التي عرفناها في chat.routing.

تحديث ملف asgi.py

بعد تجهيز ملف routing الرئيسي، نحتاج لتحديث ملف asgi.py ليستخدم هذا التطبيق الجديد.

نفتح chat_project/asgi.py ونعدل كالتالي:

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import chat.routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat_project.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

يمكن ملاحظة أننا دمجنا تعريف application ليشمل HTTP و WebSocket مع دعم مصادقة المستخدم.

الخطوة الرابعة: بناء Consumer للدردشة

في Django Channels، المستهلك (Consumer) هو مكون البرمجيات المسؤول عن التعامل مع الاتصالات غير المتزامنة مثل WebSocket. يشبه إلى حد كبير الـ View في Django التقليدي، لكنه يتعامل مع الأحداث والبروتوكولات بشكل مباشر، مثل فتح اتصال WebSocket، استقبال الرسائل، إرسالها، وإغلاق الاتصال.

إنشاء Consumer للدردشة

ننتقل إلى ملف chat/consumers.py، ونكتب فيه الكود التالي:

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # استخراج اسم غرفة الدردشة من رابط URL
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'

        # الانضمام إلى مجموعة القناة الخاصة بالغرفة
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        # قبول اتصال WebSocket
        await self.accept()

    async def disconnect(self, close_code):
        # مغادرة مجموعة القناة عند قطع الاتصال
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # استقبال رسالة من WebSocket وإرسالها إلى المجموعة
    async def receive(self, text_data):
        data = json.loads(text_data)
        message = data['message']

        # إرسال الرسالة إلى مجموعة القناة (الغرفة)
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',  # اسم الدالة التي ستعالج الرسالة
                'message': message
            }
        )

    # استقبال رسالة من مجموعة القناة وإرسالها إلى WebSocket
    async def chat_message(self, event):
        message = event['message']

        # إرسال الرسالة للعميل (مستخدمي الغرفة)
        await self.send(text_data=json.dumps({
            'message': message
        }))

شرح آلية العمل

  • connect: عند فتح اتصال WebSocket، يتم استخراج اسم الغرفة، ويتم الانضمام إلى مجموعة القناة التي تمثل غرفة الدردشة. ثم يتم قبول الاتصال.

  • disconnect: عند قطع الاتصال، يترك المستخدم مجموعة القناة.

  • receive: عند استقبال رسالة من العميل، يتم إرسالها إلى المجموعة التي تمثل الغرفة، بحيث تصل لجميع المستخدمين المتصلين بنفس الغرفة.

  • chat_message: تعالج الرسائل التي تصل من المجموعة، وتقوم بإرسالها للعميل الحالي.

إعداد الـ Channel Layer

لكي تعمل group_add و group_send يجب إعداد طبقة القنوات (Channel Layer) التي تستخدم وسيطًا (Backend) مثل Redis لتبادل الرسائل بين العملاء والخادم.

تثبيت Redis وإعداد القناة

  • قم بتثبيت Redis على جهازك (أو استخدم خدمة سحابية).

  • ثبت مكتبة channels_redis:

pip install channels_redis
  • ثم في ملف settings.py أضف الإعداد التالي:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('127.0.0.1', 6379)],
        },
    },
}

يمكنك تعديل 'hosts' حسب إعدادات Redis الخاصة بك.

الخطوة الخامسة: بناء واجهة المستخدم (Frontend) لتطبيق الدردشة باستخدام WebSocket

بعد الانتهاء من إعداد المستهلك (Consumer) الذي يدير اتصالات WebSocket على الخادم، ننتقل إلى جانب العميل (Client) حيث يتم إنشاء واجهة المستخدم التي تتواصل مع الخادم في الوقت الفعلي لإرسال واستقبال الرسائل.

إنشاء صفحة HTML بسيطة للدردشة

داخل تطبيق chat، يمكنك إنشاء مجلد باسم templates/chat/ (إذا لم يكن موجودًا)، ثم إنشاء ملف room.html يحتوي على الكود التالي:

<!DOCTYPE html>
<html>
<head>
    <title>غرفة الدردشة</title>
</head>
<body>
    <h2>غرفة الدردشة: {{ room_name }}</h2>

    <div id="chat-log" style="border:1px solid #ccc; height:300px; overflow-y:scroll; padding:10px;">
    </div>

    <input id="chat-message-input" type="text" size="100" autofocus placeholder="أدخل رسالتك هنا...">
    <button id="chat-message-submit">إرسال</button>

    <script>
        const roomName = "{{ room_name }}";
        const chatLog = document.getElementById('chat-log');
        const input = document.getElementById('chat-message-input');
        const submitButton = document.getElementById('chat-message-submit');

        // إنشاء اتصال WebSocket مع مسار الغرفة
        const chatSocket = new WebSocket(
            'ws://' + window.location.host +
            '/ws/chat/' + roomName + '/'
        );

        // استقبال الرسائل من الخادم
        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            const message = data['message'];
            const messageElement = document.createElement('div');
            messageElement.textContent = message;
            chatLog.appendChild(messageElement);
            chatLog.scrollTop = chatLog.scrollHeight;  // تمرير التمرير لأسفل تلقائيًا
        };

        // التعامل مع إغلاق الاتصال
        chatSocket.onclose = function(e) {
            console.error('اتصال WebSocket مغلق غير متوقع');
        };

        // إرسال الرسالة عند الضغط على زر الإرسال
        submitButton.onclick = function(e) {
            const message = input.value;
            if (message) {
                chatSocket.send(JSON.stringify({
                    'message': message
                }));
                input.value = '';
            }
        };

        // إرسال الرسالة عند الضغط على زر الإدخال (Enter)
        input.addEventListener('keyup', function(e) {
            if (e.key === 'Enter') {
                submitButton.click();
            }
        });
    </script>
</body>
</html>

شرح الواجهة

  • الصفحة تعرض عنوانًا به اسم الغرفة التي يدخلها المستخدم.

  • يوجد صندوق نصي لإدخال الرسائل وزر إرسال.

  • يستخدم جافاسكريبت لإنشاء اتصال WebSocket مع الخادم عبر عنوان الغرفة.

  • كل رسالة تصل من الخادم تُضاف تلقائيًا إلى سجل الدردشة في الصفحة.

  • يمكن للمستخدم إرسال الرسائل بالضغط على الزر أو مفتاح الإدخال.

إعداد View لعرض صفحة الغرفة

في ملف chat/views.py، نضيف دالة بسيطة لعرض الصفحة مع تمرير اسم الغرفة:

from django.shortcuts import render

def room(request, room_name):
    return render(request, 'chat/room.html', {
        'room_name': room_name
    })

وفي ملف chat/urls.py (قم بإنشائه إن لم يكن موجودًا):

from django.urls import path
from . import views

urlpatterns = [
    path('chat/<str:room_name>/', views.room, name='room'),
]

ثم في ملف chat_project/urls.py، استورد وأضف مسارات التطبيق:

from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('chat.urls')),
]

بهذه الخطوات نكون قد أنشأنا واجهة مستخدم بسيطة تمكن المستخدمين من دخول غرفة دردشة وإرسال واستقبال الرسائل بشكل لحظي.

الخطوة السادسة: تشغيل السيرفر واختبار تطبيق الدردشة

تشغيل Redis

بما أن Django Channels يعتمد على Redis كوسيط للرسائل، تأكد من تشغيل خادم Redis على جهازك. يمكنك تشغيل Redis باستخدام الأمر التالي في نظام لينكس أو ماك:

redis-server

أو إذا كنت تستخدم ويندوز، فتأكد من تشغيل خدمة Redis من خلال مدير الخدمات أو عبر البرنامج المخصص.

تشغيل خادم التطوير لـ Django

بعد التأكد من تشغيل Redis، شغّل خادم Django باستخدام الأمر:

python manage.py runserver

يعمل هذا الأمر على تشغيل الخادم مع دعم ASGI، وبالتالي يدعم WebSocket.

اختبار التطبيق

  1. افتح متصفح الإنترنت وادخل إلى العنوان:

http://127.0.0.1:8000/chat/room1/

حيث room1 هو اسم غرفة الدردشة التي تريد الدخول إليها.

  1. افتح نافذتين أو أكثر في المتصفح أو استخدم أجهزة مختلفة للدخول إلى نفس الغرفة.

  2. ابدأ بإرسال رسائل من نافذة إلى أخرى، ستشاهد أن الرسائل تظهر في الوقت الفعلي لجميع المتصلين بالغرفة.

الختام

لقد تعلمنا في هذا الدليل كيفية بناء تطبيق دردشة بسيط باستخدام Django Channels، وذلك عبر الخطوات التالية:

  • إعداد بيئة العمل وتثبيت الحزم المطلوبة.

  • تجهيز ملفات التوجيه (Routing) الخاصة بـ WebSocket.

  • كتابة المستهلك (Consumer) لإدارة الاتصالات والرسائل.

  • إعداد طبقة القنوات باستخدام Redis.

  • بناء واجهة مستخدم بسيطة تعتمد على WebSocket.

  • تشغيل واختبار التطبيق.

يمكن توسيع هذا التطبيق بعدة طرق مثل إضافة المصادقة، حفظ المحادثات في قاعدة البيانات، دعم غرف متعددة مع قوائم مستخدمين، وتحسين تجربة المستخدم.

هذا المشروع يُعد نقطة انطلاق ممتازة لفهم كيفية التعامل مع الاتصالات اللحظية في Django باستخدام Django Channels.

حول المحتوى:

تعلّم كيفية بناء تطبيق دردشة في الوقت الحقيقي باستخدام Django Channels خطوة بخطوة، مع شرح آلية WebSockets، إعداد المستهلكات، وإنشاء واجهة دردشة تفاعلية.