Maqola · 2026-08-16 · ~12 daqiqa o‘qiladi · murakkab
Django Channels: ilova ochiq turgan foydalanuvchiga xabar bir soniyada
Mundarija
Birinchi qismda stansiyalarni xaritaga qo'ydik. Endi jiddiyroq talab: liniyada uzilish bo'lsa, operator ogohlantirish kiritadi — va bu xabar ilovasi ochiq turgan har bir foydalanuvchiga bir soniya ichida yetib borishi kerak. HTTP bunga qodir emas: u so'rov-javob protokoli, server o'zi birinchi bo'lib gapira olmaydi.
HTTP'ning chegarasi va WebSocket#
Uch klassik yechim bor:
| Usul | Qanday ishlaydi | Kamchilik |
|---|---|---|
| Polling | Mijoz har 10 soniyada "yangilik bormi?" deb so'raydi | 10 soniyagacha kechikish, behuda so'rovlar oqimi |
| Long polling | So'rov javob kelguncha ochiq turadi | Ulanishlar band, murakkab timeout logikasi |
| WebSocket | Bitta doimiy ikki tomonlama ulanish | Server tomonda maxsus infratuzilma kerak |
Django'ning klassik qismi WSGI ustida ishlaydi — bitta so'rov, bitta javob, ulanish yopildi. WebSocket'ka esa doimiy ochiq ulanish kerak. Shu yerda Django Channels kiradi: u Django'ni ASGI (asinxron interfeys) ustiga o'tkazib, HTTP bilan yonma-yon WebSocket'ni ham boshqarishga imkon beradi.
ASGI nima
WSGI — "bitta so'rov keldi → bitta javob qaytdi" sinxron shartnoma. ASGI — uning avlodi: ulanish hodisalar oqimi sifatida ko'riladi (ulandi, xabar keldi, uzildi...), shuning uchun WebSocket kabi uzun umrli protokollarga mos. Django 3.0+ ASGI'ni tabiiy qo'llaydi.
1-qadam: o'rnatish va ASGI konfiguratsiya#
uv add channels channels-redis uvicorn
Fayl tuzilishi (1-qismdagi skeletga alerts moduli qo'shildi):
config/
├── settings.py
├── urls.py
└── asgi.py ← WebSocket marshrutlash shu yerda
apps/alerts/
├── __init__.py
├── models.py ← Alert modeli
├── consumers.py ← WebSocket "view"lari
├── routing.py ← WebSocket URL'lari
├── signals.py ← saqlanganda broadcast
├── apps.py
└── tests.py
# config/settings.py
INSTALLED_APPS = [
"daphne", # runserver'ni ASGI rejimga o'tkazadi — ro'yxat BOSHIDA tursin
# ...
"channels",
"apps.alerts",
]
ASGI_APPLICATION = "config.asgi.application"
# Channel layer — jarayonlar orasidagi "pochta": worker'lar ko'p bo'lsa ham
# xabar hammaga yetadi. Production'da doim Redis, testda InMemory.
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [("127.0.0.1", 6379)]},
}
}
# config/asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
# MUHIM: get_asgi_application() import'lardan OLDIN chaqiriladi —
# aks holda "Apps aren't loaded yet" xatosi chiqadi
django_asgi_app = get_asgi_application()
from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402
from apps.alerts.routing import websocket_urlpatterns # noqa: E402
application = ProtocolTypeRouter(
{
"http": django_asgi_app, # oddiy Django o'z yo'lida davom etadi
"websocket": URLRouter(websocket_urlpatterns),
}
)
Import tartibi — jim sinadigan joy
get_asgi_application() chaqiruvi consumer import'laridan oldin turishi
shart: u Django app registry'ni yuklaydi. Tartibni buzsangiz xato ba'zan
faqat production ASGI serverda ko'rinadi — lokalda esa hammasi "ishlab
turganday" bo'ladi.
2-qadam: model va consumer#
# apps/alerts/models.py
from django.db import models
class Alert(models.Model):
class Level(models.TextChoices):
INFO = "info", "Ma'lumot"
WARNING = "warning", "Ogohlantirish"
CRITICAL = "critical", "Jiddiy"
title = models.CharField(max_length=200)
body = models.TextField(blank=True)
level = models.CharField(max_length=8, choices=Level.choices, default=Level.INFO)
line = models.ForeignKey(
"metro.Line",
null=True,
blank=True,
on_delete=models.CASCADE,
help_text="Bo'sh qolsa — butun tarmoq bo'yicha ogohlantirish",
)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Consumer — WebSocket dunyosidagi "view". Bizniki juda sodda: ulangan har kim
alerts guruhiga qo'shiladi va guruhga kelgan har xabarni oladi:
# apps/alerts/consumers.py
from channels.generic.websocket import AsyncJsonWebsocketConsumer
ALERTS_GROUP = "alerts"
class AlertsConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
# har ulanishning o'z kanali (self.channel_name) bor;
# uni umumiy guruhga qo'shamiz — broadcast shu guruh orqali
await self.channel_layer.group_add(ALERTS_GROUP, self.channel_name)
await self.accept()
async def disconnect(self, code):
await self.channel_layer.group_discard(ALERTS_GROUP, self.channel_name)
# group_send'dagi {"type": "alert_created"} shu metodga keladi:
# type'dagi nuqta/pastki chiziq -> metod nomi
async def alert_created(self, event):
await self.send_json(event["payload"])
# apps/alerts/routing.py
from django.urls import re_path
from .consumers import AlertsConsumer
websocket_urlpatterns = [
re_path(r"^ws/alerts/$", AlertsConsumer.as_asgi()),
]
3-qadam: yozilgan ogohlantirishni tarqatish#
Operator admin'da Alert saqlaydi — signal uni guruhga uzatadi:
# apps/alerts/signals.py
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.db.models.signals import post_save
from django.dispatch import receiver
from .consumers import ALERTS_GROUP
from .models import Alert
@receiver(post_save, sender=Alert)
def broadcast_alert(sender, instance: Alert, created: bool, **kwargs):
if not created or not instance.is_active:
return
channel_layer = get_channel_layer()
# signal sinxron kontekstda ishlaydi, channel layer esa async —
# ko'prik: async_to_sync
async_to_sync(channel_layer.group_send)(
ALERTS_GROUP,
{
"type": "alert_created", # -> AlertsConsumer.alert_created
"payload": {
"id": instance.id,
"title": instance.title,
"body": instance.body,
"level": instance.level,
"line_id": instance.line_id,
},
},
)
# apps/alerts/apps.py
from django.apps import AppConfig
class AlertsConfig(AppConfig):
name = "apps.alerts"
def ready(self):
from . import signals # noqa: F401 — signal ulanishi uchun import shart
Sinov — ikkita terminal:
# 1-terminal: ASGI server
python manage.py runserver
# 2-terminal: WebSocket mijoz
python -m websockets ws://127.0.0.1:8000/ws/alerts/
# endi admin'dan Alert qo'shing — JSON shu yerga bir zumda tushadi
Async consumer ichida ORM
AsyncJsonWebsocketConsumer ichida to'g'ridan-to'g'ri
Alert.objects.filter(...) chaqirish — SynchronousOnlyOperation
xatosi (yoki undan yomoni: event loop'ni bloklab, HAMMA ulanishlarni
to'xtatib qo'yish). ORM kerak bo'lsa o'rang:
from channels.db import database_sync_to_async →
await database_sync_to_async(list)(Alert.objects.filter(is_active=True)).
Nega "bir manba, ikki kanal"#
Metro'da ogohlantirish ikki auditoriyaga boradi: ilovasi ochiq turganlarga
WebSocket bilan (shu maqola) va ilovasi yopiq turganlarga push-bildirishnoma
bilan (seriyaning keyingi qismi). Ikkalasini alohida yozish o'rniga manba bitta:
Alert saqlanadi → signal ikkala kanalga birdan tarqatadi. Nosinxronlik
("push keldi, ilovada ko'rinmadi") sinfan yo'qoladi — chunki ikkala kanal ham
bitta yozuvdan o'qiydi.
Test#
Channels test uchun maxsus WebsocketCommunicator beradi — haqiqiy server
ko'tarmasdan consumer bilan gaplashamiz:
# apps/alerts/tests.py
import pytest
from channels.layers import get_channel_layer
from channels.testing import WebsocketCommunicator
from apps.alerts.consumers import ALERTS_GROUP, AlertsConsumer
@pytest.mark.asyncio
async def test_connected_client_receives_group_broadcast(settings):
# testda Redis shart emas — xotiradagi layer yetadi
settings.CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}}
communicator = WebsocketCommunicator(AlertsConsumer.as_asgi(), "/ws/alerts/")
connected, _ = await communicator.connect()
assert connected
channel_layer = get_channel_layer()
await channel_layer.group_send(
ALERTS_GROUP,
{"type": "alert_created", "payload": {"title": "Chilonzor: uzilish"}},
)
message = await communicator.receive_json_from()
assert message["title"] == "Chilonzor: uzilish"
await communicator.disconnect()
uv add --dev pytest-asyncio
uv run pytest apps/alerts/ -q
Production eslatmalari#
- ASGI server:
uvicorn config.asgi:application --workers 2(yoki daphne). Gunicorn'ning oddiy worker'i WebSocket'ni tushunmaydi —uvicorn.workers.UvicornWorkerbilan ishlatiladi; - Nginx: WebSocket ulanishi
Upgradesarlavhalari bilan o'tadi —proxy_set_header Upgrade $http_upgrade;vaproxy_set_header Connection "upgrade";(DevOps seriyasida Nginx'ni to'liq sozlaymiz); - Redis majburiy: worker ikkitadan oshdi — InMemory layer yaramaydi, xabar faqat "o'z" worker'idagi mijozlarga borib qoladi. Bu bug juda ayyor: lokalda (1 worker) hech qachon ko'rinmaydi.
Xulosa#
- Server o'zi gapirishi kerak bo'lgan joyda HTTP tugaydi — WebSocket boshlanadi;
- Channels arxitekturasi uch tushunchada: consumer (ulanish egasi), channel layer (jarayonlararo pochta), group (broadcast manzili);
- Sinxron Django kodi bilan async dunyo orasida ko'prik —
async_to_sync/database_sync_to_async; ularni chetlab o'tish jim sinadigan xatolar beradi; - Ogohlantirish bitta yoziladi, kanallarga signal tarqatadi — "bir manba, ikki kanal" printsipi.
Keyingi qismda ikkinchi kanalni quramiz: FCM push-bildirishnomalar — ilovasi yopiq foydalanuvchiga xabar yetkazish, qurilma tokenlari va mehmon-foydalanuvchilar muammosi bilan.