Django Form Uygulaması - Öğrenci Not Takip Sistemi

Bu uygulamada Django kullanarak öğrenci not ekleme, listeleme ve ortalama hesaplama sistemi yapacağız.

Seviye: 11. sınıf + bir tık profesyonel 👌
Amaç: ModelForm + Validation + Bootstrap + Ortalama Hesaplama


📌 1️⃣ Proje Yapısı

Uygulama adı: notlar

notprojesi/
 ├── notlar/
 │    ├── models.py
 │    ├── forms.py
 │    ├── views.py
 │    ├── urls.py
 │    └── templates/
 │         └── notlar/
 │              ├── not_ekle.html
 │              └── not_listesi.html

📌 2️⃣ models.py

from django.db import models

class OgrenciNot(models.Model):
    ad = models.CharField(max_length=100)
    soyad = models.CharField(max_length=100)
    matematik = models.IntegerField()
    fizik = models.IntegerField()
    kimya = models.IntegerField()
    tarih = models.IntegerField()

    def ortalama(self):
        return (self.matematik + self.fizik + self.kimya + self.tarih) / 4

    def durum(self):
        return "Geçti" if self.ortalama() >= 50 else "Kaldı"

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

🔹 Migration

python manage.py makemigrations
python manage.py migrate

📌 3️⃣ forms.py (ModelForm)

from django.forms import ModelForm
from .models import OgrenciNot
from django import forms

class OgrenciNotForm(ModelForm):

    class Meta:
        model = OgrenciNot
        fields = "__all__"

        widgets = {
            "ad": forms.TextInput(attrs={"class": "form-control"}),
            "soyad": forms.TextInput(attrs={"class": "form-control"}),
            "matematik": forms.NumberInput(attrs={"class": "form-control"}),
            "fizik": forms.NumberInput(attrs={"class": "form-control"}),
            "kimya": forms.NumberInput(attrs={"class": "form-control"}),
            "tarih": forms.NumberInput(attrs={"class": "form-control"}),
        }

    def clean(self):
        cleaned_data = super().clean()
        for alan in ["matematik", "fizik", "kimya", "tarih"]:
            not_degeri = cleaned_data.get(alan)
            if not_degeri is not None and (not_degeri < 0 or not_degeri > 100):
                raise forms.ValidationError("Notlar 0-100 arasında olmalıdır!")
        return cleaned_data

📌 4️⃣ views.py

from django.shortcuts import render, redirect
from .forms import OgrenciNotForm
from .models import OgrenciNot

def not_ekle(request):
    if request.method == "POST":
        form = OgrenciNotForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("not_listesi")
    else:
        form = OgrenciNotForm()

    return render(request, "notlar/not_ekle.html", {"form": form})


def not_listesi(request):
    ogrenciler = OgrenciNot.objects.all()
    return render(request, "notlar/not_listesi.html", {"ogrenciler": ogrenciler})

📌 5️⃣ urls.py (app içi)

from django.urls import path
from . import views

urlpatterns = [
    path("ekle/", views.not_ekle, name="not_ekle"),
    path("liste/", views.not_listesi, name="not_listesi"),
]

Ana urls.py içine:

path("notlar/", include("notlar.urls")),

📌 6️⃣ Template – not_ekle.html

<!DOCTYPE html>
<html>
<head>
    <title>Not Ekle</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container mt-5">

<h2>Öğrenci Not Ekle</h2>

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button class="btn btn-success">Kaydet</button>
</form>

</body>
</html>

📌 7️⃣ Template – not_listesi.html

<!DOCTYPE html>
<html>
<head>
    <title>Not Listesi</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container mt-5">

<h2>Öğrenci Not Listesi</h2>

<table class="table table-bordered">
    <tr>
        <th>Ad</th>
        <th>Soyad</th>
        <th>Mat</th>
        <th>Fiz</th>
        <th>Kim</th>
        <th>Tarih</th>
        <th>Ortalama</th>
        <th>Durum</th>
    </tr>

    {% for ogr in ogrenciler %}
    <tr>
        <td>{{ ogr.ad }}</td>
        <td>{{ ogr.soyad }}</td>
        <td>{{ ogr.matematik }}</td>
        <td>{{ ogr.fizik }}</td>
        <td>{{ ogr.kimya }}</td>
        <td>{{ ogr.tarih }}</td>
        <td>{{ ogr.ortalama|floatformat:2 }}</td>
        <td>
            {% if ogr.ortalama >= 50 %}
                <span class="text-success">Geçti</span>
            {% else %}
                <span class="text-danger">Kaldı</span>
            {% endif %}
        </td>
    </tr>
    {% endfor %}
</table>

<a href="{% url 'not_ekle' %}" class="btn btn-primary">Yeni Not Ekle</a>

</body>
</html>

🎯 Sistem Ne Öğretti?

✅ ModelForm kullanımı
✅ Validation (0-100 kontrolü)
✅ Ortalama hesaplama
✅ Bootstrap tablo tasarımı
✅ Redirect kullanımı
✅ Template içinde koşul yazma


🚀 Bir Tık Profesyonel Yükseltme Fikirleri

İstersen bunu şu seviyeye çıkarabiliriz:

  • 🔐 Öğretmen giriş sistemi

  • 📊 Dashboard (Toplam öğrenci, sınıf ortalaması)

  • ✏️ Not Güncelleme / Silme (CRUD tam sistem)

  • 🔎 Arama kutusu

  • ⚡ Ajax ile sayfa yenilenmeden not ekleme

  • 🌐 REST API + Flutter mobil bağlantı (senin seviyene uygun 😉)




🔥 1️⃣ Profesyonel Admin Panel Versiyonu

Django’nun hazır Admin panelini kurumsal hale getiriyoruz.

📌 admin.py

from django.contrib import admin
from .models import OgrenciNot

@admin.register(OgrenciNot)
class OgrenciNotAdmin(admin.ModelAdmin):
    list_display = (
        "ad",
        "soyad",
        "matematik",
        "fizik",
        "kimya",
        "tarih",
        "ortalama_goster",
        "durum_goster"
    )

    search_fields = ("ad", "soyad")
    list_filter = ("matematik", "fizik")
    list_per_page = 10

    def ortalama_goster(self, obj):
        return obj.ortalama()
    ortalama_goster.short_description = "Ortalama"

    def durum_goster(self, obj):
        return obj.durum()
    durum_goster.short_description = "Durum"

🎯 Kazanım

  • Arama kutusu

  • Filtreleme

  • Sayfalama

  • Özel kolonlar

  • Ortalama admin panelde görünüyor


📊 2️⃣ Grafik Dashboard’lu Versiyon

Gerçek dashboard yapıyoruz.

📌 view ekleyelim

from django.db.models import Avg
from django.shortcuts import render
from .models import OgrenciNot

def dashboard(request):
    toplam_ogrenci = OgrenciNot.objects.count()
    sinif_ortalama = OgrenciNot.objects.aggregate(
        Avg("matematik"),
        Avg("fizik"),
        Avg("kimya"),
        Avg("tarih"),
    )

    return render(request, "notlar/dashboard.html", {
        "toplam": toplam_ogrenci,
        "ortalama": sinif_ortalama
    })

📌 dashboard.html

Chart.js kullanıyoruz:

<h2>Dashboard</h2>

<p>Toplam Öğrenci: {{ toplam }}</p>

<canvas id="grafik"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('grafik');

new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Matematik', 'Fizik', 'Kimya', 'Tarih'],
        datasets: [{
            label: 'Sınıf Ortalaması',
            data: [
                {{ ortalama.matematik__avg|default:0 }},
                {{ ortalama.fizik__avg|default:0 }},
                {{ ortalama.kimya__avg|default:0 }},
                {{ ortalama.tarih__avg|default:0 }}
            ]
        }]
    }
});
</script>

🎯 Kazanım

  • Sınıf ortalama grafiği

  • Gerçek zamanlı veri

  • Yönetici paneli görünümü


⚡ 3️⃣ Ajax’lı Versiyon (Sayfa Yenilenmeden Not Ekleme)

📌 View (JSON döndüren)

from django.http import JsonResponse

def ajax_not_ekle(request):
    if request.method == "POST":
        form = OgrenciNotForm(request.POST)
        if form.is_valid():
            form.save()
            return JsonResponse({"status": "ok"})
        return JsonResponse({"status": "error", "errors": form.errors})

📌 Template JS

<script>
document.querySelector("form").addEventListener("submit", function(e){
    e.preventDefault();

    fetch("{% url 'ajax_not_ekle' %}", {
        method: "POST",
        body: new FormData(this),
        headers: {"X-CSRFToken": "{{ csrf_token }}"}
    })
    .then(response => response.json())
    .then(data => {
        if(data.status === "ok"){
            alert("Başarılı!");
            location.reload();
        }
    });
});
</script>

🎯 Kazanım

  • SPA mantığı

  • Modern web yapısı

  • API düşünme mantığı


🔐 4️⃣ Login’li Öğretmen Paneli

Django’nun hazır auth sistemini kullanıyoruz.

📌 settings.py

LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "dashboard"
LOGOUT_REDIRECT_URL = "login"

📌 View koruma

from django.contrib.auth.decorators import login_required

@login_required
def dashboard(request):
    ...

📌 login.html

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button>Giriş Yap</button>
</form>

🎯 Yetki Sistemi

Admin → tüm öğrencileri görür
Öğretmen → sadece kendi öğrencileri (ForeignKey ile bağlanabilir)


🚀 Sonuç: Gerçek Okul Otomasyonu Seviyesi

Bu sistem artık:

✔ Admin panel
✔ Grafik dashboard
✔ Ajax veri girişi
✔ Login sistemi
✔ Yetkilendirme altyapısı

seviyesine çıktı.



Yorumlar

Bu blogdaki popüler yayınlar

Pardus Üzerine Django Kurulumu

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