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
Yorum Gönder