Merhabalar, bu yazımda Django ile session tabanlı alışveriş sepeti yapmaya çalışacağız. Umarım faydalı olur.
Session kavramını bilmiyorsak buraya bakabiliriz. Bu yazımızda session’ları kullanmaya odaklanacağız.
Örneğimize Başlarken
Öncelikle mycart adında standart bir Django projesi oluşturuyorum. Ardından cart adında bir app oluşturuyorum ve ilgili konfigürasyonları;
Template dosyalarımın yolunu set ediyorum.
Static ve Media path’lerimi (ihtiyacımız olabilir, ben genelde her projeye başlarken yapıyorum) ve zaman ile dil ayarlarımı set ediyorum. cart app’i içerisinde index adında bir view ekliyorum ve templates/index.html döndürüyor. kök urls.py içerisinde include ile cart app’i altındaki urls.py’i çağırıyorum ve anasayfa çalışınca cart/urls.py çalışyor. Default migrationları yapıyorum.
Django Session’ları Anlayalım
Django’da request objemiz sayesinde session’ları yönetebiliriz.
- request.session[‘key’] = ‘value’ ile key-value olarak değerleri saklayabiliriz
- request.session.get(‘key’) ile key’ini verdiğimiz değere ulaşabiliriz.
- del request.session[‘key’] ile de session’daki ilgili değeri silebiliriz.
Denemek için bir view (deneme) yazacağım ve buraya istek attığımızda session’a username’i ekleyecek ve tekrar index’e redirect edecek, index view ise index.html’i döndürecek ve eğer session’da username varsa hoşgeldin {{username}} yazacak eğer yok ise kullanıcı bulunamadı! yazacak.
Öncelikle henüz /deneme path’ine gitmeden bir index view’ı çalıştıralım ve bakalım bize ne dönecek.
Tahmin ettiğimiz gibi Kullanıcı Bulunamadı! dönmesidir. Çünkü henüz session’da username adında bir değişken tutulmamaktadır.
Şimdi /deneme path’ine istek atıyorum ve sonucu tahmin ettiğinizi düşünüyorum. Dilerseniz del request.session[‘key’] i deneyebilirsiniz. Daha fazla karışık gözükmemesi için resimleri buraya koymayacağım ^^
Session Ayarları
Django projelerimiz için yapabileceğimiz bazı session ayarları vardır. Bunları settings.py içerisinde set ederiz. Bunları şu şekilde özetleyebiliriz;
- SESSION_ENGINE => Session’ları sakladığımız yeri set etmemizi sağlar. Session’lar Django tarafından default olarak veritabanında (Database Sessions) saklanır. Diğer türevleri şu şekildedir; File-based Sessions (session bilgileri dosya sisteminde saklanır), Cached Sessions (session bilgileri bir cache arkaucunda tutulur. Aralarında en performanslısıdır.), Cached Database Sessions ve Cookie Based Sessions (session bilgileri cookie’ler aracılığıyla saklanır).
- SESSION_COOKIE_AGE => Session çerezlerinin saniye türünden tutulma bilgisidir. Varsayılan olarak 1209600'dır (2 hafta).
- SESSION_COOKIE_DOMAIN => Session tanımlamaları için kullanılacak domain ayarıdır. Bkz.
- SESSION_COOKIE_SECURE => Boolean olarak tanımlanır ve session bilgileri yalnızca bağlantı HTTPS ise tanımlanacaktır.
- SESSION_EXPIRE_AT_BROWSER_CLOSE => Tarayıcı kapandığında session’ın sonlanıp sonlanmayacağı. Boolean olarak saklanır.
- SESSION_SAVE_EVERY_REQUEST => Her istekte session bilgilerinin kaydedilip kaydedilmeyeceğidir. Boolean olarak tutulur. Eğer False ise yalnızca session dictionary’de bir key değiştiğinde yeniden kaydedilir.
- Bütün session ayarları için buraya bakabilirsiniz.
Session Tabanlı Alışveriş Sepeti Örneğimize Geçelim
Evet dilerseniz yavaş yavaş örneğimize başlayalım.
Product Model Oluşturalım
cart/models.py içerisinde Product modelimi oluşturuyorum. Örnek olacağı için herhangi bir category vs eklemeyeceğim. Modeli oluşturduktan sonra cart/admin.py içerisinde admin uygulamamıza kayıt edelim ve migrate işlemini gerçekleştirelim. ImageField kullandığımız için Pillow kütüphanesini yüklememiz gerekmektedir (pip install Pillow).
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True)
image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Product'
verbose_name_plural = 'Products'
def __str__(self):
return self.name
Alışveriş Sepetine Doğru
Alışveriş sepetimizi saklamak için JSON’a çevrilebilecek basit bir yapı kurmamız gerekmektedir. Bu sebeple 3 adet bilgi tutmamız yeterlidir;
- Ürün kimliği (PK, ID veya SLUG)
- Ürün adedi
- Ürün birim fiyatı
Ardından mycart/settings.py içerisinde CART_SESSION_ID değerini tanımlıyoruz. Bu session’larda oluşturduğumuz sepetlerin ID’si olacaktır.
Cart Object Oluşturalım
cart app’i altında cart.py adında bir dosya oluşturuyorum ve içerisine aşağıdaki gibi bir Cart sınıfı oluşturuyorum. Kendisi alışveriş sepetimizi yöneteceğimiz sınıfımız olacak.
Bu sınıftan bir obje oluşturulurken; Cart nesnesinin cart property’sine session’da key’i ‘cart’ olan değer atanıyor. Eğer ki böyle bir değer yok ise session’da key’i ‘cart’ olan boş bir dict ekleniyor. Bu key ise settings.py içerisinde set ettiğimiz CART_SESSION_ID ‘den gelmektedir. Yani burayı şöyle düşünelim; request.session[‘cart’] = {}
from decimal import Decimal
from django.conf import settings
from cart.models import Product
class Cart:
def __init__(self, request):
"""
Alışveriş sepeti oluşturuluyor.
"""
self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
# session'a boş bir alışveriş sepeti oluşturuyoruz
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
Sepete Ekle
Cart objemize aşağıdaki methodları ekliyoruz. Bu sayede sepete ekle işlemlerini gerçekleştirebileceğiz.
add methodumuz 3 parametre almaktadır;
- product => Sepete eklenecek olan ürünün ID’si, bu kısmı slug üzerinden yapmak isterseniz methodu da ona göre güncelleyebilirsiniz.
- quantity => Sepete ilgili üründen kaç adet ekleneceğidir. Default olarak 1 gelmektedir.
- override_quantity => Sepete eklenen ürünün miktarının güncellenip güncellenmeyeceğini belirten parametredir. Eğer miktar yeni ise True değil ise False olarak gönderilmelidir.
def add(self, product, quantity=1, override_quantity=False):
"""
Sepete ürün ekliyoruz, eğer ürün zaten ekli ise miktarını artırıyoruz
"""
product_id = str(product.id) # eğer slug vb üzerinden gidecekseniz burayı değiştirebilirsiniz
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0,
'price': str(product.price)}
if override_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
# işlem yaptığımızı belirtmek için session'u değiştirldi olarak set ediyoruz. Bu Django'ya session'un değiştiğini ve kaydedilmesi gerektiğini söyler.
self.session.modified = True
Sepetten Çıkart
Sepetten bir ürünü çıkartmak için ise aşağıdaki methodu Cart sınıfımıza kazandırıyoruz. Bu methodumuz da 1 adet parametre almaktadır. Gelen Product objesinin id’sini alır ve sepet içerisinden siler.
def remove(self, product):
"""
Sepetten ürün siliyoruz
"""
product_id = str(product.id)
if product_id in self.cart:
del self.cart[product_id]
self.save()
Ürünlere Erişelim
Cart sınıfımıza aşağıdaki methodu kazandırıyoruz ve bu sayede for item in cart şeklinde Cart objemizi iterate edebiliyoruz. Bunun hakkında daha fazla bilgiye buradan erişebilirsiniz.
def __iter__(self):
"""
Sepetteki ürünlerin bilgilerini veritabanından getirebiliriz.
Bunu şu şekilde düşünelim bu fonksiyon
for item in cart
yapılınca çalışacak fonksiyon
"""
product_ids = self.cart.keys()
products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy()
for product in products:
cart[str(product.id)]['product'] = product
for item in cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
Sepette Kaç Ürün Var
Sepetimizde toplam kaç adet ürün olduğunu bulabilmemiz için sınıfımıza aşağıdaki methodu yazıyoruz. Ayrıca bu özel method hakkında daha fazla bilgi sahibi olmak için buraya bakabilirsiniz.
def __len__(self):
"""
Sepetteki ürün adedini alalım
"""
return sum(item['quantity'] for item in self.cart.values())
Sepet Tutarını Alalım
Toplam sepet tutarımızı almak için aşağıdaki methodu Cart sınıfımıza kazandırıyoruz. Sepetteki tüm ürünlerin fiyat ve miktarını çarpar. Sözlüğün elemanları içerisinde gezinir.
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
Sepeti Silelim
Sepet ile işimiz bittiğinde sıfırlamak için session’dan sileriz. Bunun için aşağıdaki methodu sınıfımıza kazandırıyoruz.
def clear(self):
# sepeti session'dan silelim
del self.session[settings.CART_SESSION_ID]
self.save()
Sepette Toplam Kaç Ürün Var
def get_total_items(self):
"""
Sepet toplam ürün adedini verir
"""
return sum(item['quantity'] for item in self.cart.values())
Sepete Her Yerden Erişelim
cart/context_processors.py adlı dosyayı oluşturuyoruz ve sepetimize her yerden erişmek için bir context_processors yazıyoruz.
def cart(request):
return {'cart': Cart(request)}
Bu sayede {{cart}} ile her yerden sepetimize erişebileceğiz. Yazdığımız bu context_processor’u settings.py içerisinde tanıtıryoruz.
Alışveriş sepetimizi (Cart) başarıyla oluşturduk.
Alışveriş Sepetimiz İçin View’larımızı Yazalım
Öncelikle admin panele giderek bir kaç adet ürün ekliyorum. Ardından index view ile birlikte bunları index’e gönderiyorum.
Şimdi gerekli bütün viewlarımızı yazalım. Öncelikle views.py içerisine ilgili importlarımı gerçekleştiriyorum.
from django.shortcuts import render, redirect, get_object_or_404
from cart.models import Product
from cart.cart import Cart
Sepete Ekle
İlgili view’ımı yazıyorum. Örneğin basit olması için miktar ile (quantity) oynama yapmayacağım. Dileyenler kendi denemelerinde güncelleyip kullanabilirler.
def cart_add(request, id):
cart = Cart(request)
product = get_object_or_404(Product, id=id)
cart.add(product=product,
quantity=1,
override_quantity=False)
return redirect('index')
urls.py içerisine view’ımı ekliyorum ve test ediyorum. Eğer başarılı olursak anasayfamızda ürün sayısı 1 olarak gözükmeli.
urlpatterns = [
...
path('cart-add/<int:id>', views.cart_add, name='cart_add'),
...
]
Sepet Detayları
cart_detail adında bir view yazıyorum. Bu sayede sepet detaylarımı görüntüleyebileceğim.
def cart_detail(request):
cart = Cart(request)
context = {
'cart': cart
}
return render(request, 'detail.html', context=context)
detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Alışveriş Sepeti</title>
</head>
<body>
<h1 style="text-align: center;">Sepet Detayları</h1>
{% for item in cart %}
<div style="width: 300px; height: 200px; display: inline-block; border: 1px red dotted; margin-left: 25px;">
<h3>{{ item.product }}</h3>
<img src="{{ item.product.image.url }}" alt="" width="50" height="50">
<hr>
<b>{{ item.price }}</b>
<a href="{% url 'cart_remove' id=item.product.id %}">
<button>Sepetten Çıkar</button>
</a>
</div>
{% endfor %}
</body>
</html>
urls.py
urlpatterns = [
...
path('cart-detail', views.cart_detail, name='cart_detail'),
]
Sepetten Çıkartmak
İlgili view’ımı yazıyorum.
def cart_remove(request, id):
cart = Cart(request)
product = get_object_or_404(Product, id=id)
cart.remove(product)
return redirect('index')
urls.py içerisine ekliyorum
urlpatterns = [
...
path('cart-remove/<int:id>', views.cart_remove, name='cart_remove'),
...
]
Sepet detaylarında ilgili ürünün altındaki Sepetten Çıkart butonuna tıklıyorum. Eğer başarılıysak bizi anasayfaya redirect eder ve mevcut ürün sayısı 0 olarak gösterir.
Sonucu tahmin ediyorsunuzdur muhtemelen. Daha fazla karışıklık olmaması için resmi buraya koymayacağım.
Örneğimizin sonuna geldik. İnternette alışveriş sepeti örneğini araştırırken session tabanlı bir örnek bulamadım. En sonunda bir kaynakta bu örneği buldum. Yukarıdaki kodların hepsi bana ait değildir. Ben öğrenirken zorlandım fakat belki siz biraz daha rahat edersiniz. Böyle önemli bir konuyu elimden geldiğince türkçeleştirmeye çalıştım. Umarım faydalı olmuştur, iyi çalışmalar.
Kodlara buradan ulaşabilirsiniz.