Django JSON ve API Kavramları


1. API Nedir?

API (Application Programming Interface) farklı yazılımların birbiri ile veri alışverişi yapmasını sağlayan arayüzdür.

Örnek:

  • Bir Flutter mobil uygulaması

  • Bir React web uygulaması

  • Bir IoT cihazı

hepsi bir Django API ile konuşabilir.

Günlük Hayattan Örnek

API'yi restoran garsonu gibi düşünebiliriz.

Müşteri → Garson → Mutfak
        ←

Yazılım dünyasında:

Mobil Uygulama → API → Veritabanı
                ←

2. JSON Nedir?

JSON (JavaScript Object Notation) veri taşımak için kullanılan metin tabanlı veri formatıdır.

JSON Örneği

{
  "ad": "Ali",
  "soyad": "Yılmaz",
  "yas": 16,
  "sehir": "Ankara"
}

JSON Özellikleri

  • Anahtar → Değer yapısı vardır

  • İnsan tarafından okunabilir

  • API'lerde en çok kullanılan veri formatıdır


3. Django'da JSON Döndürme

Django'da JSON döndürmek için JsonResponse kullanılır.

Örnek

views.py

from django.http import JsonResponse

def ogrenci_api(request):

    veri = {
        "ad": "Ayşe",
        "soyad": "Demir",
        "yas": 17
    }

    return JsonResponse(veri)

Çalışma Mantığı

Browser
   ↓
Django View
   ↓
Python Dictionary
   ↓
JSON
   ↓
Kullanıcı

4. JSON Liste Döndürme

API'lerde genelde liste şeklinde veri döndürülür.

Örnek

def ogrenciler(request):

    veri = [
        {"ad": "Ali", "yas": 16},
        {"ad": "Ayşe", "yas": 17},
        {"ad": "Mehmet", "yas": 15}
    ]

    return JsonResponse(veri, safe=False)

safe=False Neden Kullanılır?

Django normalde sadece dictionary döndürmeye izin verir.

Liste döndürmek için:

safe=False

kullanılır.


5. Django Model Verisini JSON Yapmak

Gerçek projelerde veri modelden gelir.

Model

models.py

from django.db import models

class Ogrenci(models.Model):

    ad = models.CharField(max_length=100)
    soyad = models.CharField(max_length=100)
    yas = models.IntegerField()

    def __str__(self):
        return self.ad

View

from django.http import JsonResponse
from .models import Ogrenci

def ogrenci_listesi(request):

    ogrenciler = Ogrenci.objects.all()

    veri = list(ogrenciler.values())

    return JsonResponse(veri, safe=False)

Çalışma Diyagramı

Database
   │
   ▼
Django Model
   │
   ▼
QuerySet
   │
   ▼
values()
   │
   ▼
JSON
   │
   ▼
Client

6. Django REST API Mantığı

REST API'de her işlem için HTTP method kullanılır.

HTTP Methodİşlem
GET        Veri okuma
POST        Veri ekleme
PUT        Veri güncelleme
DELETE        Veri silme

7. Basit Django API Örneği

GET API

def ogrenci_list(request):

    if request.method == "GET":

        ogrenciler = Ogrenci.objects.all()

        veri = list(ogrenciler.values())

        return JsonResponse(veri, safe=False)

POST API

import json

def ogrenci_ekle(request):

    if request.method == "POST":

        data = json.loads(request.body)

        Ogrenci.objects.create(
            ad=data["ad"],
            soyad=data["soyad"],
            yas=data["yas"]
        )

        return JsonResponse({"mesaj": "Öğrenci eklendi"})

8. Django API Veri Alma

POST isteğinde veri şu şekilde gelir:

{
 "ad":"Ahmet",
 "soyad":"Kaya",
 "yas":18
}

Python'da okumak için:

data = json.loads(request.body)

9. API Endpoint Nedir?

Endpoint = API adresidir.

Örnek:

/api/ogrenciler/
/api/ogrenci-ekle/
/api/ogrenci-sil/5

urls.py

from django.urls import path
from . import views

urlpatterns = [

    path("api/ogrenciler/", views.ogrenci_list),
    path("api/ogrenci-ekle/", views.ogrenci_ekle),

]

10. API Test Etme

API test etmek için:

Araçlar

  • Postman

  • Thunder Client

  • curl

  • Browser


GET isteği

http://127.0.0.1:8000/api/ogrenciler/

POST isteği

POST /api/ogrenci-ekle/

Body:

{
 "ad":"Ali",
 "soyad":"Yılmaz",
 "yas":16
}

11. Mini Proje

Öğrenci API Sistemi

Özellikler

  • öğrenci listeleme

  • öğrenci ekleme

  • öğrenci silme


Model

class Ogrenci(models.Model):

    ad = models.CharField(max_length=100)
    soyad = models.CharField(max_length=100)
    yas = models.IntegerField()

View

def ogrenci_sil(request, id):

    ogrenci = Ogrenci.objects.get(id=id)
    ogrenci.delete()

    return JsonResponse({"mesaj":"silindi"})

urls.py

urlpatterns = [

    path("api/ogrenciler/", views.ogrenci_list),
    path("api/ogrenci-ekle/", views.ogrenci_ekle),
    path("api/ogrenci-sil/<int:id>/", views.ogrenci_sil),

]

12. Django API Kullanım Alanları

Django API şu sistemlerde kullanılır:

Mobil Uygulamalar

  • Flutter

  • Kotlin

  • Swift

Web Frontend

  • React

  • Vue

  • Angular

Diğer Sistemler

  • IoT cihazları

  • Mikro servisler

  • Veri entegrasyonu


13. Django API Mimarisi

          Mobil Uygulama
                │
                │ JSON
                ▼
            Django API
                │
                │ ORM
                ▼
             Database

14. Profesyonel Projelerde Kullanılan Araç

Gerçek projelerde genelde şu kütüphane kullanılır:

Django REST Framework

Avantajları:

  • otomatik serializer

  • authentication

  • pagination

  • browsable API

  • filtering


15. Django REST Framework Örneği

Serializer:

from rest_framework import serializers
from .models import Ogrenci

class OgrenciSerializer(serializers.ModelSerializer):

    class Meta:
        model = Ogrenci
        fields = "__all__"

View:

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(["GET"])
def ogrenciler(request):

    ogrenciler = Ogrenci.objects.all()

    serializer = OgrenciSerializer(ogrenciler, many=True)

    return Response(serializer.data)

Sonuç

Django ile API geliştirme şu adımlardan oluşur:

1️⃣ Model oluşturma
2️⃣ Veriyi JSON'a çevirme
3️⃣ HTTP method kullanma
4️⃣ URL endpoint oluşturma
5️⃣ API test etme


Öğrenci İçin Alıştırma

Aşağıdaki API'yi yazınız.

Kitap API

Model:

Kitap
- ad
- yazar
- sayfa

API'ler:

GET /api/kitaplar
POST /api/kitap-ekle
DELETE /api/kitap-sil

Bu yapı özellikle mobil uygulama + backend mimarisini anlamak için kullanılır.


1. Django API Genel Mimari Diyagramı

Bir mobil uygulama doğrudan veritabanına bağlanmaz. Arada API katmanı olur.

┌───────────────────────┐
│     Flutter App       │
│  (Mobil Uygulama)     │
└───────────┬───────────┘
            │
            │ HTTP Request
            │ (GET / POST / PUT / DELETE)
            ▼
┌───────────────────────┐
│      Django API       │
│        Views          │
└───────────┬───────────┘
            │
            │ ORM
            ▼
┌───────────────────────┐
│     Django Model      │
│   (Veri Yapısı)       │
└───────────┬───────────┘
            │
            ▼
┌───────────────────────┐
│      Database         │
│   SQLite / PostgreSQL │
└───────────────────────┘

Akış

1️⃣ Flutter veri ister
2️⃣ Django API isteği alır
3️⃣ Model üzerinden veritabanına gider
4️⃣ JSON olarak cevap döner


2. API Request Response Diyagramı

Flutter App
     │
     │  GET /api/ogrenciler
     ▼
Django URL Router
     │
     ▼
Django View
     │
     ▼
Django Model (ORM)
     │
     ▼
Database
     │
     │
     ▲
     │
JSON Response
     │
     ▼
Flutter App UI

3. Django API İç Yapısı

Django içinde API şu dosyalarla çalışır.

Django Project
│
├── models.py
│     Veri yapısı
│
├── views.py
│     API işlemleri
│
├── urls.py
│     API adresleri
│
└── serializers.py
      JSON dönüşümü (DRF)

4. Flutter + Django Veri Akışı

Flutter UI
   │
   │ HTTP Request
   ▼
Flutter Service Layer
(API çağrısı)
   │
   ▼
Internet
   │
   ▼
Django API
   │
   ▼
Database

5. Flutter + Django Tam Proje

Şimdi sana tam mini proje mimarisi gösteriyorum.

Proje

Öğrenci Listeleme Uygulaması

Özellikler

  • öğrenci ekleme

  • öğrenci listeleme

  • Flutter'da gösterme


6. Django Backend Kurulumu

Proje oluşturma

django-admin startproject okul_api
cd okul_api
python manage.py startapp ogrenci

15. Tam Sistem Diyagramı

        Flutter Mobile App
                │
                │ HTTP Request
                ▼
        Django REST API
                │
                │ ORM
                ▼
            Database
                │
                │ JSON
                ▼
        Flutter UI Güncellenir



BÖLÜM 1: Django ile RESTful API Geliştirme (Backend)

Django, normalde kendi HTML şablonlarını (template) sunmak üzere tasarlanmıştır.

Ancak biz burada Django'yu sadece bir veri sağlayıcısı (API) olarak kullanacağız.

Dışarıdan ekstra bir kütüphane (Django REST Framework gibi) kullanmadan,

saf Django ile JSON yanıtları döndüreceğiz.

1. Modelin Oluşturulması (models.py)

İlk olarak veritabanımızda "Öğrenci" tablosunu nasıl tutacağımızı tanımlıyoruz.

Python:
# ogrenci/models.py
from django.db import models

class Ogrenci(models.Model):
    ad = models.CharField(max_length=100)
    soyad = models.CharField(max_length=100)
    yas = models.IntegerField()

    def __str__(self):
        return f"{self.ad} {self.soyad}"

Not: Bu kodu yazdıktan sonra python manage.py makemigrations ve

python manage.py migrate komutlarını çalıştırarak veritabanı tablolarını oluşturmayı unutmayın.

2. Görünümlerin ve İş Mantığının Yazılması (views.py)

Uygulamamızın kalbi burasıdır. Flutter'dan gelecek olan GET, POST, PUT ve DELETE

isteklerini burada karşılayacağız.

Django normalde dışarıdan gelen form verileri için bir güvenlik anahtarı (CSRF Token) bekler.

Flutter gibi dış bir istemciden istek atacağımız için

@csrf_exempt dekoratörünü kullanarak bu sayfaları dış erişime açıyoruz.

Python:
# ogrenci/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import Ogrenci
import json

# 1. READ (Okuma - GET)
def ogrenci_list(request):
    ogrenciler = list(Ogrenci.objects.values())
    return JsonResponse(ogrenciler, safe=False)

# 2. CREATE (Oluşturma - POST)
@csrf_exempt
def ogrenci_ekle(request):
    if request.method == "POST":
        data = json.loads(request.body)
        Ogrenci.objects.create(
            ad=data["ad"],
            soyad=data["soyad"],
            yas=data["yas"]
        )
        return JsonResponse({"mesaj": "Öğrenci başarıyla eklendi"}, status=201)

# 3. UPDATE (Güncelleme - PUT)
@csrf_exempt
def ogrenci_guncelle(request, id):
    if request.method == "PUT":
        try:
            ogrenci = Ogrenci.objects.get(id=id)
            data = json.loads(request.body)
            
            ogrenci.ad = data.get("ad", ogrenci.ad)
            ogrenci.soyad = data.get("soyad", ogrenci.soyad)
            ogrenci.yas = data.get("yas", ogrenci.yas)
            ogrenci.save()
            
            return JsonResponse({"mesaj": "Öğrenci güncellendi"}, status=200)
        except Ogrenci.DoesNotExist:
            return JsonResponse({"hata": "Öğrenci bulunamadı"}, status=404)

# 4. DELETE (Silme - DELETE)
@csrf_exempt
def ogrenci_sil(request, id):
    if request.method == "DELETE":
        try:
            ogrenci = Ogrenci.objects.get(id=id)
            ogrenci.delete()
            return JsonResponse({"mesaj": "Öğrenci silindi"}, status=200)
        except Ogrenci.DoesNotExist:
            return JsonResponse({"hata": "Öğrenci bulunamadı"}, status=404)

3. API Uç Noktalarının Tanımlanması (urls.py)

Son olarak, yazdığımız bu fonksiyonları belirli web adreslerine (URL'lere) bağlıyoruz.

<int:id> yapısı sayesinde, URL'den gelen öğrenci numarasını dinamik olarak yakalayabiliyoruz

(Örneğin: /api/ogrenci-sil/5/).

Python:
# ogrenci/urls.py (veya ana urls.py)
from django.urls import path
from ogrenci import views

urlpatterns = [
    path("api/ogrenciler/", views.ogrenci_list),
    path("api/ogrenci-ekle/", views.ogrenci_ekle),
    path("api/ogrenci-guncelle/<int:id>/", views.ogrenci_guncelle),
    path("api/ogrenci-sil/<int:id>/", views.ogrenci_sil),
]

Django API'miz hazır! python manage.py runserver komutu ile sunucumuzu ayağa kaldırıp Flutter tarafına geçebiliriz.


BÖLÜM 2: Flutter ile Mobil İstemci Geliştirme (Frontend)

Uygulamamızın mobil arayüzünü Flutter ile inşa edeceğiz.

Projenize API istekleri atabilmek için terminalden flutter pub add http komutunu çalıştırarak

http paketini projenize dahil edin.

1. Veri Modeli (ogrenci.dart)

Django'dan gelen JSON verilerini Dart dilinde kullanabileceğimiz Nesnelere dönüştürmemiz

ve kendi verilerimizi Django'ya yollarken tekrar JSON'a çevirmemiz gerekir.

Dart:
// ogrenci.dart
class Ogrenci {
  final int? id; // Yeni kayıtlarda ID olmaz, o yüzden '?' kullanıyoruz
  final String ad;
  final String soyad;
  final int yas;

  Ogrenci({
    this.id,
    required this.ad,
    required this.soyad,
    required this.yas,
  });

  // Gelen JSON verisini Dart nesnesine çevirir (GET işlemleri için)
  factory Ogrenci.fromJson(Map<String, dynamic> json) {
    return Ogrenci(
      id: json['id'],
      ad: json['ad'],
      soyad: json['soyad'],
      yas: json['yas'],
    );
  }

  // Dart nesnesini JSON verisine çevirir (POST ve PUT işlemleri için)
  Map<String, dynamic> toJson() {
    return {
      'ad': ad,
      'soyad': soyad,
      'yas': yas,
    };
  }
}

2. API Servis Katmanı (api_service.dart)

Tüm HTTP işlemlerimizi tek bir merkezden yönetmek, kodun temiz kalmasını sağlar.

Önemli İpucu: Android emülatör kullanıyorsanız 127.0.0.1 yerine 10.0.2.2 kullanmalısınız,

aksi takdirde bağlantı reddedilir.

Dart:
// api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'ogrenci.dart';

class ApiService {
  // Django sunucumuzun adresi
  static const baseUrl = "http://127.0.0.1:8000";

  // 1. GET: Tüm Öğrencileri Getir
  static Future<List<Ogrenci>> ogrencileriGetir() async {
    final response = await http.get(Uri.parse("$baseUrl/api/ogrenciler/"));

    if (response.statusCode == 200) {
      // Türkçe karakter sorunu olmaması için utf8.decode kullanıyoruz
      List data = json.decode(utf8.decode(response.bodyBytes));
      return data.map((e) => Ogrenci.fromJson(e)).toList();
    } else {
      throw Exception("API Hatası: Veriler alınamadı.");
    }
  }

  // 2. POST: Yeni Öğrenci Ekle
  static Future<void> ogrenciEkle(Ogrenci ogrenci) async {
    final response = await http.post(
      Uri.parse("$baseUrl/api/ogrenci-ekle/"),
      headers: {"Content-Type": "application/json; charset=UTF-8"},
      body: json.encode(ogrenci.toJson()),
    );

    if (response.statusCode != 201 && response.statusCode != 200) {
      throw Exception("Ekleme başarısız.");
    }
  }

  // 3. PUT: Öğrenci Güncelle
  static Future<void> ogrenciGuncelle(int id, Ogrenci ogrenci) async {
    final response = await http.put(
      Uri.parse("$baseUrl/api/ogrenci-guncelle/$id/"),
      headers: {"Content-Type": "application/json; charset=UTF-8"},
      body: json.encode(ogrenci.toJson()),
    );

    if (response.statusCode != 200) {
      throw Exception("Güncelleme başarısız.");
    }
  }

  // 4. DELETE: Öğrenci Sil
  static Future<void> ogrenciSil(int id) async {
    final response = await http.delete(
      Uri.parse("$baseUrl/api/ogrenci-sil/$id/")
    );

    if (response.statusCode != 204 && response.statusCode != 200) {
      throw Exception("Silme işlemi başarısız.");
    }
  }
}

3. Kullanıcı Arayüzü (main.dart)

Artık verilerimizi ekrana çizebiliriz. Listeyi API'den asenkron (Future) çektiğimiz için

FutureBuilder kullanacağız. Veriler eklendiğinde veya silindiğinde ekranın güncellenmesi

için bir StatefulWidget kullanıyoruz.

Dart:
// main.dart
import 'package:flutter/material.dart';
import 'ogrenci.dart';
import 'api_service.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Öğrenci Yönetimi',
      theme: ThemeData(primarySwatch: Colors.indigo),
      home: const OgrenciListesiEkrani(),
    );
  }
}

class OgrenciListesiEkrani extends StatefulWidget {
  const OgrenciListesiEkrani({super.key});

  @override
  State<OgrenciListesiEkrani> createState() => _OgrenciListesiEkraniState();
}

class _OgrenciListesiEkraniState extends State<OgrenciListesiEkrani> {
  // Gelecekte gelecek olan veri listemiz
  late Future<List<Ogrenci>> _ogrenciListesiFuture;

  @override
  void initState() {
    super.initState();
    _listeyiYenile();
  }

  // API'ye tekrar istek atarak UI'ı güncelleyen fonksiyon
  void _listeyiYenile() {
    setState(() {
      _ogrenciListesiFuture = ApiService.ogrencileriGetir();
    });
  }

  // Ekleme ve Güncelleme için açılan form penceresi
  void _formGoster([Ogrenci? mevcutOgrenci]) {
    final adController = TextEditingController(text: mevcutOgrenci?.ad ?? '');
    final soyadController = TextEditingController(text: mevcutOgrenci?.soyad ?? '');
    final yasController = TextEditingController(text: mevcutOgrenci?.yas.toString() ?? '');

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text(mevcutOgrenci == null ? 'Öğrenci Ekle' : 'Öğrenci Düzenle'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(controller: adController, decoration: const InputDecoration(labelText: 'Ad')),
              TextField(controller: soyadController, decoration: const InputDecoration(labelText: 'Soyad')),
              TextField(
                controller: yasController, 
                decoration: const InputDecoration(labelText: 'Yaş'),
                keyboardType: TextInputType.number,
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('İptal'),
            ),
            ElevatedButton(
              onPressed: () async {
                final yeniOgrenci = Ogrenci(
                  ad: adController.text,
                  soyad: soyadController.text,
                  yas: int.tryParse(yasController.text) ?? 0,
                );

                if (mevcutOgrenci == null) {
                  await ApiService.ogrenciEkle(yeniOgrenci);
                } else {
                  await ApiService.ogrenciGuncelle(mevcutOgrenci.id!, yeniOgrenci);
                }

                if (mounted) Navigator.pop(context); // Dialogu kapat
                _listeyiYenile(); // Değişiklikleri görmek için listeyi yenile
              },
              child: const Text('Kaydet'),
            ),
          ],
        );
      },
    );
  }

  void _ogrenciSil(int id) async {
    await ApiService.ogrenciSil(id);
    _listeyiYenile();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Öğrenciler')),
      body: FutureBuilder<List<Ogrenci>>(
        future: _ogrenciListesiFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Hata: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            final ogrenciler = snapshot.data!;
            
            if (ogrenciler.isEmpty) {
              return const Center(child: Text('Kayıtlı öğrenci yok.'));
            }

            return ListView.builder(
              itemCount: ogrenciler.length,
              itemBuilder: (context, index) {
                final ogrenci = ogrenciler[index];
                return Card(
                  margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  child: ListTile(
                    leading: CircleAvatar(child: Text(ogrenci.id.toString())),
                    title: Text('${ogrenci.ad} ${ogrenci.soyad}'),
                    subtitle: Text('Yaş: ${ogrenci.yas}'),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        IconButton(
                          icon: const Icon(Icons.edit, color: Colors.blue),
                          onPressed: () => _formGoster(ogrenci),
                        ),
                        IconButton(
                          icon: const Icon(Icons.delete, color: Colors.red),
                          onPressed: () => _ogrenciSil(ogrenci.id!),
                        ),
                      ],
                    ),
                  ),
                );
              },
            );
          }
          return const SizedBox.shrink();
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _formGoster(),
        child: const Icon(Icons.add),
      ),
    );
  }
}


Sonuç

Django ile veritabanı yönetimi yapabilen, JSON üreten bir backend ve

Flutter ile bu verileri tüketip ekranda gösteren dinamik bir frontend geliştirdiniz.

Bu mimari, günümüzde Instagram'dan bankacılık uygulamalarına kadar pek çok modern

projenin temelini oluşturmaktadır.



16. Gerçek Projelerde Kullanılan Mimari

Profesyonel projelerde genelde şu mimari kullanılır:

Flutter
   │
   ▼
Repository Layer
   │
   ▼
API Service
   │
   ▼
Django REST API
   │
   ▼
PostgreSQL

1️⃣ Flutter + Django tam gerçek proje

Örneğin:

  • Öğrenci kayıt sistemi

  • Login sistemi

  • JWT authentication

  • CRUD işlemleri


2️⃣ Profesyonel Django API mimari diyagram seti

Şunları içeren:

  • REST API mimarisi

  • MVC vs MVT diyagramı

  • Django request lifecycle

  • Flutter → API → Database akışı


3️⃣ Flutter + Django okul yönetim sistemi (tam proje)

İçinde

  • öğrenci

  • öğretmen

  • not sistemi

  • giriş sistemi


Flutter + Django REST API

1. Giriş

Modern uygulamalarda genellikle şu mimari kullanılır:

  • Frontend (Mobil / Web) → Flutter

  • Backend (API) → Django

  • Veritabanı → PostgreSQL / SQLite

Bu mimariye Client – Server Architecture denir.


2. Sistem Mimarisi

Flutter Mobile App
        │
        │ HTTP Request (JSON)
        ▼
Django REST API
        │
        │ ORM
        ▼
Database (PostgreSQL / SQLite)
        │
        │ JSON Response
        ▼
Flutter UI Güncellenir

3. REST API Nedir?

REST API, HTTP protokolünü kullanarak veri alışverişi yapan bir mimaridir.

HTTP Methodİşlem
GETVeri alma
POSTVeri ekleme
PUTVeri güncelleme
DELETEVeri silme

Örnek endpointler:

GET    /api/ogrenciler
POST   /api/ogrenci-ekle
PUT    /api/ogrenci-guncelle/1
DELETE /api/ogrenci-sil/1

Modern yazılım dünyasında en çok tercih edilen Client-Server (İstemci-Sunucu) mimarisini kullanarak basit bir öğrenci yönetim sisteminin nasıl inşa edileceğini anlatır.

1. Mimari Yapı ve Veri Akışı

Profesyonel projelerde verinin izlediği yol şöyledir:

  1. Flutter UI: Kullanıcı düğmeye basar (Örn: Öğrenci Listele).

  2. Repository/Service: Flutter, Django API'sine bir HTTP isteği (GET/POST) gönderir.

  3. Django URL & View: İstek karşılanır, veritabanından veri çekilir.

  4. Serializer: Veritabanı nesneleri (Queryset) JSON formatına dönüştürülür.

  5. Response: JSON veri Flutter'a döner ve arayüz güncellenir.


2. Backend: Django REST API Kurulumu

Adım 1: Ortam Hazırlığı

Pardus veya macOS terminalinizde şu komutları çalıştırın:

Bash:
mkdir okul_projesi && cd okul_projesi
python3 -m venv venv
source venv/bin/activate  # macOS/Linux için
pip install django djangorestframework django-cors-headers

Önemli Not: django-cors-headers paketini ekledik çünkü Flutter (Client) ve Django (Server) farklı portlarda çalıştığı için CORS hatası almamak gerekir.

Adım 2: Proje ve App Oluşturma

Bash:
django-admin startproject backend .
python manage.py startapp ogrenci

Adım 3: Ayarlar (settings.py)

INSTALLED_APPS ve MIDDLEWARE kısımlarına eklemeleri yapın:

Python:
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'corsheaders',
    'ogrenci',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # En üste ekle
    # ...
]

CORS_ALLOW_ALL_ORIGINS = True # Geliştirme aşaması için

Adım 4: Model ve Serializer

ogrenci/models.py:

Python:
from django.db import models

class Ogrenci(models.Model):
    ad = models.CharField(max_length=100)
    soyad = models.CharField(max_length=100)
    numara = models.IntegerField(unique=True)

    def __str__(self):
        return f"{self.ad} {self.soyad}"

ogrenci/serializers.py:

Python:
from rest_framework import serializers
from .models import Ogrenci

class OgrenciSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ogrenci
        fields = '__all__'

Adım 5: Views ve URLS

ogrenci/views.py:

Python:
from rest_framework import viewsets
from .models import Ogrenci
from .serializers import OgrenciSerializer

class OgrenciViewSet(viewsets.ModelViewSet):
    queryset = Ogrenci.objects.all()
    serializer_class = OgrenciSerializer

(Not: viewsets kullanarak GET, POST, PUT, DELETE işlemlerini tek seferde hallettik.)

backend/urls.py:

Python:
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from ogrenci.views import OgrenciViewSet

router = DefaultRouter()
router.register(r'ogrenciler', OgrenciViewSet)

urlpatterns = [
    path('admin/', admin.hsite.urls),
    path('api/', include(router.urls)),
]

3. Frontend: Flutter Uygulaması

Adım 1: Bağımlılıklar

pubspec.yaml dosyasına ekleyin:

YAML:
dependencies:
  http: ^1.1.0


1. lib/ogrenci_model.dart

Veri yapısını tanımlayan sınıfımız.

Dart:
class Ogrenci {
  final int? id;
  final String ad;
  final String soyad;
  final int numara;

  Ogrenci({this.id, required this.ad, required this.soyad, required this.numara});

  // JSON'dan Dart nesnesine dönüşüm
  factory Ogrenci.fromJson(Map<String, dynamic> json) => Ogrenci(
    id: json['id'],
    ad: json['ad'],
    soyad: json['soyad'],
    numara: json['numara'],
  );

  // Dart nesnesinden JSON'a dönüşüm (Ekleme işlemi için)
  Map<String, dynamic> toJson() => {
    "ad": ad,
    "soyad": soyad,
    "numara": numara,
  };
}

2. lib/api_service.dart

Django API ile konuşan servis katmanımız.

Dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'ogrenci_model.dart';

class ApiService {
  // ÖNEMLİ: Android Emülatör kullanıyorsanız 127.0.0.1 yerine 10.0.2.2 kullanın.
  // Gerçek cihaz için bilgisayarınızın yerel IP'sini yazın (örn: 192.168.1.x)
  static const String baseUrl = "http://127.0.0.1:8000/api/ogrenciler/";

  // Listeleme (GET)
  Future<List<Ogrenci>> fetchOgrenciler() async {
    try {
      final response = await http.get(Uri.parse(baseUrl));
      if (response.statusCode == 200) {
        // Türkçe karakterler için utf8.decode kullanımı kritiktir
        List jsonResponse = json.decode(utf8.decode(response.bodyBytes));
        return jsonResponse.map((data) => Ogrenci.fromJson(data)).toList();
      } else {
        throw Exception('Veriler alınamadı: ${response.statusCode}');
      }
    } catch (e) {
      throw Exception('Bağlantı hatası: $e');
    }
  }

  // Ekleme (POST)
  Future<bool> ogrenciEkle(Ogrenci ogrenci) async {
    try {
      final response = await http.post(
        Uri.parse(baseUrl),
        headers: {"Content-Type": "application/json"},
        body: json.encode(ogrenci.toJson()),
      );
      return response.statusCode == 201;
    } catch (e) {
      return false;
    }
  }
}

3. lib/main.dart

Uygulamanın ana giriş noktası ve arayüzü.

Dart:
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'ogrenci_model.dart';

void main() {
  runApp(const OkulApp());
}

class OkulApp extends StatelessWidget {
  const OkulApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Okul Yönetim Sistemi',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.indigo, useMaterial3: true),
      home: const OgrenciListeEkrani(),
    );
  }
}

class OgrenciListeEkrani extends StatefulWidget {
  const OgrenciListeEkrani({super.key});

  @override
  State<OgrenciListeEkrani> createState() => _OgrenciListeEkraniState();
}

class _OgrenciListeEkraniState extends State<OgrenciListeEkrani> {
  final ApiService apiService = ApiService();
  late Future<List<Ogrenci>> ogrenciListesi;

  @override
  void initState() {
    super.initState();
    _listeyiYenile();
  }

  void _listeyiYenile() {
    setState(() {
      ogrenciListesi = apiService.fetchOgrenciler();
    });
  }

  // Yeni Öğrenci Ekleme Dialog Penceresi
  void _ogrenciEkleDialog() {
    final adController = TextEditingController();
    final soyadController = TextEditingController();
    final numaraController = TextEditingController();

    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("Yeni Öğrenci Ekle"),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(controller: adController, decoration: const InputDecoration(labelText: "Ad")),
            TextField(controller: soyadController, decoration: const InputDecoration(labelText: "Soyad")),
            TextField(
              controller: numaraController, 
              decoration: const InputDecoration(labelText: "Okul No"),
              keyboardType: TextInputType.number,
            ),
          ],
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context), child: const Text("İptal")),
          ElevatedButton(
            onPressed: () async {
              if (adController.text.isNotEmpty && numaraController.text.isNotEmpty) {
                final yeni = Ogrenci(
                  ad: adController.text,
                  soyad: soyadController.text,
                  numara: int.parse(numaraController.text),
                );
                
                bool basarili = await apiService.ogrenciEkle(yeni);
                if (mounted) {
                  Navigator.pop(context);
                  if (basarili) {
                    _listeyiYenile();
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text("Öğrenci başarıyla eklendi!")),
                    );
                  }
                }
              }
            },
            child: const Text("Kaydet"),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Öğrenci Yönetimi"),
        actions: [
          IconButton(icon: const Icon(Icons.refresh), onPressed: _listeyiYenile),
        ],
      ),
      body: FutureBuilder<List<Ogrenci>>(
        future: ogrenciListesi,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text("Hata: ${snapshot.error}"));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return const Center(child: Text("Henüz kayıtlı öğrenci yok."));
          }

          return ListView.builder(
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              final ogrenci = snapshot.data![index];
              return Card(
                margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                child: ListTile(
                  leading: CircleAvatar(child: Text(ogrenci.ad[0].toUpperCase())),
                  title: Text("${ogrenci.ad} ${ogrenci.soyad}"),
                  subtitle: Text("Numara: ${ogrenci.numara}"),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _ogrenciEkleDialog,
        child: const Icon(Icons.add),
      ),
    );
  }
}

🚀 Çalıştırma Planı

  1. Django'yu Başlat: Terminalde python manage.py runserver komutunun çalıştığından emin olun.

  2. Flutter'ı Başlat: flutter run komutuyla uygulamayı ayağa kaldırın.

  3. Test Et: "+" butonuna basın, bilgileri girin ve "Kaydet" deyin. Liste anında güncellenecektir.


Kodun Önemli Detayları:

  1. CORS Hatası Hatırlatması: Eğer Pardus üzerinde geliştirme yapıyorsan ve emülatör yerine tarayıcıda (Flutter Web) çalıştırıyorsan, Django tarafındaki django-cors-headers ayarlarının yapıldığından emin olmalısın.

  2. utf8.decode: api_service.dart içinde kullandığımız utf8.decode(response.bodyBytes) kısmı, Türkçe karakterlerin (ş, ğ, ı, İ vb.) düzgün görünmesi için kritiktir.

  3. State Management: Bu örnekte basitlik olması açısından setState ve FutureBuilder kullandık. Profesyonel projelerde bunu Provider veya Bloc ile yönetmek daha sürdürülebilir olacaktır.


4. Proje README.md Dosyası

Markdown:
# 🏫 Okul Yönetim Sistemi (Flutter & Django)

Bu proje, bir Computer Science eğitim materyali olarak geliştirilmiştir. Django tabanlı bir API ile Flutter mobil uygulamasının nasıl haberleştiğini gösterir.

## 🚀 Özellikler
- **Django REST Framework** ile tam CRUD desteği.
- **CORS** yapılandırması ile güvenli erişim.
- **Flutter FutureBuilder** ile asenkron veri listeleme.
- **Clean Architecture** prensiplerine uygun katmanlı yapı.

## 🛠 Kurulum

### 1. Backend (Django)
```bash
cd backend
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver

2. Frontend (Flutter)

Bash:
cd frontend
flutter pub get
flutter run

📝 API Endpoints

  • GET /api/ogrenciler/ - Tüm öğrencileri listeler.

  • POST /api/ogrenciler/ - Yeni öğrenci ekler.

  • GET /api/ogrenciler/{id}/ - Detay görüntüler.

👨‍🏫 Eğitmen

Nuri TIRAŞ - Bilişim Teknolojileri Öğretmeni


16. Flutter → Django Veri Akışı

Flutter UI
   │
   │ http.get()
   ▼
API Service
   │
   ▼
Internet
   │
   ▼
Django REST API
   │
   ▼
Serializer
   │
   ▼
Database

17. Profesyonel Projelerde Kullanılan Katmanlar

Gerçek projelerde genelde şu mimari kullanılır:

Flutter
│
├── UI
├── State Management
├── Repository
└── API Service
       │
       ▼
Django REST API
       │
       ▼
Database

18. Projeyi Geliştirme Fikirleri

Bu projeye şu özellikleri ekleyebilirsin:

Login sistemi

JWT Authentication

CRUD

  • öğrenci ekle

  • öğrenci güncelle

  • öğrenci sil

Filtreleme

/api/ogrenciler?yas=16

Pagination

/api/ogrenciler?page=2

19. Gerçek Hayat Kullanımı

Flutter + Django kombinasyonu şu projelerde kullanılır:

  • okul yönetim sistemi

  • e-ticaret uygulaması

  • haber uygulaması

  • sosyal medya uygulaması

  • görev takip uygulaması


Sonuç

Flutter + Django REST API mimarisi şu avantajları sağlar:

  • mobil ve web aynı API'yi kullanabilir

  • backend ve frontend ayrılır

  • ölçeklenebilir sistem kurulur


"ClientException: Failed to fetch" hatası, Flutter Web projelerinde farklı bir porttaki veya alan adındaki bir sunucuya (backend) istek atıldığında karşılaşılan klasik bir CORS (Cross-Origin Resource Sharing) kısıtlamasıdır.

Tarayıcın (Flutter uygulaman) localhost:37145 portunda çalışırken, veriyi 127.0.0.1:8000 adresinden çekmeye çalışıyor. Tarayıcılar, güvenlik politikaları gereği sunucu tarafından açıkça izin verilmediği sürece bu çapraz port/alan adı isteklerini engeller ve Flutter bunu "Failed to fetch" olarak ekrana yansıtır.

Bu sorunu çözmek için backend tarafında tarayıcıya "Bu isteğe izin veriyorum" demen gerekiyor.

Çözüm Adımları

1. Sunucunun Çalıştığından Emin Ol

Öncelikle arka plandaki API sunucunun (127.0.0.1:8000) gerçekten ayakta ve çalışır durumda olduğunu kontrol et.

2. Backend'de CORS Ayarlarını Yapılandır (Django Örneği)

Eğer backend tarafında Django kullanıyorsan, CORS izinlerini ayarlamak için django-cors-headers paketini kullanabilirsin. Geliştirme ortamında bu sorunu aşmak için şu adımları izleyebilirsin:

  • Paketi Kur:

    Terminalinde backend klasörüne giderek paketi yükle:

    Bash:
    pip install django-cors-headers
    
  • settings.py Dosyasını Güncelle:

    Projendeki settings.py dosyasını açıp aşağıdaki eklemeleri yap:

    Python:
    # INSTALLED_APPS içine ekle
    INSTALLED_APPS = [
        # ... diğer uygulamalar ...
        'corsheaders',
    ]
    
    # MIDDLEWARE içine ekle (Mümkün olan en üst sıralara, CommonMiddleware'den önceye koyman önemlidir)
    MIDDLEWARE = [
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware',
        # ... diğer middleware'ler ...
    ]
    
  • İzinleri Tanımla:

    Yine settings.py dosyasının alt kısımlarına geliştirme aşamasında her porttan gelen isteği kabul etmesi için şu satırı ekle:

    Python:
    CORS_ALLOW_ALL_ORIGINS = True
    

    (Not: Uygulamanı canlıya (production) alırken güvenlik amacıyla True yerine CORS_ALLOWED_ORIGINS = ['http://localhost:37145'] şeklinde sadece güvendiğin adresleri tanımlamanı tavsiye ederim. Ancak Flutter Web her çalıştığında port değişebildiği için geliştirme sırasında True yapmak en pratik yoldur.)

Bu ayarları kaydedip Django sunucunu (python manage.py runserver) yeniden başlattıktan sonra, Flutter uygulamanın sayfayı yenilediğinde (Refresh) öğrencilerin listesini sorunsuz bir şekilde çekebildiğini göreceksin.

Yorumlar

Bu blogdaki popüler yayınlar

Pardus Üzerine Django Kurulumu

Python ile Web Geliştirme: Django App Oluşturma