Skip to content

Django ile Gerçek Zamanlı(Realtime) Hava Durumu Projesi

Part 1

Bu yazı serisinde Python Django ve external(harici, üçüncü parti) API'ler ile gerçek zamanlı hava durumu uygulaması geliştireceğiz.

Bu serinin sonunda, Python Django ile bir web uygulamasının nasıl geliştirileceğini, web soketlerini Python Django'ya nasıl uygulayacağınızı ve verileri harici API'lerden nasıl alacağınızı öğreneceksiniz.

Proje Başlatma ve Kurulumlar

hava_durumu adında bir klasör açın ve istediğiniz bir kod editörü ile bu klasörü açın. Tavsiye olarak vscode kullanabilirsiniz.

Terminali açarak hava_durumu klasörü içerisinde bir adet python sanal ortam oluşturalım. Yeni bir python projesine başlarken sanal bir ortam oluşturmak iyi bir pratiktir.

$ cd hava_durumu/
# Sanal ortamımı env311 olarak adlandırdım
$ python3.11 -m venv env311
# Sanal ortamı etkinleştirin (macos/linux kullanıcıları)
$ source env311/bin/activate
# Windows kullanıcısıysanız bu şekilde etkinleştirebilirsiniz.
# $ env311\Scripts\activate
- pip install django diyerek django'u kuralım
(env311) $ pip install django
- Kurulum tamamlandıktan sonra bulunduğumuz klasör içerisinde django projesi başlatmak için aşağıdaki komutu yazalım.
$ django-admin startproject src .
- Bu komut ile beraber proje dosya ve klasör yapınız aşağıdaki gibi olmalıdır.
$ tree -I env311 .
.
├── manage.py
└── src
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

1 directory, 6 files
- Burada tree programını kullanarak bulunduğum klasör . (nokta) dosya ve klasörlerini gösteriyorum. env311'i ignore etmesini istiyorum çünkü içerisinde projemize ait olmayan ama projemizi çalıştırmak için kullanacağımız python ve ilgili kütüphaneler mevcut. Yani kalabalık görünmemesi için ignore ediyoruz.

Projeyi çalıştıralım

$ python manage.py runserver

# asagidaki gibi bir console mesaji gorursunuz

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
June 27, 2023 - 13:39:14
Django version 4.2.2, using settings 'src.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
- Tarayıcıdan http://127.0.0.1:8000/ adresine veya http://localhost:8000/ adresine giderek projenin çalıştığını görebilirsiniz.

Migration

Aşağıdaki komutu çalıştırarak django tarafından üretilmiş varsayılan migration dosyalarını veritabanına(sqlite3) gönderelim

$ python manage.py migrate
- Migration Çıktısı
$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
- Admin Kullanıcısı Oluşturmak

Aşağıdaki komut ile yönetici/admin kullanıcısı oluşturabilirsiniz.

$ python manage.py createsuperuser
- İstediğiniz kullanıcı adı(username), email ve parolayı(password) girin ve Enter'a basınız.
Username (leave blank to use 'dev'): admin
Email address: admin@example.com
Password: 
Password (again): 
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
- http://127.0.0.1:8000/admin/ adresine giderek admin paneline giriş yapabilirsiniz.

Yeni bir Uygulama(App) Oluşturalım.

Aşağıdaki komut ile projede yeni bir uygulama oluşturabilirsiniz. Django projeleri birden fazla uygulamadan oluşabilir. Böylece modüler bir çalışma düzenine sahip olur.

$ python manage.py startapp myapp
- Daha sonra myapp adını verdiğimiz uygulamayı settings.py dosyasında INSTALLED_APPS listesine ekleyelim
# hava_durumu/src/settings.py

INSTALLED_APPS = [
    # external apps
    # TODO
    # project apps
    'myapp',
    # django default apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
- settings.py dosyasını açmışken templates yani html dosyalarının bulunduğu klasörü de belirtelim. TEMPLATES adındaki değişkeni bularak içerisindeki DIRS listesine BASE_DIR / "templates" ekliyoruz
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / "templates", # yeni eklendi
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
- HTML Templatelerini Oluşturalım

hava_durumu projemiz içerisine templates adında bir klasör oluşturalım ve içerisine core.html adında bir html dosyası oluşturalım. myapp içerisine templates klasörü onun da içerisine myapp adında bir klasör oluşturalım ve index.html adında bir html dosyasını oluşturalım. URLS dosyaları

myapp içerisine urls.py adında bir python dosyası oluşturalım. İçerisine aşağıdaki path'i ekleyelim

from django.urls import path

# internals
from . import views

urlpatterns = [
    path('', views.index, name="app-index"),
]
- myapp/views.py düzenleme yapalım
from django.shortcuts import render

# Create your views here.


def index(request):
    return render(request, "myapp/index.html")
- hava_durumu/src/urls.py düzenleme yapalım
# src/urls.py
from django.contrib import admin
from django.urls import path
from django.urls import include # yeni

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("myapp.urls")), # yeni
]
- Proje Dosya Klasör Yapısı Şimdi Hali
.
├── db.sqlite3 # otomatik oluştu
├── manage.py
├── myapp
   ├── __init__.py
   ├── __pycache__
   ├── admin.py
   ├── apps.py
   ├── migrations
      ├── __init__.py
      └── __pycache__
   ├── models.py
   ├── templates # yeni
      └── myapp
          └── index.html
   ├── tests.py
   ├── urls.py # yeni
   └── views.py
├── src
   ├── __init__.py
   ├── __pycache__
   ├── asgi.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py
└── templates # yeni
    └── core.html


9 directories, 17 files
- templates/core.html içerisine aşağıdaki kodları yapıştıralım.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>kayace.com | Hava Durumu Django Projesi</title>
    <!-- CSS -->
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <!-- header -->
    <div class="centered">
        {% block core_header%}
        <h3>
            <a href="/">
                Gerçek Zamanlı Hava Durumu Uygulaması
            </a>
        </h3>
        {% endblock core_header%}
    </div>
    <!-- body -->
    <div class="centered">
        {% block core_body%}

        {% endblock core_body%}
    </div>
    <!-- footer -->
    <div class="centered bg-light">
        {% block core_footer%}
        <h4>https://kayace.com</h4>
        {% endblock core_footer%}
    </div>
</body>
</html>
- {% load static %}: Django'nun statik dosyaları yüklemek için statik etiketini yüklemesini sağlar. : css dosyasının yolu. {% static %} etiketi, statik dosyaların yüklendiği dizini temsil eder. {% block core_header%}: Django şablon bloğunun başlangıcını temsil eder. Bu blok daha sonra genişletilebilir veya üzerine yazılabilir. {% endblock core_header%}: Django şablon bloğunun sonunu temsil eder. {% block core_body%}: Django şablon bloğunun başlangıcını temsil eder. Bu blok daha sonra genişletilebilir veya üzerine yazılabilir. {% endblock core_body%}: Django şablon bloğunun sonunu temsil eder. {% block core_footer%}: Django şablon bloğunun başlangıcını temsil eder. Bu blok daha sonra genişletilebilir veya üzerine yazılabilir. {% endblock core_footer%}: Django şablon bloğunun sonunu temsil eder. myapp/templates/myapp/index.html içerisini aşağıdaki gibi düzenleyelim
{% extends 'core.html' %}

{% block core_body %}
<div class="container">
    <div class="row">
        <form>
            <input type="text" id="cityInput" class="textbox" placeholder="Şehir ismi giriniz. Ör; Malatya">
            <button class="mat-button mat-primary-button" type="button" id="checkWeatherButton">
                Kontrol Et
            </button>
        </form>
    </div>
    <div class="row">
        <div class="card-container" id="weather-container">
            <!-- api yanıtı ve html elementleri burada olusturulacak -->
        </div>
    </div>

</div>


{% endblock core_body %}
- {% extends 'core.html' %}: Bu şablonun core.html adlı bir temel şablona dayandığını belirtir. Şablonun bu bölümü, temel şablonun içeriğini genişletir. {% block core_body %}: Django şablon bloğunun başlangıcını temsil eder. Bu blok, temel şablonda tanımlanan core_body bloğunu extend edecektir. {% endblock core_body %}: Django şablon bloğunun sonunu temsil eder. 1 adet text tipinde şehir adını almak için input, 1 adet buton eklenmiştir. STATIC dosyalar

Projede kullanılan css/style.css dosyasına https://github.com/adnankaya/pytrinfo-projects/blob/master/hava_durumu/static/css/style.css adresinden ulaşabilirsiniz. Yazıyı kalabalıklaştırmamak için eklemiyorum. hava_durumu içerisinde static adında bir klasör oluşturun ve içerisine css adında bir klasör ve bunun içine de style.css adında bir dosya oluşturun hava_durumu/static/css/style.css olacak şekilde bir dosya dizini oluşturmalısınız.

(env311) $ mkdir static 
(env311) $ mkdir static/css
(env311) $ touch static/css/style.css
- settings.py içerisine static dosyalarımızın bulunabilmesi için bir konfigürasyon eklememiz gerekiyor
STATIC_URL = 'static/'
# yeni eklendi
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
- Bu aşamaya kadar proje oluşturma ve kurulumları gerçekleştirdik. Basit bir frontend arayüzü geliştirdik.

Part 2

Bu yazımızda https://www.weatherapi.com sitesinin sunduğu hava durumu verilerine API üzerinden erişerek veri çekmeye çalışacağız. Üye olduktan sonra My Account linkine tıklayarak size verilen API Key'e erişebilirsiniz. Bu API Keyi kopyalayın ve proje ana dizininde yani hava_durumu klasöründe .env adında bir dosya açarak içerisine aşağıdaki gibi ekleyin. hava_durumu/.env

export WA_APIKEY="api-keyiniz"
- dot env yani .env dosyalarını python, django projelerinden okuyabilmek için python-dotenv paketini kuralım.
pip install python-dotenv
- Şimdi settings.py dosyasını açalım ve aşağıdaki eklemeleri yapalım.
# settings.py

import os # yeni
from dotenv import load_dotenv # yeni
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv( BASE_DIR/ ".env" ) #yeni
WA_APIKEY = os.getenv("WA_APIKEY") # yeni
- Böylece .env içerisine eklediğimiz environment variables yani ortam değişkenlerine erişebileceğiz. API Keyleri, SECRET_KEY gibi önemli bilgileri proje kodları içerisinde değil de böyle environment variables içerisinde muhafa etmek daha güvenli bir pratiktir.

Ekleme yaptıktan sonra test etmek için shell ortamına geçebilirsiniz.

>>> import os
>>> os.getenv("WA_APIKEY")
'api-keyiniz'
- API Keyinizi kullanarak weatherapi servisine istek atmak için Docs'ta belirtilen URL'i kullanacağız. curl ile örnek bir istek atmak isterseniz API-KEYINIZ yerine kendi api keyinizi yapıştırınız.
curl --location --request GET 'http://api.weatherapi.com/v1/current.json?key=API_KEYINIZ&q=istanbul'
- Cevap olarak istanbul şehrine ait hava durumu bilgisi aşağıdaki gibi JSON formatında dönecektir.
{
    "location": {
        "name": "Istanbul",
        "region": "Istanbul",
        "country": "Turkey",
        "lat": 41.02,
        "lon": 28.96,
        "tz_id": "Europe/Istanbul",
        "localtime_epoch": 1687881139,
        "localtime": "2023-06-27 18:52"
    },
    "current": {
        "last_updated_epoch": 1687880700,
        "last_updated": "2023-06-27 18:45",
        "temp_c": 28.0,
        "temp_f": 82.4,
        "is_day": 1,
        "condition": {
            "text": "Sunny",
            "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png",
            "code": 1000
        },
        "wind_mph": 17.4,
        "wind_kph": 28.1,
        "wind_degree": 30,
        "wind_dir": "NNE",
        "pressure_mb": 1011.0,
        "pressure_in": 29.85,
        "precip_mm": 0.0,
        "precip_in": 0.0,
        "humidity": 40,
        "cloud": 0,
        "feelslike_c": 28.7,
        "feelslike_f": 83.6,
        "vis_km": 10.0,
        "vis_miles": 6.0,
        "uv": 7.0,
        "gust_mph": 14.1,
        "gust_kph": 22.7
    }
}
- Şimdi python, django'dan external(harici) kaynaklara, servislere, API'lere istek atabilmek için requests paketini kuralım
pip install requests
- Kurulum tamamlandıktan sonra python manage.py shell diyerek shell ortamına geçelim ve aşağıdaki kodları yazarak gerekli paketleri import edip api key ve city ile URL'yi oluşturalım
>>> import os
>>> import requests
>>> 
>>> city = "istanbul"
>>> WA_APIKEY = os.getenv("WA_APIKEY")
>>> URL = f"http://api.weatherapi.com/v1/current.json?key={WA_APIKEY}&q={city}"
>>> 
- Şimdi request gönderelim
>>> response = requests.get(URL)
>>> response
<Response [200]>
>>> 
>>> response.json()
{'location': {'name': 'Istanbul', 'region': 'Istanbul', 'country': 'Turkey', 'lat': 41.02, 'lon': 28.96, 'tz_id': 'Europe/Istanbul', 'localtime_epoch': 1687881591, 'localtime': '2023-06-27 18:59'}, 'current': {'last_updated_epoch': 1687880700, 'last_updated': '2023-06-27 18:45', 'temp_c': 28.0, 'temp_f': 82.4, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 17.4, 'wind_kph': 28.1, 'wind_degree': 30, 'wind_dir': 'NNE', 'pressure_mb': 1011.0, 'pressure_in': 29.85, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 40, 'cloud': 0, 'feelslike_c': 28.7, 'feelslike_f': 83.6, 'vis_km': 10.0, 'vis_miles': 6.0, 'uv': 7.0, 'gust_mph': 14.1, 'gust_kph': 22.7}}
- Response statusu 200 ve başarılı bir istek atıldı ve cevap alındı. response.json() diyerek dönen verinin json formatından Python veri yapısına dönüştürülmesini gerçekleştirebiliriz.
>>> data = response.json()
>>> data["location"]["name"]
'Istanbul'
>>> data["current"]["condition"]
{'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}
>>> data["current"]["temp_c"]
28.0
- Dönen cevaptaki veriden istediğimiz alanlara ait değerlere anahtar isimlerini(location, name, current, condition, temp_c vs.) kullanarak erişebiliriz.

Part 3

Bu yazıda weatherapi'dan dönen veriyi kendi uygulamamızda muhafaza edebilmek için model oluşturacağız. Ayrıca gerekli olan view fonksiyonlarını ekleyeceğiz. Ve frontend tarafında hava durumu kayıtlarını göstereceğiz.

myapp/models.py içerisine Weather modelini aşağıdaki gibi ekleyelim.

from django.db import models

# Create your models here.

class Weather(models.Model):
    city = models.CharField(max_length=32)
    temperature = models.CharField(max_length=24)
    description = models.TextField()
    icon = models.CharField(max_length=16)
    updated_date = models.DateTimeField()
    api_response = models.TextField()

    def __str__(self) -> str:
        return self.city
- city alanında şehir ismini muhafaza edeceğiz temerature alanında sıcaklık bilgisini muhafaza edeceğiz. Celcius olarak. description kısmında API'dan gelen bulutlu, yağmurlu gibi durum bilgisini muhafaza edeceğiz icon kısmında API'dan dönen icon url'sini muhafaza edeceğiz updated_date kısmında API'daki en son güncelleme tarihini alıp muhafaza edeceğiz api_response içerisinde de API'dan dönen bütün veriyi muhafaza edeceğiz. python manage.py makemigrations python manage.py migrate komutlarını çalıştıralım ve tabloyu veritabanında oluşturalım myapp/utils.py adında bir python dosyası oluşturalım ve içerisine aşağıdaki modülleri import edip fonksiyonu yazalım
import requests
from django.conf import settings


def request_to_weatherapi(city: str) -> dict:
    URL = f"http://api.weatherapi.com/v1/current.json?key={settings.WA_APIKEY}&q={city}"
    res = requests.get(URL)
    res.raise_for_status()  # HTTP 200 olmayan durum kodları için exception raise eder
    res_json = res.json()
    return {
        "city": res_json["location"]["name"],
        "description": res_json["current"]["condition"]["text"],
        "icon": f'http:{res_json["current"]["condition"]["icon"]}',
        "temperature": res_json["current"]["temp_c"],
        "updated_date": res_json["current"]["last_updated"],
        "api_response": res.text
    }
- Bu fonksiyonu harici API'ya istek atmak ve dönen cevaptan istediğimiz alanlara ait değerleri çıkarmak/export etmek için kullanacağız. Bu fonksiyon city parametresini string türünde kabul edecek şekilde tanımlandı ve dict tipinde bir cevap döneceği belirtilmiştir. Shell ortamında yaptığımız gibi bir URL oluşturuyoruz. WA_APIKEY ve city değişkenleri ile... requests kütüphanesi ile API'ya istek atıp dönen cevabı Python veri yapısına res.json() fonksiyonu ile parse ediyoruz./dönüştürüyoruz. En son istediğimiz alanlara ait değerleri belirlediğimiz anahtar isimler ile (key names) bir dict içerisinde return ediyoruz. myapp/views.py içerisinde aşağıdaki gibi düzenlemeler yapalım.
from django.shortcuts import render
from django.http.response import JsonResponse # yeni
# internals
from .utils import request_to_weatherapi # yeni


def index(request):
    city = request.GET.get("city")
    if city:
        data_dict = request_to_weatherapi(city)
        return JsonResponse(data_dict)

    return render(request, "myapp/index.html")
- Django uygulamamızın index URL'sine(http://localhost:8000) HTTP URL parametre olarak city gönderilmişse(http://localhost:8000/?city=istanbul gibi) eğer bu city bilgisini request_to_weatherapi(city) fonksiyonuna verip harici API'ya istek atıp bu şehrin hava durumu datasını alan ve daha sonra JsonResponse ile bu hava durumu datasını istemciye / client yanıt olarak dönen bir ekleme yaptık. Test edelim! Local adresimize curl ile istek atıyoruz
curl -X GET "http://localhost:8000/?city=ankara"
- Dönen cevap
{"city": "Ankara", "description": "Clear", "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png", "temperature": 17.0, "updated_date": "2023-06-28 03:30", "api_response": "{\"location\":{\"name\":\"Ankara\",\"region\":\"Ankara\",\"country\":\"Turkey\",\"lat\":39.93,\"lon\":32.86,\"tz_id\":\"Europe/Istanbul\",\"localtime_epoch\":1687912859,\"localtime\":\"2023-06-28 3:40\"},\"current\":{\"last_updated_epoch\":1687912200,\"last_updated\":\"2023-06-28 03:30\",\"temp_c\":17.0,\"temp_f\":62.6,\"is_day\":0,\"condition\":{\"text\":\"Clear\",\"icon\":\"//cdn.weatherapi.com/weather/64x64/night/113.png\",\"code\":1000},\"wind_mph\":2.2,\"wind_kph\":3.6,\"wind_degree\":290,\"wind_dir\":\"WNW\",\"pressure_mb\":1013.0,\"pressure_in\":29.91,\"precip_mm\":0.0,\"precip_in\":0.0,\"humidity\":77,\"cloud\":0,\"feelslike_c\":17.0,\"feelslike_f\":62.6,\"vis_km\":10.0,\"vis_miles\":6.0,\"uv\":1.0,\"gust_mph\":9.4,\"gust_kph\":15.1}}"}
- Şimdi bu veriyi kendi veritabanımıza yazmak için düzenlemeler yapalım. myapp/views.py içindeki index fonksiyonunu düzenleyelim
from django.shortcuts import render
from django.http.response import JsonResponse
# internals
from .utils import request_to_weatherapi
from .models import Weather # yeni

def index(request):
    city = request.GET.get("city")
    if city:
        data_dict = request_to_weatherapi(city)
        # veritabanından bu city e ait kaydı getir
        qset = Weather.objects.filter(city__iexact=city)
        # eğer kayıt veritabanında varsa
        if qset.exists():
            # veritabanındaki kaydı, API'den dönen veri ile güncelle
            qset.update(**data_dict)
        else:
            # kayıt yoksa yeni kayıt oluştur.
            new_weather = Weather.objects.create(**data_dict)
        return JsonResponse([data_dict], safe=False)

    return render(request, "myapp/index.html")
- Yorum satırları ile yeni eklemeleri açıkladık. Temel olarak istemci tarafından gönderilen city'ye ait hava durumu kaydı veritabanında varsa kayıt güncellenir, yoksa yeni kayıt eklenir. Frontend tarafında backend response datasına liste olarak ihtiyacımız olacak. Bu yüzden JsonResponse([data_dict], safe=False) düzenlemesini yaparak, data_dict'i bir liste içerisinde [data_dict] şeklinde JsonResponse'a veriyoruz. Test edelim! İstek atmadan önce veritabanındaki kayıtlarımızı kontrol edelim. python manage.py shell ile kontrol ettiğimde henüz herhangi bir kayıt mevcut değil.
>>> from myapp.models import Weather
>>> Weather.objects.all()
<QuerySet []>
- Uygulama testi
curl -X GET "http://localhost:8000/?city=istanbul"
- Cevap
[{"city": "Istanbul", "description": "Clear", "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png", "temperature": 21.0, "updated_date": "2023-06-28 04:15", "api_response": "{\"location\":{\"name\":\"Istanbul\",\"region\":\"Istanbul\",\"country\":\"Turkey\",\"lat\":41.02,\"lon\":28.96,\"tz_id\":\"Europe/Istanbul\",\"localtime_epoch\":1687915448,\"localtime\":\"2023-06-28 4:24\"},\"current\":{\"last_updated_epoch\":1687914900,\"last_updated\":\"2023-06-28 04:15\",\"temp_c\":21.0,\"temp_f\":69.8,\"is_day\":0,\"condition\":{\"text\":\"Clear\",\"icon\":\"//cdn.weatherapi.com/weather/64x64/night/113.png\",\"code\":1000},\"wind_mph\":5.6,\"wind_kph\":9.0,\"wind_degree\":70,\"wind_dir\":\"ENE\",\"pressure_mb\":1011.0,\"pressure_in\":29.85,\"precip_mm\":0.0,\"precip_in\":0.0,\"humidity\":83,\"cloud\":0,\"feelslike_c\":21.0,\"feelslike_f\":69.8,\"vis_km\":10.0,\"vis_miles\":6.0,\"uv\":1.0,\"gust_mph\":10.1,\"gust_kph\":16.2}}"}]
- Veritabanı Kontrolü
>>> Weather.objects.all()
<QuerySet [<Weather: Ankara>]>
- 2 tane daha şehir için istek attıktan sonra belirli bir zaman sonra tekrar Ankara için istek atın ve sonucu gözlemleyin
>>> Weather.objects.all()
<QuerySet [<Weather: Ankara>, <Weather: Malatya>, <Weather: Istanbul>]>
- Django uygulamamızdaki bütün hava durumu kayıtlarını client/istemciye cevap olarak dönmek için aşağıdaki eklemeleri yapalım. myapp/views.py index fonksiyonu
# ...

def index(request):
    city = request.GET.get("city")
    if city:
        data_dict = request_to_weatherapi(city)
        qset = Weather.objects.filter(city__iexact=city)
        if qset.exists():
            qset.update(**data_dict)
        else:
            new_weather = Weather.objects.create(**data_dict)
        return JsonResponse([data_dict], safe=False)
    # eger all parametresi client tarafindan gonderilmisse butun kayitlari cevap olarak döndür
    all = request.GET.get("all")
    if all:
        weathers = Weather.objects.all()
        serialized_data = [
            {
                "id": obj.id,
                "city": obj.city,
                "icon": obj.icon,
                "temperature": obj.temperature,
                "description": obj.description,
                "updated_date": obj.updated_date.strftime('%Y-%m-%d %H:%M')
            }
            for obj in weathers
        ]
        return JsonResponse(serialized_data, safe=False)

    return render(request, "myapp/index.html")
- Test etmek için aşağıdaki şekilde istek atabilirsiniz.
curl -X GET "http://127.0.0.1:8000/?all=true"
- Cevap olarak kayıtlı şehirler için hava durumu kayıtları dönecektir
[
  {
    "id": 1,
    "city": "Ankara",
    "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png",
    "temperature": "17.0",
    "description": "Clear",
    "updated_date": "2023-06-28 03:45"
  },
  {
    "id": 2,
    "city": "Malatya",
    "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png",
    "temperature": "21.0",
    "description": "Clear",
    "updated_date": "2023-06-28 03:45"
  },
  {
    "id": 3,
    "city": "Istanbul",
    "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png",
    "temperature": "21.0",
    "description": "Clear",
    "updated_date": "2023-06-28 03:45"
  }
]
- Frontend tarafında ekleme ve düzenlemeler yapalım.

myapp/templates/myapp/index.html

{% block core_body %}
<div class="container">
    <!-- ... -->
</div>
<script>
    // HTML elements
    const weatherContainer = document.getElementById('weather-container');
    const cityInput = document.getElementById('cityInput');
    const checkWeatherButton = document.getElementById('checkWeatherButton');



    function createElements(data, weatherContainer) {
        // Clear the weather container
        weatherContainer.innerHTML = '';

        // Loop over the weather data and create HTML elements
        data.forEach(weather => {

            const cardContainer = document.createElement('div');
            cardContainer.classList.add('card-container');

            const materialCard = document.createElement('div');
            materialCard.classList.add('material-card');

            const weatherIcon = document.createElement('img');
            weatherIcon.src = `${weather.icon}`;
            weatherIcon.alt = 'Image';

            const temperature = document.createElement('span');
            temperature.classList.add('temperature');
            temperature.textContent = `${weather.temperature}° C`;

            const city = document.createElement('span');
            city.classList.add('city');
            city.textContent = weather.city;

            const description = document.createElement('span');
            description.classList.add('description');
            description.textContent = weather.description;

            const updatedDate = document.createElement('small');
            updatedDate.classList.add('updated_date');
            updatedDate.textContent = weather.updated_date;

            // Append the created elements
            materialCard.appendChild(weatherIcon);
            materialCard.appendChild(temperature);
            materialCard.appendChild(city);
            materialCard.appendChild(description);
            materialCard.appendChild(updatedDate);

            cardContainer.appendChild(materialCard);
            weatherContainer.appendChild(cardContainer);

        });
    }



</script>

{% endblock core_body %}
- weather-container, cityInput, checkWeatherButton elementlerine ID'leri ile erişiyoruz. createElements fonksiyonu: kendisine verilen datayı kullanarak forEach döngüsü ile weatherContainer içerisinde yeni elementler oluşturur. Bu fonksiyona verdiğimiz data parametresi django backend'den dönen response datasıdır ve liste olmalıdır.

Bu script içerisine 1 event listener ve 1 fonksiyon ekleyelim.

// HTML elements
    const weatherContainer = document.getElementById('weather-container');
    const cityInput = document.getElementById('cityInput');
    const checkWeatherButton = document.getElementById('checkWeatherButton');

    // Event listener for the button click
    checkWeatherButton.addEventListener('click', () => {
        const city = cityInput.value;
        const queryParams = { city };

        // Call the getApiDataByParameter function with the query parameters
        getApiDataByParameter(queryParams);
    });

    function getApiDataByParameter(queryParams = {}) {
        // Convert query parameters to URL search parameters
        const urlSearchParams = new URLSearchParams(queryParams);
        const queryString = urlSearchParams.toString();

        // Fetch data from the API with query parameters
        fetch(`/?${queryString}`)
            .then(response => response.json())
            .then(data => {
                if (data) {
                    console.log("getApiDataByParameter -> API response:", data);
                    createElements(data, weatherContainer);
                }
            })
            .catch(error => {
                console.error('Error triggering:', error);
            });
    }
- checkWeatherButton.addEventListener('click' ...) diyerek butona click edildiğinde yapılacak aksiyonu belirtiyoruz. const queryParams = { city }; diyerek city parametresini URL parametre olması için hazırlıyoruz. getApiDataByParameter(queryParams); diyerek fonksiyonu çağırıyoruz. function getApiDataByParameter(queryParams = {}) tanımı ile opsiyonel queryParams kabul eden ve django backend index URL'ye istek atan ve eğer dönen bir data varsa bu data ile createElements(data, weatherContainer); diyerek weatherContainer içerisinde elementler oluşturan bir fonksiyondur.

script'in en alt kısmında aşağıdaki şekilde bir çağrı yaparak django backend'den bütün kayıtları alıp frontend'de gösterebiliriz

// initial call
    getApiDataByParameter({"all":true});
Yeni Eklediğimiz Script'in Tamamı
<script>
    // HTML elements
    const weatherContainer = document.getElementById('weather-container');
    const cityInput = document.getElementById('cityInput');
    const checkWeatherButton = document.getElementById('checkWeatherButton');

    // Event listener for the button click
    checkWeatherButton.addEventListener('click', () => {
        const city = cityInput.value;
        const queryParams = { city };

        // Call the getApiDataByParameter function with the query parameters
        getApiDataByParameter(queryParams);
    });

    function getApiDataByParameter(queryParams = {}) {
        // Convert query parameters to URL search parameters
        const urlSearchParams = new URLSearchParams(queryParams);
        const queryString = urlSearchParams.toString();

        // Fetch data from the API with query parameters
        fetch(`/?${queryString}`)
            .then(response => response.json())
            .then(data => {
                if (data) {
                    console.log("getApiDataByParameter -> API response:", data);
                    createElements(data, weatherContainer);
                }
            })
            .catch(error => {
                console.error('Error triggering:', error);
            });
    }

    function createElements(data, weatherContainer) {
        // Clear the weather container
        weatherContainer.innerHTML = '';

        // Loop over the weather data and create HTML elements
        data.forEach(weather => {

            const cardContainer = document.createElement('div');
            cardContainer.classList.add('card-container');

            const materialCard = document.createElement('div');
            materialCard.classList.add('material-card');

            const weatherIcon = document.createElement('img');
            weatherIcon.src = `${weather.icon}`;
            weatherIcon.alt = 'Image';

            const temperature = document.createElement('span');
            temperature.classList.add('temperature');
            temperature.textContent = `${weather.temperature}° C`;

            const city = document.createElement('span');
            city.classList.add('city');
            city.textContent = weather.city;

            const description = document.createElement('span');
            description.classList.add('description');
            description.textContent = weather.description;

            const updatedDate = document.createElement('small');
            updatedDate.classList.add('updated_date');
            updatedDate.textContent = weather.updated_date;

            // Append the created elements
            materialCard.appendChild(weatherIcon);
            materialCard.appendChild(temperature);
            materialCard.appendChild(city);
            materialCard.appendChild(description);
            materialCard.appendChild(updatedDate);

            cardContainer.appendChild(materialCard);
            weatherContainer.appendChild(cardContainer);

        });
    }

    // initial call
    getApiDataByParameter({"all":true});
</script>
- Kaydettikten sonra tarayıcıdan http://127.0.0.1:8000/ adresini açın ve sonucu gözlemleyin. Veritabanında bulunan kayıtlar adedince card oluşmalı ve bunlar içerisinde hava durumları görülebilmelidir. Input'a şehir ismi girip Kontrol Et butonuna tıkladığınızda sadece o şehir için olan hava durumu bilgisi gelecektir.

Part 4

Serinin bu yazısında websoket üzerinden veri alışverişini projemize entegre ederek gerçek zamanlı veri alışverişini sağlamaya çalışacağız.

Django varsayılan olarak HTTP ile veri alışverişi gerçekleştirecek bir uygulama geliştirme imkanı sağlamaktadır. Uzun süreli bağlantılar(websockets, MQTT, chatbots, amateur radio vs.) gerektiren uygulamalar geliştirebilmek için ekstra kütüphanelere ihtiyaç duyarız. channels kütüphanesi de bunalrdan biridir.

channels, Django'yu alıp WebSockets, chat protokolleri, IoT protokolleri ve daha fazlasını işlemek için djangonun yapabileceklerini HTTP'nin ötesine taşıyan bir projedir. ASGI adlı bir Python spesifikasyonu üzerine inşa edilmiştir.

Bu projede ASGI server olarak da Django Daphne kullanılacaktır.

Bu paketleri kuralım

pip install daphne
pip install channels
- settings.py dosyasını açarak INSTALLED_APPS listesinin ilk sırasına daphne uygulamasını ekleyelim ve ASGI_APPLICATION settings değerini aşağıdaki gibi ekleyelim.
INSTALLED_APPS = [
    # external apps
    'daphne', # yeni
    # project apps
    'myapp',
    # django default apps
    # ...
]

# ...
WSGI_APPLICATION = 'src.wsgi.application'
# Daphne icin bunu ekliyoruz
ASGI_APPLICATION = "src.asgi.application"
- Şimdi src/asgi.py dosyasını açalım. Varsayılan olarak aşağıdaki gibidir.
import os

from django.core.asgi import get_asgi_application

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

application = get_asgi_application()
- Şimdi websocket haberleşme ve http haberleşmenin bir arada yapılabilimesi için asgi.py içerisinde aşağıdaki ekleme ve düzenlemeleri yapalım
import os

from django.core.asgi import get_asgi_application

from channels.routing import ProtocolTypeRouter, URLRouter # yeni
from channels.auth import AuthMiddlewareStack # yeni
from channels.security.websocket import AllowedHostsOriginValidator # yeni
# yeni
from myapp.routing import websocket_urlpatterns as myapp_websocket_urlpatterns

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

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # websocket konfigurasyonlari yazilacak...
     "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(myapp_websocket_urlpatterns))
        ),
})
- Gerekli importları yaptıktan sonra; application adında bir ProtocolTypeRouter nesnesi tanımlıyoruz. Bu, ASGI sunucusuna gelen istekleri belirli protokollere yönlendirmek için kullanılır. WebSocket protokolünün konfigürasyonunu yapmak için "websocket" key'i application nesnesine ekliyoruz. İzin verilen ana bilgisayar kökenlerini doğrulamak için AllowedHostsOriginValidator kullanılır. Ardından, AuthMiddlewareStack ve URLRouter ile birlikte kendi uygulama routing patterns/ rota desenlerimizi (myapp_websocket_urlpatterns) tanımlıyoruz/ekliyoruz Django ile WebSocket desteği sağlamak için gerekli yapılandırmaları içerir. WebSocket istekleri, myapp_websocket_urlpatterns üzerinde tanımlanan route patterns'e yönlendirilir ve AuthMiddlewareStack tarafından yetkilendirilir. Ayrıca, gelen isteklerin izin verilen ana bilgisayar kökenleri tarafından doğrulandığı AllowedHostsOriginValidator kullanılır.

Bu konfigurasyon ile beraber daphne kendini Django'ya entegre edecek ve runserver komutunun kontrolünü ele geçirecektir.

Şimdi myapp içerisinde routing.py adında bir python dosyası oluşturalım ve aşağıdaki gibi düzenleyelim

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r"weather-websocket-data/$", consumers.WeatherConsumer.as_asgi()),
]
- Şimdi de myapp içerisinde consumers.py adında bir python dosyası oluşturalım ve aşağıdaki gibi düzenleyelim
import json

from channels.generic.websocket import WebsocketConsumer

active_consumers = []

class WeatherConsumer(WebsocketConsumer):
    def connect(self):
        active_consumers.append(self)
        self.accept()

    def disconnect(self, close_code):
        active_consumers.remove(self)

    def receive(self, qset):
        self.send(text_data=json.dumps(qset))
- İlk olarak, json modülünü ve channels.generic.websocket modülünden WebsocketConsumer sınıfını import ediyoruz active_consumers adlı bir boş liste tanımlanıyor. Bu liste, bağlı WebSocket consumer objelerini/nesnelerini takip etmek için kullanılacak. WeatherConsumer adlı bir sınıf tanımlanıyor ve WebsocketConsumer sınıfından miras alınıyor. connect metodu, bir WebSocket bağlantısı kurulduğunda çağrılır. Bu metodda, consumer nesnesi active_consumers listesine eklenir ve bağlantı kabul edilir (self.accept()). disconnect metodu, bir WebSocket bağlantısı kesildiğinde çağrılır. Bu metodda, tüketici nesnesi active_consumers listesinden çıkarılır. receive metodu, bir WebSocket mesajı alındığında çağrılır. Bu metod, alınan veriyi JSON formatına dönüştürerek diğer kullanıcılara gönderir (self.send()). Django'da websocket üzerinden veri alışverişini anlamak için aşağıdaki diagram yardımcı olacaktır.

django-websocket-diagram

HTTP istekleri için django urls içerisinde tanımladığımız path'lere gelen istekler view fonksyionları/sınıfları tarafından karşılanır. Websocket istekleri için routing içerisinde tanımladığımız path'lere gelen istekler consumer fonksiyon/sınıfları tarafından karşılanır. Şimdi myapp/index.html içerisine javascript kısmına websocket haberleşme için kullanacağımız fonksiyonu yazalım.

<script>
    // HTML elements
    const weatherContainer = document.getElementById('weather-container');
    const cityInput = document.getElementById('cityInput');
    const checkWeatherButton = document.getElementById('checkWeatherButton');
    // ...

    function getApiDataByParameter(queryParams = {}) {
    // ...
    }

    function createElements(data, weatherContainer) {
    // ...
    }

    // web soket baglantisi olusturmak icin tanimlanan fonksiyon
    function connectWebSocket() {
        const WEBSOCKET_PATH = "/weather-websocket-data/"
        const WEBSOCKET_PROTOCOL_NAME = "ws://"
        const SERVER_DOMAIN = window.location.host;
        _socket = new WebSocket(
            // websocket haberlesme icin URL olusturma. Ör; ws://localhost:8000/my-ws-data/
            WEBSOCKET_PROTOCOL_NAME + SERVER_DOMAIN + WEBSOCKET_PATH
        );
        // backendden veri aliyoruz
        _socket.onmessage = function (event) {
            const data = JSON.parse(event.data);
            // Alinan veriyi isle
            console.log("alınan data: ", data);
        };

        _socket.onclose = function (event) {
            console.error('web socket beklenmedik şekilde kapandı');
        };

        _socket.onerror = function (error) {
            console.error('WebSocket error:', error);
            // hata alma durumunda socket baglantisini kapat ve yeniden baglan
            _socket.close();
            setTimeout(connectWebSocket, 2000);
        };
    }
    /*******************************************************/
    // Connect to the WebSocket
    /*******************************************************/
    connectWebSocket();

    // initial call
    getApiDataByParameter({ "all": true });
</script>

WebSocket için gerekli olan değişkenler tanımlanır. WEBSOCKET_PATH değişkeni, WebSocket yolunu belirtir. WEBSOCKET_PROTOCOL_NAME değişkeni, WebSocket protokolünü (ws://) temsil eder. SERVER_DOMAIN değişkeni, sunucu alan adını (window.location.host) temsil eder. _socket adlı bir WebSocket nesnesi oluşturulur ve belirtilen URL'ye bağlantı yapılır. URL, WEBSOCKET_PROTOCOL_NAME + SERVER_DOMAIN + WEBSOCKET_PATH şeklinde oluşturulur. _socket nesnesinin onmessage olayı dinlenir. Yeni bir mesaj alındığında, alınan veri JSON formatına dönüştürülür (JSON.parse(event.data)) ve data değişkenine atanır. Ardından, data consola yazdırılır. _socket nesnesinin onclose olayı dinlenir. Bağlantı beklenmedik bir şekilde kapatıldığında, bir hata mesajı görüntülenir. _socket nesnesinin onerror olayı dinlenir. Bir WebSocket hatası oluştuğunda, hata mesajı görüntülenir ve bağlantı kapatılır. Ardından, 2 saniye sonra tekrar connectWebSocket fonksiyonu çağrılarak yeniden bağlantı sağlanır. Son olarak, connectWebSocket fonksiyonu çağrılarak WebSocket'e bağlanılır. Bu kod, bir WebSocket bağlantısı kurarak backend verilerini alır ve gelen verilere göre işlemler yapar. Aynı zamanda, bağlantı kesintisi durumunda otomatik olarak yeniden bağlanmayı sağlar.

Buraya kadar ekleme ve düzenlemeler sonucunda, projeyi python manage.py runserver diyerek çalıştırdığınızda, tarayıcı konsolunu da açarak http://localhost:8000 adresine gidebilirsiniz.

Tarayıcı -> Network bölümünden websocket bağlantısını görebilirsiniz.

Eğer django server'i durdurup tarayıcı -> Console'dan kontrol gerçekleştirirseniz "web socket beklenmedik şekilde kapandı" hata mesajını görebilirsiniz.

Websocket Üzerinden Frontend'e Veri Göndermek

myapp/views.py dosyasını açalım ve index view fonksiyonuna trigger parametresi herhangi bir değer ile gönderildiğinde websockete bağlanmış istemcilere {"data": "kayace.com sitesinden zengin içerikler..."} verisini cevap olarak dönen birkaç ekleme yapalım.

# myapp/views.py

# ...
from .consumers import active_consumers # yeni


def index(request):
    city = request.GET.get("city")
    if city:
        # ...
    all = request.GET.get("all")
    if all:
        # ...
    # websocket ile haberleşme yapmak için view üzerinden tetikleme yapacagiz
    triggered = request.GET.get("trigger")
    if triggered:
        for consumer in active_consumers:
            consumer.receive(qset={"data": "kayace.com sitesinden zengin içerikler..."})
        return JsonResponse({"message":"Tetiklendi!"}, safe=False)

    return render(request, "myapp/index.html")
- triggered adında bir değişken, request.GET.get("trigger") ile alınan "trigger" parametresinin değerini temsil eder. Bu parametre varsa, WebSocket üzerinden etkileşimli olarak veri gönderilir. Eğer "trigger" parametresi varsa, active_consumers listesindeki her bir tüketiciye (consumer) consumer.receive() metodu çağrılarak WebSocket üzerinden qset adında bir JSON verisi gönderilir. Ardından, JSON yanıtı olarak {"message": "Tetiklendi!"} gönderilir. Test Edelim

Terminalden curl ile aşağıdaki gibi test edebiliriz. Bir yandan tarayıcınızdan http://localhost:8000 adresine gidin ve tarayıcı console'unu açın.

dev@developers-MacBook-Pro ~ % curl -X GET "http://localhost:8000/?trigger=true"
{"message": "Tetiklendi!"}
dev@developers-MacBook-Pro ~ % curl -X GET "http://localhost:8000/?trigger=true"
{"message": "Tetiklendi!"}
dev@developers-MacBook-Pro ~ % curl -X GET "http://localhost:8000/?trigger=true"
{"message": "Tetiklendi!"}
dev@developers-MacBook-Pro ~ %
- {"message": "Tetiklendi!"} yanıtını backendden alırsınız. Tarayıcı consolunda da websocket üzerinden gelen verileri görebilirsiniz.
alınan data:  {data: 'kayace.com sitesinden zengin içerikler...'}
alınan data:  {data: 'kayace.com sitesinden zengin içerikler...'}
alınan data:  {data: 'kayace.com sitesinden zengin içerikler...'}
- Bu aşamadan sonra istediğimiz senaryoya göre web socket ile frontend'e veri gönderebilir, gerçek zamanlı olarak frontenddeki verileri güncelleyebilir, html elementleri, komponentleri manipüle edebiliriz.

Part 5

Bu yazıda web socket üzerinden gönderilen şehir ismini veritabanında arayıp silen ve daha sonra tekrar web socket üzerinden frontend tarafına kalan şehirlerin güncel listesini gönderen, tamamen sayfayı yenilemeden sadece şehirlere ait hava durumu kartlarının bulunduğu weather container elementini güncelleyen ekleme ve düzenlemeyi yapacağız.

Önce myapp/index.html içerisinde web socket fonksiyonuna 1 satırlık ekleme yapalım.

// web soket baglantisi olusturmak icin tanimlanan fonksiyon
    function connectWebSocket() {
        // ...
        // backendden veri aliyoruz
        _socket.onmessage = function (event) {
            const data = JSON.parse(event.data);
            // Alinan veriyi isle
            console.log("alınan data: ", data);
            // socket uzerinden gelen data ile sehirlerin hava durumu cardlarini yeniden olustur.
            createElements(data, weatherContainer); // [Yeni]
        };

        // ...
    }
- Büyük bir değişiklik yok! Sadece socket üzerinden gelen güncel şehir hava durumu listesini (data) createElements fonksiyonuna veriyoruz ve böylece weatherContainer içerisinde en yeni şehirleri ve hava durumu bilgilerini görüyoruz. Şimdi myapp/views.py dosyasını açalım ve içerisine aşağıdaki eklemeleri ve düzenlemeleri yapalım.
def index(request):
    city = request.GET.get("city")
    if city:
        # ...
    all = request.GET.get("all")
    if all:
        # ...
    # websocket ile haberleşme yapmak için view üzerinden tetikleme yapacagiz
    triggered = request.GET.get("trigger")
    deleted_city = request.GET.get("deleted_city")
    if triggered and deleted_city:
        # silinecek objeyi bul
        obj = Weather.objects.filter(city__iexact=deleted_city)
        if obj.exists():
            obj.delete()
        # butun kayitlari getir
        qset = Weather.objects.all()
        # serialize ederek consumer'a gondermemiz gerekiyor.
        qs_weathers = [
            {
                "id": obj.id,
                "city": obj.city,
                "icon": obj.icon,
                "temperature": obj.temperature,
                "description": obj.description,
                "updated_date": obj.updated_date.strftime('%Y-%m-%d %H:%M')
            }
            for obj in qset
        ]
        for consumer in active_consumers:
            consumer.receive(qset=qs_weathers)
        return JsonResponse({"message":"Tetiklendi!"}, safe=False)

    return render(request, "myapp/index.html")
- Eğer "trigger" ve "deleted_city" parametreleri varsa, Weather modelinde "city" alanı, "deleted_city" parametresine eşit olan kaydı bulur ve siler. Ardından, tüm Weather kayıtlarını qset adlı bir değişkene atar. Weather kayıtlarını döngüyle dolaşarak her bir kaydı bir sözlük/dict olarak qs_weathers listesine ekler. Bu sözlük, kayıtların özelliklerini (id, city, icon, temperature, description, updated_date) içerir. active_consumers listesindeki her bir tüketiciye consumer.receive() metodu çağrılarak WebSocket üzerinden qset adında bir JSON verisi gönderilir. Bu veri, qs_weathers listesinin serialize edilmiş halidir. Son olarak, JSON yanıt olarak {"message": "Tetiklendi!"} gönderilir. Şu anda veritabanımda 2 adet şehir ve hava durumu bilgisi mevcut.
[
    {
        "id": 2,
        "city": "Malatya",
        "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png",
        "temperature": "19.0",
        "description": "Clear",
        "updated_date": "2023-06-28 04:15"
    },
    {
        "id": 3,
        "city": "Istanbul",
        "icon": "http://cdn.weatherapi.com/weather/64x64/night/113.png",
        "temperature": "21.0",
        "description": "Clear",
        "updated_date": "2023-06-28 04:15"
    }
]
- Terminalden trigger ve deleted_city parametrelerini ve değerlerini göndererek, web socket haberleşmesini tetikliyorum. Aynı zamanda tarayıcım ve console da açık bir şekilde gözlemleme yapıyorum.
curl -X GET "http://localhost:8000/?trigger=true&deleted_city=malatya"
- Yukarıdaki tetikleme ile beraber bütün sayfa yeniden yüklenmeden sadece ilgili weather container güncellenecektir.

Ekstra

Periyodik çalışan tasklar yazarak(örneğin celery ile) belirli zaman dilimlerinde kayıtlı olan şehirlere ait hava durumu bilgilerini güncelleyen eklemeler yapabilirsiniz.