Gerçek hayatta Django I: Sinsi ‘sinyal’ yürütümü
“Gerçek hayatta Django” olarak isimlendirdiğim yazı serisinin ilk yazısına hoş geldiniz. Bu seride, engin Django deneyimlerimi sizlerle paylaşıyorum.
İlk yazı olmasından ötürü ufak bir konsept tanımı yapacağım. Genel olarak, Django’da yaptığım hataları, bunları nasıl çözdüğümü ve şöyle böyle kazandığım tecrübeleri sizlere aktaracağım.
Ana amacım, Django ile fazla haşır neşir olmayan veya yeni başlayan geliştiricilerin bu tarz yazıları okuyup “vay anasını” demelerini sağlamak, mümkünse ekşi sözlükte “öğrenildiğinde ufku iki katına çıkaran şeyler” başlığının altında bu yazıların linklerin paylaşılmasını sağlamak (hehe).
Sinsi ‘sinyal’ yürütümü
Bu konuya ekşi sözlükten devam edelim, zira olay alakalı proje üzerinden gerçekleşiyor.
Biliyorsunuz ki, ekşi sözlük’te bir çaylaklık sistemi var. Bu sistemde, 10 entry girerek bir sıraya alınıyorsunuz. Bir süre sonra entry’leriniz inceleniyor; uygun görülürse yazar yapılıyorsunuz. Bu sistemin bir benzeri de django-sozluk projesinde var.
Bizim senaryomuzda, çaylak bir kullanıcının tam 10 adet entry’si var. Kullanıcı entry’lerinden birini silmek istiyor. Entry silindikten sonra kullanıcın toplam entry sayısı 9’a düştüğü için yazar bekleme listesinden şutlanması gerekiyor. Ama o da ne? Bunun için ayrılan bir logic olmasına rağmen, ve ilgili database isteği de çalışmasına rağmen kişi listeden bir türlü çıkarılmıyor.
Aynı request’te çalışan diğer database isteklerine bakıyoruz. Bir diğer istek, kullanıcı entry sildiği için karma puanını düşürüyor (bu olay ekşide yok sanıyorum, amacı kullanıcıları içerik silmekten alıkoymak — öyle bi uydurdum).
Çaylak sırasından şutlama mekanizması bir sinyal sistemi üzerinden çalışıyor
(aslında delete
metodu kullanılmış — ama fark etmiyor). Karma düşürme işlemi
ise view’de halledilmiş.
Peki hata neydi? Hata, karma puanı düşürme ve çaylak yapma database isteklerinin farklı user instance’leri üzerinden yürütülmesi. Şöyle ki:
entry.delete()
çağrılıyor, dolayısıyla ona bağlı sinyal çalışıyor, içerideForeginKey
ile ilişkilendirilmiş kullanıcı şu şekilde kaydediliyor (buradakiself
entry instance’i oluyor):if ( self.author.is_novice and self.author.application_status == "PN" and self.author.entry_count < 10 ): # If the entry count drops less than 10, remove user from novice lookup. self.author.application_status = "OH" self.author.application_date = None self.author.queue_priority = 0 self.author.save() ...
Hemen ardından,
request.user
kullanılarak karma puanı düşürülüyor, (bu varsayım doğru, çünkü kullanıcılar sadece kendilerine ait entry’leri silebiliyorlar; bu durum önden bir izinle kontrol ediliyor):... entry.delete() if not entry.is_draft: # Deduct some karma upon entry deletion. request.user.karma = F("karma") - 1 request.user.save() ...
Sonuçta request.user
ve entry.author
nesneleri birbirinden habersizler.
Dolayısıyla ikisi memory’de sadece ilk kez çekildikleri anki bilgiyi
tutuyorlar. Yani request.user
kaydedildiği zaman memory’de hala çaylak
sırasında olduğu için, daha demin çaylak sırasından çıkardığımız adam tekrar
çaylak sırasına giriyor.
Bunun sebebi save
metodunun varsayılan olarak BÜTÜN field’leri overwrite
etmesi. Bunu önüne geçmek için update_fields
argümanı verilmeliydi. Bu
sayede sadece ilgili field’ler update edilirdi. Karma ve çaylaklık bilgileri de
kesişmediği için sorun olmazdı.
Bana kalırsa update_fields
burada ikincil bir sorun. Esas sorun,
instance’lerin birbirinden bağımsız olduğunun unutulması. Dolayısıyla en
garantici yöntem, bütün operasyonları aynı instance üzerinden yapmak. Tabii
burda işin içinde sinyal olduğu için ne olduğu görmek ekstra zor, zira kod
lineer bir şekilde okunamıyor.
Bu durum özellikle aynı nesneye paralel bir şekilde update’ler yapıyorsanız çok canınızı yakar; ama bu başka bir konu 🤓.
Temel sonuçlar şu şekilde:
- Birden fazla operasyonun aynı anda bir nesneye uygulanması gerekiyorsa, her zaman bir instance üzerinden bunu yönetin.
- Her zaman
update_fields
kullanın. Bu sayede potansiyel hatalardan kaçabilirsiniz (bonus: güzel bir database optimizasyonu). - Sinyal kullanırken bu gibi durumlar için ekstra dikkatli olun, mümkünse nesneler arası sinyaller birbirlerini değiştirmesinler. Hatta mümkünse hiç sinyal yazmayın; sinyal maintain etmek acı verir.
Bu sorunun çözüldüğü commit’i görmek için buraya tıklayın.