20 Ağustos 2014 Çarşamba

PyQt Akıllı Diyaloglar

     Akıllı diyalogların akılsız diyaloglardan oluşturulma yönünde bir farkı yoktur. Sadece akılsız diyaloglara oranlara birtakım avantajları vardır. Bu avantajlar ise kullanılacak duruma göre dezavantaj da olabilir. Akıllı ve akılsız diyalogların farklılıklarına kısaca değinelim.

Akılsız diyaloglarda bir pencere açıldığında onay butonları veya çarpı işaretinden kapatılması sonucu sadece kullanıcıya görünmez olur; ama bellekte yer kaplar. Ve her yeni çağrılışında yeni bir pencere oluşturmaktadır. Bunu diyalog sonuna ekleyebileceğimiz del diyalog komutu ile çözebiliriz.
Ayrıca akılsız diyaloglar kendisini çağıran pencere ile etkileşim içinde değildir. Yani yapılan değişiklikler onaylanmadan görüntülenemez. Onaylandığında da kalıcı olarak değişiklik yapılmış olur. Bu durumda önceki duruma dönülemez.

İşte bu bahsettiğimiz durumları akıllı diyaloglar giderir. Akılsız diyaloglarda yapılan değişiklikler, kendisini çağıran pencerede gerçekleşmekte; fakat akıllı diyaloglarda yapılan değişiklikler diyaloğun kendisinde yapılır.
Diyalog kapatıldıktan sonra kendisini sildiğinden bellekte yer kaplama durumu da söz konusu değildir. Bunu da örneğimizde self.setAttribute(Qt.WA_DeleteOnClose) kodu ile sağlayacağız. Bu kodu akılsız diyaloglara koyamıyoruz; çünkü akılsız diyaloglar kendisini çağıran pencereye değişikliklere dair veri aktarmaktadır. Oysaki bu değişiklikler akıllı diyaloglarda kendi içinde halledilmiştir.

Şimdi bir metnin yazı tipini akıllı diyalog ile değiştirebileceğimiz bir örnek yapalım. Kodlarımız şu şekilde:
from PyQt4.QtCore import *
from PyQt4.QtGui import *

simgeler = 'D:/Simgeler/orange-moonlight-icons/png/64x64/'
# simgeler => http://dryicons.com/free-icons/preview/orange-moonlight-icons/

class yaziTipiDiyalogu(QDialog):
    def __init__(self, parent=None):
        super(yaziTipiDiyalogu, self).__init__(parent)
        self.parent = parent

        self.setAttribute(Qt.WA_DeleteOnClose)
        # Üst satırda yazmış olduğumuz kod pencere kapatılırken doğrudan silinmesini sağlar.
        # Böylelikle bellekte yer kaplamamış olur.

        self.yaziTipiListesi = QFontComboBox()
        self.yaziTipiListesi.setCurrentFont(QFont(self.parent.yaziTipi))

        butonKutusu = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        #Butonların üstündeki yazıları Tamam ve İptal olarak değiştirelim.
        butonKutusu.button(QDialogButtonBox.Ok).setText('Tamam')
        butonKutusu.button(QDialogButtonBox.Cancel).setText('İptal')
        self.connect(butonKutusu, SIGNAL('accepted()'), self.kabul)
        self.connect(butonKutusu, SIGNAL('rejected()'), self.reject)

        pencere = QGridLayout() # Izgara pencere düzeni
        pencere.addWidget(QLabel('Yazı Tipi Seçimi: '), 0, 0)
        pencere.addWidget(self.yaziTipiListesi, 0, 1)
        pencere.addWidget(butonKutusu, 1, 0, 1, 2)
        self.setLayout(pencere)

        self.setWindowTitle("Yazı Tipini Ayarla")
        self.setWindowIcon(QIcon(simgeler + 'edit.png'))

    def kabul(self):
        self.parent.yaziTipi = self.yaziTipiListesi.currentFont().family()
        self.parent.label.setText(self.parent.metin % self.parent.yaziTipi)
        QDialog.accept(self)

class akilliDiyaloglar(QDialog):
    def __init__(self, parent=None):
        super(akilliDiyaloglar, self).__init__(parent)
        self.yaziTipi = 'Comic Sans MS'
        self.metin = '
PythonDersleri.com
' self.label = QLabel(self.metin % self.yaziTipi) yatayKutu = QVBoxLayout() yatayKutu.addWidget(self.label) yaziTipiDugme = QPushButton('Yazı Tipini Değiştir') self.connect(yaziTipiDugme, SIGNAL('pressed()'), self.yaziTipiDegistir) yatayKutu.addWidget(yaziTipiDugme) self.setLayout(yatayKutu) self.setWindowTitle("Akıllı Diyaloglar") self.setWindowIcon(QIcon(simgeler + 'page.png')) def yaziTipiDegistir(self): diyalog = yaziTipiDiyalogu(self) diyalog.show() uygulama = QApplication([]) anaPencere = akilliDiyaloglar() anaPencere.show() uygulama.exec_()
Kodlarımızı çalıştırdığımızda karşımıza şöyle bir pencere gelecektir:
"Yazı Tipini Değiştir" butonuna tıkladığımızda ise pencere şu şekle gelecektir:
Buradaki kodlarımızda "Tamam" butonuna tıkladığımızda oluşan kabul durumunu kabul metodu ile bu diyalog içinde gerçekleştirdik. Bu sayede pencereyi kapattıktan sonra bellekten silebiliyoruz. İptal denildiğinde ise hiç bir değişiklik olmadan pencereyi kapatıp bellekten silebiliyoruz.

Akıllı diyaloglarla ilgili dikkatinizi çekmek istediğim bir nokta daha var. "Yazı Tipini Ayarla" penceremizi sürükleyip ana pencere üzerinden başka bir yere çektikten sonra ana pencere üzerinde "Yazı Tipini Değiştir" butonuna tekrar tıklamanıza yani ana pencere üzerinde işlem yapmanıza izin verir, bu durum akılsız diyaloglarda yoktur. Bu durum yapacağınız uygulamalara göre avantaj ya da dezavantaj oluşturabilir. Buna dikkat etmekte fayda var.

Akıllı diyaloglarda bir de kullanıcının yaptığı değişikliklerin kalıcı olmadığından bahsettik. Peki bu durum nasıl oluyor? Penceremize bazı uygulamalarda görmüş olabileceğiniz "Uygula" ve "Sıfırla" isminde iki buton daha eklememiz gerekiyor. Bu butonları da ekledikten sonra bütün butonları metotlara bağlamamız gerekiyor.
# Butonların eklenmiş hali
butonKutusu = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply | QDialogButtonBox.Reset)
butonKutusu.button(QDialogButtonBox.Apply).setText('Uygula')
# Butonun üzerindeki yazıyı "Uygula" yaptık
butonKutusu.button(QDialogButtonBox.Reset).setText('Sıfırla')
# Butonun üzerindeki yazıyı "Sıfırla" yaptık 


# Eklediğimiz butonların sinyallerini metotlara bağlıyoruz
self.connect(butonKutusu.button(QDialogButtonBox.Apply), SIGNAL('clicked()'), self.uygula)
self.connect(butonKutusu.button(QDialogButtonBox.Reset), SIGNAL('clicked()'), self.sifirla)


# Tüm butonlara ait metotların son hali de bu şekilde olmalı
def kabul(self):
        self.parent.yaziTipi = self.yaziTipiListesi.currentFont().family()
        self.parent.label.setText(self.parent.metin % self.parent.yaziTipi)
        QDialog.accept(self)

def uygula(self):
        self.parent.label.setText(self.parent.metin % self.yaziTipiListesi.currentFont().family())

def sifirla(self):
        self.parent.label.setText(self.parent.metin % self.parent.yaziTipi)
        self.yaziTipiListesi.setCurrentFont(QFont(self.parent.yaziTipi))

def reject(self):
        self.parent.label.setText(self.parent.metin % self.parent.yaziTipi)
        QDialog.reject(self)
Uygulamamıza yukarıdaki kodları da ekleyip çalıştırdığımızda bahsetmiş olduğumuz durumları gerçekleştirebilir hale gelmekteyiz. Pencerelere ait ekran görüntüsü de şu şekilde olacaktır:
Burada listeden yazı tipini seçtikten sonra Uygula diyerek kalıcı değişiklik yapmadan ve pencere kapanmadan yaptığımız değişiklikleri görebiliriz. Sıfırla diyerek de pencereyi açtığımızdaki ilk haline döndürebiliriz.


Kaynaklar:
http://zetcode.com/gui/pyqt4/dialogs/
Mustafa Başer - Python Kitabı

14 Ağustos 2014 Perşembe

PyQt Akılsız Diyaloglar

Akılsız diyaloglara neden akılsız diyalog denildiğinden çok bu diyalogların çalışma şekline değinelim. Akılsız diyaloglarda parçacıkların değerleri çağırıcı işlev tarafından ayarlanır ve yapılan değişiklikler de çağırıcı işlev tarafından yakalanır. Bu diyaloglar yalnızda parçacıkları ekranda görüntüleme işlevi görür. Ayrıca bu penceredeki işlem tamamlanmadan bu pencereyi çağıran ana pencerede başka bir işlem yapılamaz. Öncelikle açılan akılsız diyalog kapatılmalıdır.

Şimdi ana penceremiz üzerinde var olan bir label'ımızın içindeki yazının rengini bir alt pencere ile seçebileceğimiz bir örnek kod yazalım:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

simgeler = 'D:/Simgeler/orange-moonlight-icons/png/64x64/'
# simgeler => http://dryicons.com/free-icons/preview/orange-moonlight-icons/

class yaziRengiDlg(QDialog):
    def __init__(self, parent=None):
        super(yaziRengiDlg, self).__init__(parent)
        izgara = QGridLayout()
        izgara.addWidget(QLabel('Yazı Rengi: '), 0, 0)
        self.yaziRengiListe = QComboBox()
        self.yaziRengiListe.addItems(('Red', 'Blue', 'Green', 'Yellow', 'Brown'))
        izgara.addWidget(self.yaziRengiListe, 0, 1)

        onayButonu = QPushButton('Tamam')
        redButonu = QPushButton('İptal')
        onayButonu.setDefault(True)

        butonKutusu = QHBoxLayout()
        butonKutusu.addWidget(onayButonu)
        butonKutusu.addWidget(redButonu)
        izgara.addLayout(butonKutusu, 1, 0, 1, 2)

        self.connect(onayButonu, SIGNAL('pressed()'), self.accept)
        self.connect(redButonu, SIGNAL('pressed()'), self.reject)

        self.setLayout(izgara)
        self.setWindowTitle('Yazı Rengini Seç')
        self.setWindowIcon(QIcon(simgeler + 'paint_brush.png'))

class akilsizDiyaloglar(QDialog):
    def __init__(self, parent=None):
        super(akilsizDiyaloglar, self).__init__(parent)
        self.yaziRengi = 'Blue'
        self.metin = 'PythonDersleri.com'
        self.label = QLabel(self.metin % self.yaziRengi)
        kutu = QVBoxLayout()
        kutu.addWidget(self.label)
        yaziRengiButonu = QPushButton('Yazı Rengini Değiştir')
        self.connect(yaziRengiButonu, SIGNAL('pressed()'), self.yaziRengiDegistir)
        kutu.addWidget(yaziRengiButonu)

        self.setLayout(kutu)
        self.setWindowTitle('Akılsız Diyaloglar')
        self.setWindowIcon(QIcon(simgeler + 'page.png'))

    def yaziRengiDegistir(self):
        diyalog = yaziRengiDlg()
        yaziRengiIndis = diyalog.yaziRengiListe.findText(self.yaziRengi)
        diyalog.yaziRengiListe.setCurrentIndex(yaziRengiIndis)
        if diyalog.exec_():
            self.yaziRengi = diyalog.yaziRengiListe.currentText()
            self.label.setText(self.metin % self.yaziRengi)

uygulama = QApplication([])
anaPencere = akilsizDiyaloglar()
anaPencere.show()
uygulama.exec_()
Bu programı çalıştırdığımızda ve "Yazı Rengini Değiştir" butonuna tıkladığımızda şu şekilde bir pencere gelecektir.

Burada da farklı olarak yazmış olduğumuz kodlardan bahsedelim.

4. Satır = Bu satırda simge dosyalarımın bulunduğu dizinin yolunu bir değişkene atadık. Programımızda kullandığımız simgeleri 5. satırda belirtmiş olduğumuz linkten ücretsiz temin edebilirsiniz.
30 ve 46. Satır = Bu satırlarda belirtilen QIcon nesneleri ile pencerelerin sol üst köşesinde görünen simgeleri belirledik.
51. Satır = Bu satır ile alt pencere açıldığında ana penceredeki yazının renginin seçili olarak alt pencerede gelmesini istedik.
18. Satır = Izgara pencerenin en altına yerleştirdiğimiz yatay buton kutumuzda "Tamam" butonunu ön tanımlı olarak belirledik. Bu tarz butonlar genellikle programların sağ alt köşesine yerleştirilir. Aynı hizanın sol tarafı boş kalmaktadır. Bunu sağlamak için 20. satırdan sonra butonKutusu.addStretch() kodunun eklenmesi yeterli olacaktır. Değişikliği farkedebilmek için pencerenin boyutunu artırmanız yeterli.
25 ve 26. Satır = Diyalog pencereleri kapatıldığında geri değer döndürürler. Bu değerler 0 ve 1'dir (accept ve reject). Biz buradan dönen 1 değerini "Tamam" butonuna atadık. Bunun anlamı o pencerede yapılan değişikliklerin uygulanacağı anlamına gelmektedir.


Kaynaklar:
-Mustafa Başer - Python Kitabı

10 Ağustos 2014 Pazar

PyQt Diyaloglar

      Şu ana kadar PyQt'ye ait temel elemanlardan bir kaçını kullanarak değişik uygulamalar yaptık. Ancak grafiksel arayüze sahip programlar bunlardan daha fazlasını içerir. Kullanıcılarla etkileşimi artırmak ve kullanıcıyı daha iyi yönlendirebilmek için Diyaloglar kullanılır. Peki nedir bu diyaloglar? Diyaloglar ana pencerenin üzerine açılan ve üzerinde Qt elemanları olan pencerelerdir. Yani bir başka deyişle alt pencerelerdir.
Örneğin ana pencereniz üzerinde bulunan bir yazının rengini veya yazı tipini seçmek için açılabilecek yeni bir pencere bir diyalogdur. Diyaloglar çeşitli gruplara ayrılmaktalar. Referans olarak aldığım bir kaynakta diyaloglar üç grupta incelenmiş ben de bu şekilde ele alıp ifade etmeye çalışacağım. Bu gruplar akıllı, akılsız ve canlı diyaloglar.

İlerleyen bölümlerde kullanacağımız simgeleri http://dryicons.com/free-icons/preview/orange-moonlight-icons/ adresinden ücretsiz temin edebilirsiniz. Ben bu simgeleri kullanacağım siz başka simgeleri de tercih edebilirsiniz.


8 Ağustos 2014 Cuma

PyQt Birbiriyle Bağımlı Parçacıklar

       Grafiksel kullanıcı arayüzüne sahip programlarda; kullanıcı tarafından fare tıklanması, klavyeden bir tuşa basılması gibi durumlar program tarafından dinlenilerek parçacıklarla ilgili tepkilerde belirli sinyaller için belirlediğimiz metotları çağırıyorduk. Biz bu oluşan sinyalleri bir başka parçacığa ait herhangi bir özellikle ilişkilendirebiliriz. Bu durumda ilk parçacıktaki değişim diğerinde de bir etki oluşturacaktır. Üstüne bir de ikinci parçacık için oluşacak bir sinyali de ilk parçacığın bir özelliğine bağlarsak, bu parçacıklar birbirlerini etkiler duruma gelecektir.

       Şöyle bir örnek ile açıklamaya devam edeyim. Örneğin ses seviyesini belirlediğimizi düşünelim. Seviye belirlediğimiz bir parçacıkla (QSlider) aynı seçimi sayıyla gösterir halde seçmemizi sağlayan bir döner kutu (QSpinBox) olabilir. Bu durumda ikisi de aynı değer üzerinde değişiklik yapmamızı sağlamaktadır. Bunun için bu parçacıkların birindeki değişim diğerini de değiştirmelidir.
       Bizim bu iki parçacığın aynı özelliklerini ortak bir değer için birbiriyle ilişkilendirebilmemiz için her iki parçacıkta da bu özelliklerin mevcut olması gerekir. Bizim burada QSlider ile QSpinBox parçacıklarını ilişkilendirmemizi sağlayan, ikisinin de valueChanged(int) sinyali ile setValue() metoduna sahip olmalarıdır. Parçacıklar ilişkilendirilirken bu duruma dikkat edilmelidir.

Yapacağımız örneğe şöyle bir görsellik daha katalım; biri yatay biri dikey iki tane QSlider ve bir tane QSpinBox kullanalım. Ve bu üç parçacıktan herhangi birinin valueChange(int) sinyalini diğerlerinin setValue() özelliklerine bağlayalım.
Kodlarımız şu şekilde:
# !/usr/bin/env python
# -*- coding: cp1254 -*-

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class BirbiriyleBagimliParcaciklar(QDialog):
    def __init__(self, parent=None):
        super(BirbiriyleBagimliParcaciklar, self).__init__(parent)

        yataySlider = QSlider()
        yataySlider.setRange(0, 10)
        yataySlider.setOrientation(Qt.Horizontal) #Slider'ın yatay olması için

        dikeySlider = QSlider()
        dikeySlider.setRange(0, 10)
        dikeySlider.setOrientation(Qt.Vertical) #Slider'ın dikey olması için

        dKutu = QSpinBox()
        dKutu.setRange(0, 10)

        self.connect(dikeySlider, SIGNAL('valueChanged(int)'), yataySlider.setValue)
        self.connect(yataySlider, SIGNAL('valueChanged(int)'), dKutu.setValue)
        self.connect(dKutu, SIGNAL('valueChanged(int)'), dikeySlider.setValue)
        
        grid = QGridLayout()
        grid.addWidget(dikeySlider, 0, 0, 2, 1)
        grid.addWidget(yataySlider, 0, 1)
        grid.addWidget(dKutu, 1, 1)


        self.setLayout(grid)
        self.setFixedSize(300, 150)
        self.setWindowTitle('PyQt QSlider & QSpinBox')

uygulama = QApplication([])
pencere = BirbiriyleBagimliParcaciklar()
pencere.show()
uygulama.exec_()

Programımızı çalıştırdığımızda karşımıza şu şekilde bir pencere gelecektir.
Burada parçacıklar üzerinde herhangi bir değişiklik yaptığımızda diğer parçacıkların değiştiğini de göreceksiniz. Şimdi de koddaki  bir kaç noktaya değinelim.
13-17. Satırlar = Bu iki satırda yorumda da belirttiğimiz gibi Slider'ımızın yatay konumda mı yoksa dikey konumda mı olması gerektiğini belirtiyoruz. Bu satırı yazmazsak varsayılan olarak dikey konumlandırılıyor.
22-23-24. Satırlar = Bu satırlarda üç parçacığa ait sinyaller birbirlerini etkileyecek şekilde birbirlerine bağlanmışlardır. Halka gibi düşünülebilir. Böylelikle bir tanesinde oluşan değer değişikliği sonucu o değer diğer parçacıklara gönderilmektedir.


Kaynaklar:
-Mustafa Başer - Python Kitabı

5 Ağustos 2014 Salı

PyQT Klavye Kısayolları ve QRadioButton

Kes, kopyala, yapıştır, kaydet gibi bazı işlemler için klavyedeki kısayolları kullanmak birçok kullanıcı için vazgeçilmezdir. Grafiksel arayüze sahip programlarda da genellikle fareyi kullanırız; ancak bazı kullanıcılar klavyeyi kullanmak isteyebilirler. Bu durumda programımızı klavyedeki tuşlar ile ilişkilendirmemiz gerekecektir.

Bir örnek ile bunu nasıl yapabileceğimize bakalım. Pencere üzerine bir yazı koyalım ve bu yazının rengini değiştirebileceğimiz seçenekler ekleyelim.  Aynı zamanda kullanıcı bu renk seçeneklerini klavyeden de seçebilsin.

Kodlarımız şu şekilde:
# !/usr/bin/env python
# -*- coding: cp1254 -*-

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Klavye_Kisayollari(QDialog):
    def __init__(self, parent=None):
        super(Klavye_Kisayollari, self).__init__(parent)

        self.renk = 'blue'
        self.yazi = '''PythonDersleri.com'''

        self.metin = QLabel(self.yazi % self.renk)

        aciklamaLabeli = QLabel('Yazı rengi:')

        self.maviButon = QRadioButton('&Mavi')
        self.maviButon.setChecked(True)
        self.yesilButon = QRadioButton('&Yeşil')
        self.kirmiziButon = QRadioButton('&Kırmızı')

        self.butonGrubu = QButtonGroup()
        self.butonGrubu.addButton(self.maviButon)
        self.butonGrubu.addButton(self.kirmiziButon)
        self.butonGrubu.addButton(self.yesilButon)
        self.connect(self.butonGrubu, SIGNAL('buttonClicked(int)'), self.metniGuncelle)

        izgara = QGridLayout()
        izgara.addWidget(self.metin, 0, 1, 4, 1)
        izgara.addWidget(aciklamaLabeli, 0, 0)
        izgara.addWidget(self.maviButon, 1, 0)
        izgara.addWidget(self.yesilButon, 2, 0)
        izgara.addWidget(self.kirmiziButon, 3, 0)

        self.setLayout(izgara)
        self.setWindowTitle('PyQt Klavye Kısayolları')
        self.setFixedSize(300, 100)

    def metniGuncelle(self):
        if self.maviButon.isChecked():
            self.renk = 'blue'
        elif self.kirmiziButon.isChecked():
            self.renk = 'red'
        elif self.yesilButon.isChecked():
            self.renk = 'green'

        self.metin.setText(self.yazi % self.renk)

uygulama = QApplication([])
pencere = Klavye_Kisayollari()
pencere.show()
uygulama.exec_()
Bu kodu çalıştırdığımızda karşımıza şu şekilde bir pencere gelecektir.
Windows işletim sisteminde renklerin baş harflerinin altı çizili olması için çalıştıktan sonra ALT tuşuna basılması gerekiyor.

Şimdi biz burada renkler için klavye kısayolları atamış olduk. Bu kısa yollar Qt'de & işareti ile belirtiliyor. & işaretinden sonra gelen karakteri kısayol olarak atamış oluyoruz. Pencerede de kısayol olarak belirlenmiş olan karakter altı çizili olarak gösteriliyor. Örneğin uygulamamızda yazımızın rengini kırmızı yapmak için klavyeden ALT + K tuşlarına basmamız yeterlidir.

Parçacıklar içinde QRadioButton, QPushButton gibi kısayol olarak atayabileceğimiz bir metin geçmiyorsa ne yapacağımız düşünüyor olabilirsiniz. Bunun çözümü de çok basit. İçinde metin geçmeyen bu parçacıklar için kullanıcıya o parçacığın ne işlev gördüğünü belirtmek amacıyla genellikle bir QLabel kullanılmaktadır. Yapmak istediğimiz kısayolu o labelde belirtip, label ile de o parçacığı ilişkilendirmemiz gerekir. Bunu da labelIsmi.setBuddy(parcacikIsmi) şeklinde belirtmemiz yeterlidir.

Bu programda yeni görmüş olduğumuz bir de Radio Butonların gruplandırılması var. Bu butonları gruplandırmamızın sebebi, çoklu seçimi önleyip, sadece bir tanesinin seçili olmasını sağlamaktır. Eğer penceremizde başka fonksiyonlar için oluşturduğumuz başka radio butonlar yoksa gruplandırmaya gerek yoktur; ancak varsa birbirlerinden ayırt edilmeleri için gruplandırmamız gerekmektedir.

Not: Programınızda kısayolları belirlerken aynı karaktere birden fazla atama yapmamaya dikkat ediniz.


Kaynaklar:
-Mustafa Başer - Python Kitabı

1 Ağustos 2014 Cuma

PyQt QLineEdit Kullanımı

       Bu yazımızda sizlere QLineEdit yani kullanıcıdan veri almamızı sağlayan kutucuklardan bahsedeceğiz. Kullanıcıdan bu kutucuklar ile bilgilerini alıp, aldığımız bu verileri de bir dosyaya kaydedeceğimiz örnek bir uygulama yapalım. Bu sayede anlaşılması kolay olur hem de dosya işlemlerini kısaca bir tekrar etmiş oluruz.
       Izgara pencere düzeni kullanarak kullanıcıdan adını, soyadını, yaşını ve bölümünü alacağımız alanları belirtelim, aldığımız bu verileri dosyaya kaydetmek için bir de buton oluşturalım ve bunları penceremize yerleştirelim.
Hazırlamış olduğumuz örnek kodlarımız şu şekilde:
# !/usr/bin/env python
# -*- coding: cp1254 -*-
 
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class QLineEdit_Kullanimi(QDialog):
    def __init__(self, parent=None):
        super(QLineEdit_Kullanimi, self).__init__(parent)

        self.ad = QLabel('Ad:')
        self.soyad = QLabel('Soyad:')
        self.yas = QLabel('Yaş:')
        self.bolum = QLabel('Bölüm:')

        self.adi = QLineEdit()
        self.soyadi = QLineEdit()
        self.yasi = QLineEdit()
        self.yasi.setInputMask('99')
        self.bolumu = QLineEdit()
        
        self.kaydet = QPushButton('Kaydet')
        self.connect(self.kaydet, SIGNAL('pressed()'), self.dosyayaYaz)

        izgara = QGridLayout()
        izgara.addWidget(self.ad, 0, 0,)
        izgara.addWidget(self.soyad, 1, 0,)
        izgara.addWidget(self.yas, 2, 0,)
        izgara.addWidget(self.bolum, 3, 0,)
        
        izgara.addWidget(self.adi, 0, 1,)
        izgara.addWidget(self.soyadi, 1, 1,)
        izgara.addWidget(self.yasi, 2, 1,)
        izgara.addWidget(self.bolumu, 3, 1,)

        izgara.addWidget(self.kaydet, 4, 1,)

        self.setLayout(izgara)
        self.setWindowTitle('PyQt QLineEdit')
        

    def dosyayaYaz(self):
        dosya = open('Çıktı.txt', 'w')
        dosya.write(self.adi.text() + '\n' + self.soyadi.text() + '\n' + self.yasi.text() + '\n' + self.bolumu.text())
        dosya.close()        
        self.adi.setText('')
        self.soyadi.setText('')
        self.yasi.setText('')
        self.bolumu.setText('')

uygulama = QApplication([])
pencere = QLineEdit_Kullanimi()
pencere.show()
uygulama.exec_()

Bu programımızı çalıştırdığımızda şekildeki gibi bir ekran bizi karşılayacaktır.

Sonrasında bu formu doldurup "Kaydet" butonuna tıkladığımızda kodlarımızı yazdığımız dosya ile aynı dizinde "Çıktı.txt" isimli bir dosya oluşacaktır. Bu dosyayı açıp baktığımızda ise içeriği şöyledir:
Uygar
Köroğlu
22
Bilgisayar Mühendisliği

Şimdi yukarıdaki yazmış olduğumuz kodları kısaca açıklayalım. Burada da sadece farklı olarak yazdığımız kodları açıklayacağız.

11-12-13-14. Satırlar  = Bu satırlarda kutucukları anlamlandırmak için Label oluşturuyoruz.
16-17-18-20. Satırlar = Bu dört satırda kullanıcıdan veri almak için kutucuk oluşturuyoruz.
19. Satır = Bu satırda kişiye ait yaşı alırken kullanıcının gireceği veriye bir sınırlama getirdiğimizi belirtiyoruz. Buraya yazdığımız '9' sayısının kaç adet olduğu ile kaç karakter girilebileceğini ve bu sayının kendisi ise buraya [0, 9] aralığında bir rakamın girilebileceğini belirtmektedir. Yani bu şekilde kullanıcının yalnızca sayı girmesi ve bu sayının da maksimum iki basamaklı olabileceğini belirtmiş olduk.
22. Satır = Bu satırda da "Kaydet" butonumuzu oluşturduk.
23. Satır = Bu satırda da butona tıklandığında verileri dosyaya yazacak olan metodu çağırdık.
42. Satır = Buton tıklandığında çalışacak olan metodu tanımladık.
43. Satır = "Çıktı.txt" isminde bir dosyayı yazma modunda açıyoruz.
44. Satır = Kutucuklardan aldığımız verileri satır satır dosyaya yazıyoruz.
45. Satır = Dosyamızı kapatıyoruz.
46-47-48-49. Satırlar = Bu satırlarda dosyaya yazma işleminden sonra kutucukların içini temizliyoruz. Bir nevi kaydettiğimizin de farkına varabilmemiz için bunu yazdık.

       Yukarıda 19. satırda bahsetmiş olduğumuz setInputMask() metodu ile verebileceğimiz kısıtlamalar için kullanabileceğimiz parametrelerden bahsetmek istiyorum. Bu parametreler ile kullanıcının gireceği karakterlere belli kısıtlamalar getiriyoruz, bu kısıtlamalar sayesinde kullanıcıların geçersiz karakterler girmesinin önüne geçilebilir. Bu metot ile kullanabileceğimiz parametreler şu şekilde:

h       =>       Hexadecimal (onaltılık) karakterler için kullanılır, zorunluluk gerektirmez.
H      =>       Hexadecimal (onaltılık) karakterler için. a-f, A-F, 0-9
b       =>       Binary (ikilik) karakterler için kullanılır, zorunluluk gerektirmez.
B      =>       Binary (ikilik) karakterler için. 0-1
0       =>       ASCII sayılar için kullanılır, zorunluluk gerektirmez. 0-9
9       =>       ASCII sayılar için
d       =>       ASCII sayılar için kullanılır, zorunluluk gerektirmez. 1-9 (0 yok)
D      =>       ASCII sayılar için
#       =>       ASCII sayılar ve artı eksi işareti için kullanılır, zorunluluk gerektirmez.
a       =>       ASCII alfabe karakterleri için kullanılır, zorunluluk gerektirmez. a-z, A-Z
A      =>       ASCII alfabe karakterleri için
n       =>       ASCII sayısal karakterler için kullanılır, zorunluluk gerektirmez.
N      =>       ASCII alfabe karakterler için a-z, A-Z, 0-9
x       =>       Herhangi bir karakter olabilir, zorunluluk gerektirmez.
X      =>       Herhangi bir karakter olabilir
>      =>       Bu karakterden sonra gelecek olan bütün alfabe büyük harf olacak.
<      =>       Bu karakterden sonra gelecek olan bütün alfabe küçük harf olacak.
!       =>       Büyük-Küçük harf zorunluluğunu kapatır.


Kaynaklar:
-Mustafa Başer - Python Kitabı

29 Temmuz 2014 Salı

PyQt QSpinBox ve QComboBox

       Şimdiye kadar yaptığımız örneklerde sadece Buton(QPushButton) ve Label(QLabel) kullanmıştık. Bu yazımızda iki tane yeni parçacık daha göreceğiz ve bunların kullanımına bakacağız.  Bu parçacıklarımız döner kutu ve açılan bir liste olan QSpinBox ve QComboBox. QSpinBox sayısal değerleri kullanıcıdan hatasız bir şekilde almak için kullandığımız bir parçacık. QComboBox da kullanıcıya verdiğimiz bir liste üzerinden seçim yapmasını sağladığımız bir parçacıktır.

       Şimdi bu parçacıkları nasıl kullanacağımıza dair bir örnek verelim:

# !/usr/bin/env python
# -*- coding: cp1254 -*-
 
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class QSpinBox_QComboBox(QDialog):
    def __init__(self, parent=None):
        super(QSpinBox_QComboBox, self).__init__(parent)

        self.yil = 2014
        self.mevsim = 'kış'
        self.metin = ('
%d yılının %s mevsimi.
') self.label = QLabel(self.metin % (self.yil, self.mevsim)) self.dKutu = QSpinBox() self.dKutu.setRange(2000, 2050) self.dKutu.setValue(self.yil) self.connect(self.dKutu, SIGNAL('valueChanged(int)'), self.yilDegistir) self.aListe = QComboBox() self.mevsimler = ['sonbahar', 'kış', 'ilkbahar', 'yaz'] self.aListe.addItems(self.mevsimler) self.aListeMevsimIndisi = self.aListe.findText(self.mevsim) self.aListe.setCurrentIndex(self.aListeMevsimIndisi) self.connect(self.aListe, SIGNAL('currentIndexChanged(QString)'), self.mevsimDegistir) izgara = QGridLayout() izgara.addWidget(self.label, 0, 0, 1, 2) izgara.addWidget(self.dKutu, 1, 0,) izgara.addWidget(self.aListe, 1, 1) self.setLayout(izgara) self.setWindowTitle('PyQt QSpinBox ve QComboBox') self.setFixedSize(350, 100) def yilDegistir(self, sinyaldenGelenYil): self.yil = sinyaldenGelenYil self.label.setText(self.metin % (self.yil, self.mevsim)) def mevsimDegistir(self, sinyaldenGelenMevsim): self.mevsim = sinyaldenGelenMevsim self.label.setText(self.metin % (self.yil, self.mevsim)) uygulama = QApplication([]) pencere = QSpinBox_QComboBox() pencere.show() uygulama.exec_()

Bu kodu çalıştırdığımızda karşımıza şöyle bir pencere gelecektir:

Kodlarımıza baktığımızda daha önceki programlarımızdan farklı bir connect kullandığımızı görebilirsiniz. Bunun sebebi bazı QT parçacıklarının sinyal ile birlikte bir de değer göndermesidir. Bu değer sinyalde parametre olarak belirtilmektedir. Bağlantıda yayımlanan sinyal sonucu çalışmasını istediğimiz metotlara bu şekilde değerler gönderebiliriz. Burada yilDegisitir() ve mevsimDegistir() metotlarına int ve QString şeklinde sinyal argümanları gönderdik. Bu sinyal ile gönderdiğimiz int ve QString değerleri parçacıkların o anki değerlerini ifade etmektedir.
       Bu yeni görmüş olduğumuz parçacıkları satırları ile birlikçe açıklayacak olursak:
17. Satır = Bir tane QSpinBox nesnesi oluşturuyoruz.
18. Satır = setRange() metodu ile döner kutunun alt ve üst sınırlarını belirtiyoruz.
19. Satır = setValue() metodu ile program başladığı andaki değerini belirtiyoruz. (Eğer bu değer alt değerden küçükse alt değeri, üst değerden büyükse de üst değeri otomatik olarak yazılır).
20. Satır = Döner kutu üzerinde değer değiştiği anda oluşan sinyale yilDegistir() metodunu bağlıyoruz.

22. Satır = Bir tane QComboBox nesne oluşturuyoruz.
23. Satır = Açılır listenin elemanlarını belirleyebilmek için bir liste oluşturuyoruz
24. Satır = Oluşturduğumuz bu mevsimler isimli listeyi, açılır listenin elemanları olarak gönderiyoruz.
25. Satır = findText ile listedeki bir elemanın konumunu bulduk.
26. Satır = Listenin seçilmiş olan elemanını belirttik.
27. Satır = Açılır listede bir index değişikliği olduğunda mevsimDegistir() metodunun çalıştırılacağı bağlantıyı kuruduk.

Bu tür parçacıkların sinyallarinin ve özelliklerinin akılda tutulması zordur. Bunlar genelikle sürekli kullandıkça akılda kalmaktadır. Ancak unutulması veya öğrenilmesi istenildiğinde hafif bir İngilizce bilgisi ile şuradaki dökümana bakarak anlaşılabilir:
Başlat > Tüm Programlar > PQt GPL vX.X.X for Python vX.X > Documentation > PyQt Class Reference


Kaynaklar:
-Mustafa Başer - Python Kitabı
-PyQt Class Reference

26 Temmuz 2014 Cumartesi

Yönetim Paneli Özelleştirme

Bu yazı da kısaca yönetim panelini nasıl özelleştirebiliriz ona değineceğiz. Django yönetim panelinde ana şablon olarak admin/base.html'i kullanır. Ve bize de kendi yönetim şablonumuzu oluşturma imkanı verir. Bu yönetim şablonunu sablonlar/admin/base_site.html olarak arar. Yönetim panelinde varsayılan olarak header kısmında Django Yönetimi yazmaktadır. Şimdi basit bir şekilde bunu nasıl değiştirebileceğimizi görelim.
İlk önce şun söyleyelim oluşturacağımız base_site.html artık bizim yönetim şablonumuz olacak. Bunun içine sadece 'merhaba' yazarsanız yönetim panelinde de sadece 'merhaba' yazısını görürsünüz. Django'nun kendi yönetim panelinin devam edebilmesi için bizim kendi şablonundan kalıtım almamız gerekiyor.
{% extends "admin/base.html" %}
{% load i18n %}

{% block title %}{{ title }} | {% trans 'Başlık' %}{% endblock %}

{% block branding %}

{% trans 'Header Başlık' %}

{% endblock %} {% block nav-global %}{% endblock %}
Burada ilk önce extends ifadesi ile kalıtım aldık. Kalıtım alırken her şey olduğu gibi alınır. Ancak aynı block ifadeleri hem kalıtım alınan şablonda hem de kalıtımı alan şablonda var ise kalıtımı alan şablondaki gösterilir. Biz block branding ifadesini yeniden yazdığımız için bizim yazdığımı görünecektir.
Bu şekilde block ifadelerini kullanarak yönetim panelini özelleştirebilirsiniz. Django'nun yönetim şablonlarını buradan inceleyebilirsiniz.

24 Temmuz 2014 Perşembe

ModelAdmin Metodları

ModelAdmin içerisinde kullanılabilen metodlar bulunmaktadır. Bu yazı da bazı metodların ne işe yaradıklarını ve nasıl kullanıldıklarını anlatmaya çalışacağız.
save_model(self, request, obj, form, change)
Bir model elemanı kaydedilirken olmasını istediğimiz şeyleri bu metot içerisinde yapabiliriz. Örneğin elimizde şöyle modeller olsun;
class Yazilar(models.Model):
    yazar = models.ForeignKey(User,blank=True,null=True)
    baslik = models.CharField(max_length=100)
    icerik = models.TextField()
Burada yazı kaydedilirken yazar kısmı giriş yapmış kullanıcı olsun istiyoruz. İşte bunun için save_model() metodunu kullanabiliriz.
class YazilarAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.yazar = request.user
        obj.icerik = "İçerik kısmını otomatik atadık." ##Bu şekilde model kaydedilirken istediğimiz alana müdahale edebiliriz.
        obj.save()

delete_model(self, request, obj)
save_model() metodunun tersi olarak modelden eleman silinirken olmasını istediklerinizi bu model içerisine yazabilirsiniz.
get_ordering(self, request):
Sıralama işleminin neye göre yapılacağının dönmesini bekler. Dönen değer liste veya demet olmalıdır.
class YazilarAdmin(admin.ModelAdmin):
    list_display = ['baslik','icerik']
    def get_ordering(self, request):
        if request.user.is_superuser:
            return ['baslik','icerik']
        else:
            return ['icerik']
Burada eğer kullanıcı süper ise, sıralama ile başlık için sonra içerik için yapılacaktır. Eğer kullanıcı süper değil ise, sıralama sadece içerik için yapılacaktır.
get_readonly_fields(self, request, obj=None):
Model alanlarından sadece okunabilir alanları geriye döndürür. Dönen değer liste veya demet olmalıdır.
    def get_readonly_fields(self, request, obj=None):
        return ['id']
Not: Bu kullandığımız fonksiyon ModelAdmin seçeneklerinden readonly_fields seçeneği ile aynı işlemleri yapmaktalar. Ama gördüğünüz gibi bunlar birer fonksiyon oldukları için istediğimiz kontrolleri yaparak bu kontrollere göre sonuçlar alabilmekteyiz. Fakat bu işlemleri ModelAdmin seçenekleri ile yaptığımız zaman yaptığımız işlem tüm hepsi için geçerli olmaktadır. Yani kontrol koyma imkanına sahip olamıyoruz.
get_list_display(self, request):
list_display seçeneği ile aynı işlevi görmektedir.
    def get_list_display(self, request):
        return ['baslik','icerik','kategori']
get_list_display_links(self, request, list_display):
list_display_links seçeneği ile aynı işlevi görmektedir.
    def get_list_display_links(self, request, list_display):
        return ['baslik','icerik',]
get_urls(self):
Metodunu kullanarak ModelAdmin içerisinde url tanımlaması yaparak bu url'i görünüme yönlendirebiliriz.
from django.conf.urls import patterns
from django.shortcuts import render_to_response
class YazilarAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super(YazilarAdmin, self).get_urls()
        my_urls = patterns('',
            (r'^gorunum/$', self.admin_site.admin_view(self.gorunum))
        )
        return my_urls + urls

    def gorunum(self, request):
        return render_to_response('gorunum.html')
Oluşturduğumuz url'e tarayıcıdan şu şekilde ulaşılır: http://localhost:8000/admin/uygulamaadi/modeladi/gorunum/
get_form(self, request, obj=None, **kwargs)
Form üzerinde değişiklik yapabilmemizi sağlar.
    def get_form(self, request, obj=None, **kwargs):
        self.exclude = ['baslik_slug']
        return super(YazilarAdmin, self).get_form(request, obj, **kwargs)
Burada form içerisindeki baslik_slug alanını gizlemiş olduk. Bunun dışında doğrudan kendi oluşturmuş olduğumuz formu da kullanabiliriz.
from django.contrib.auth.forms import *
class YazilarFormu(forms.ModelForm):
    class Meta:
        model = Yazilar
        fields = ['baslik']


class YazilarAdmin(admin.ModelAdmin):
    def get_list_display(self, request):
        return ['baslik','icerik','kategori']

    def get_form(self, request, obj=None, **kwargs):
        return YazilarFormu
has_add_permission(self, request):
class YazilarAdmin(admin.ModelAdmin):
    def has_add_permission(self, request):
        if not request.user.is_superuser:
            return False
        return True
Ekleme iznini belirler. True veya False döndürür. Eğer False döndürür ise model için ekleme yapılamaz. Burada eğer kullanıcı süper kullanıcı değilse ekleme yapamaz.
has_change_permission(self, request, obj=None)
has_add_permission gibi çalışır. Eğer kullanıcının model üzerinde değiştirme yetkisi verilmemiş ise model sayfasını göremez.
class YazilarAdmin(admin.ModelAdmin):
    list_display = ['baslik']
    def has_change_permission(self, request, obj=None):
        if not request.user.is_superuser:
            return False
        return True
has_delete_permission(self, request, obj=None)
Ekleme ve değiştirmeden bir farkı yoktur. Eğer False döndürülür ise model elemanları silinemez olur. queryset(self, request)
Model üzerinde sorgu çalıştırabilmemizi sağlar. Örneğin aşağıdaki method eğer kullanıcı süper değil ise sadece kendi yazılarını görebilmesini sağlıyor.
class YazilarAdmin(admin.ModelAdmin):
    def queryset(self, request):
        qs = super(YazilarAdmin, self).queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(yazar=request.user)
Burada ki qs içerisinde tüm yazılar bulunmaktadır. Bu nesne üzerinden bu yazılar üzerinde sorgular çalıştırılabilir. Örneğin tüm yazıların tarihlerini güncelleyen şöyle bir fonksiyon yazılabilir.
import datetime
class YazilarAdmin(admin.ModelAdmin):
    def queryset(self, request):
        qs = super(YazilarAdmin, self).queryset(request)
        for q in qs:
            q.tarih =datetime.datetime.now()
            q.save()
        return qs
Kaynakça

18 Temmuz 2014 Cuma

ModelAdmin Seçenekleri

Bu yazı serimizde Django'nun bize sağlamış olduğu yönetim panelini özelleştirmekten bahsedeceğiz. Django ile birlikte gelen güçlü bir yönetim paneli bulunmaktadır. Ama işin asıl güzel kısmı bu yönetim panelini isteklerimiz doğrultusunda şekillendire biliyoruz.
date_hierarchy
Elimizde bulunan bir modelin listelemesi yapılırken tarih bazlı filtreleme yapılması isteniyorsa bu seçenek kullanılabilir. Elimizde şöyle bir model olduğunu düşünelim.
class Kisi(models.Model):
    isim = models.CharField(max_length=100)
    dogum_tarihi  = models.DateField()

    def __unicode__(self):
        return self.isim
Şimdi admin.py dosyasına şu kodları yazıyoruz.
class KisiAdmin(admin.ModelAdmin):
    date_hierarchy = 'dogum_tarihi'

admin.site.register(Kisi,KisiAdmin)
Bu ayarlamaları yaptıktan sonra yönetim panelinde Kisi modeline girerseniz en üstte girdiğiniz tarihleri görebilirsiniz. Bu tarihlere tıklayarak sadece o tarihlere ait kişileri listeleye bilirsiniz.
Resimde 1992 ve 1993 sayılarını görebiliyorsunuz.
ordering
ordering seçeneği ile listeleme yapılırken hangi alana göre sıralama yapılacağını seçebiliriz. Listeleme için ordering seçeneğini de ekleyelim.
class KisiAdmin(admin.ModelAdmin):
    date_hierarchy = 'dogum_tarihi'
    ordering = ('isim', )
Ali ve Veli'nin yerlerinin değiştiğini görebilirsiniz.
actions
Listelenen elemanları seçerek toplu bir şekilde işlem yapabiliyoruz. Django'da varsayılan olarak sadece "Seçilileri sil" seçeneği gelmektedir. Ama biz seçilen elemanlar üzerinde başka işlemler yaptırmak isteyebiliriz. Bunun için actions seçeneğini kullanacağız. Çok mantıklı olmasa da göstermek açısından kullanıcı doğum tarihlerini günümüz tarihi olarak güncelleyen bir seçenek ekleyelim.
import time
def yil_duzenle(modeladmin,request,queryset):
    queryset.update(dogum_tarihi=time.strftime("%Y-%m-%d"))
yil_duzenle.short_description = 'Doğum tarihlerini güncelle'

class KisiAdmin(admin.ModelAdmin):
    actions = [yil_duzenle]

admin.site.register(Kisi,KisiAdmin)
Kodlaru bu şekilde düzenledikten sonra seçenekler listesinde "Doğum tarihlerini güncelle" seçeneğini şu şekilde göreceksiniz.
Seçtiğiniz kullanıcıların doğum tarihleri güncellenecektir.
actions_on_top & actions_on_bottom
Seçenekler kutusunun nerede duracağına karar vermemizi sağlar. Varsayılan olarak şöyle gelir.
    actions_on_bottom = False
    actions_on_top = True
Eğer her ikisi de True yapılırsa hem listenin üstünde hemde altında gözükecektir.
actions_selection_counter
Yukarıda ki resmi incelerseniz "İki nesne arasından seçim.." diye bir alan göreceksiniz. Burada seçili olan nesnelerin sayısı hakkında bilgi verilmektedir. Bu actions_selection_counter seçeneği ile yapılmaktadır. Varsayılan olarak True gelmektedir. Eğer görünmesini istemiyorsak False yapabiliriz.
class KisiAdmin(admin.ModelAdmin):
    actions = [yil_duzenle]
    actions_selection_counter = False

fields & exlude
Modelden ekleme işlemi yaparken fields ile gösterilecek alanları exlude ile ise gösterilmeyecek alanları belirleyebiliyoruz. Örneğin elimizde şöyle bir model olduğunu düşünelim.
class Yazi(models.Model):
    baslik  = models.CharField(max_length=100)
    tarih   = models.DateTimeField()
    taslak  = models.BooleanField(default=False)
Burada yazi ekleme işlemi yapılırken sadece "baslik" be "tarih" alanlarının görüntülenmesini istersek;
class YaziAdmin(admin.ModelAdmin):
    fields = ('baslik', 'tarih')

admin.site.register(Yazi,YaziAdmin)
Aynı işlemi exlude ile de yapabiliriz. Göstermek istemediğimiz alan "taslak" olduğu için onu yazmalıyız.
class YaziAdmin(admin.ModelAdmin):
    exclude = ('taslak', )
Aynı işlemi görecektir. Eğer iki alanın aynı satırda gözükmesini istersek fields özelliği içerisinde alanları demet içerisinde yazabiliriz.
class YaziAdmin(admin.ModelAdmin):
    fields = (('baslik', 'taslak'), 'tarih', )

fieldsets
Alan ekleme sayfasını parçalamak istersek bu özelliği kullanabiliriz. Örneğin eklem sayfasında zorunlu alanlar ile zorunlu olmayan alanları altlı üstlü iki parça olarak göstermek istersek şöyle bir kullanım işimizi görecektir. Bir önceki modelden devam edelim.
class YaziAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Mecburi Alanlar',{
            'fields':('baslik', 'tarih')
        }),
        ('Seçmeli Alanlar',{
            'classes': ('collapse', ),
            'fields' : ('taslak', ),
        })
    )
Burada sayfayı ikiye böldük. Demetin ilk elemanıda bir demet ve bu demetin ilk elamanı ise bölümün başlığını içeriyor. Burada dikkat ettiyseniz "Seçmeli Alanlar" kısmında classes diye bir alan kullandık. Bu bölüme stil atamamızı sağlıyor. Burada atadığımız stil Django'nun kendisinde bulunan collapse. Bu stil sayesinde bölüme Göster/Gizle butonu gelecektir.

css & js
Eğer istersek model sayfasında yüklenecek css ve js dosyalarının tanımını yapabiliriz.
class YaziAdmin(admin.ModelAdmin):
    class Media:
        css = {
            "all":("style.css", )
        }
        js = ("code.js", )
Buradaki style.css ve code.js dosyaları "static" dizini içerisinde aranır. Yani /static/ linkine hangi dizini tanımladıysak o dosya içerisinde arama yapılır.
filter_horizontal
Model içerisinde bulunan ManyToManyField alanları için gelişmiş seçim alanı sunar. Bu alanı Django'nun User modeli gibi alanlarda görmüştük. Bu seçenek seçenek sadece ManyToManyField alanlar için kullanılabilir.
class YaziAdmin(admin.ModelAdmin):
    filter_horizontal = ('kategori', )

filter_vertical
filter_horizontol ile aynı işlemi yapmaktadır. Sadece seçim alanları dikey gözükmektedir.
forms
Django yönetim panelindeki modele ekleme yaparken ekleme işleminin oluşturduğumuz bir forma göre yapılmasını istersek bu seçeneği kullanabiliriz. Örneğin oluşturduğumuz Yazi modeline ekleme yapılırken başlığının tekrarlanmasını engelleyen bir form oluşturalım ve modele bu formu gösterelim.
class YaziForm(forms.ModelForm):
    def clean_baslik(self):
        baslik = self.cleaned_data.get('baslik')
        if Yazi.objects.filter(baslik=baslik).exclude(id=self.instance.id).count():
            raise forms.ValidationError('Bu başlıkta bir yazı bulunmaktadır.')
        return baslik
class YaziAdmin(admin.ModelAdmin):
    list_display = ('baslik', )
    filter_vertical = ('kategori', )
    form = YaziForm
admin.site.register(Yazi, YaziAdmin)
Burada bir formu oluşturduk ve bu formu YaziAdmin içerisinde gösterdik. Artık bir yazı kaydedilirken bu form dikkate alınacak ve aynı başlıktan birden fazla yazı kaydedilmeyecektir.

formfield_ovverides
Form alanlarını özelleştirmek için kullanabiliriz. Örneğin elimizdeki yazı modeline içerik kısmı ekleyelim.
class Yazi(models.Model):
    baslik  = models.CharField(max_length=100)
    icerik = models.TextField()
    tarih   = models.DateTimeField()
    taslak  = models.BooleanField(default=False)
    kategori = models.ManyToManyField(Kategori)
Bu modeldeki TextField alanları için CKEditor kullanmak istediğimizi düşünürsek bu özellik bizim işimizi görebilir.
from django.db import models
from ckeditor.widgets import CKEditorWidget

class YaziAdmin(admin.ModelAdmin):
    list_display = ('baslik', )
    filter_vertical = ('kategori', )
    form = YaziForm
    formfield_overrides = {
        models.TextField : {'widget':CKEditorWidget}
    }

admin.site.register(Yazi, YaziAdmin)
Formda bulunan tüm TextField alanları artık CKEditor olacaktır. İşte bu tarz özelleştirmeler için kullanbileceğimiz bir özelliktir.
CKEditör'ün Django'da nasıl kullanıldığı bu yazının konusu olmadığı için anlatılmayacaktır. Kullanımına buradan bakabilirsiniz.
Veya şöyle kullanarak Model içerisindeli CharField alanlarına class ataması yapabilirsiniz.
class YaziAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.TextField : {'widget':CKEditorWidget},
        models.CharField : {'widget':forms.TextInput(attrs={'class':'sinif'})}
    }

inlines
inlenes seçeneği ile Ebeveyn Model'e ait olan alt modelleri aynı sayfada düzenleyebilmemiz olanağını verir. Yani bir yazar eklerken aynı zamanda Yazar'a ait kitapları da ekleyebiliriz. Alanın ForeingKey olması gerekir. Elimizde şöyle iki model olduğunu düşünelim.
class Yazar(models.Model):
    isim = models.CharField(max_length=100)

class Kitap(models.Model):
    yazar = models.ForeignKey(Yazar)
    baslik = models.CharField(max_length=100)
    tarih = models.DateField()
Şimdi yazar ekleme veya düzenleme işlemi yaparken aynı zamanda yazara ait kitapları da düzenleyebilmemizi sağlayacak inlines tanımlamasını yapalım.
class KitapInline(admin.StackedInline):
    model = Kitap

class YazarAdmin(admin.ModelAdmin):
     inlines = [KitapInline]

admin.site.register(Yazar, YazarAdmin)
Burada KitapInline içerisinde hangi modeli kullanacağını belirtiyoruz. YazarAdmin içerisinde ise oluşturduğumuz inline tanımlamasını gösteriyoruz.
Inline tanımlaması yaparken admin.StackedInline'ı kullandık eğer isterseniz admin.TabularInline tanımlasını kullanabilirsiniz. Tek fark olarak Kitap modeli içerisindeki alanlar yan yana görünecektir.
list_display
Model elemanları listelenirken hangi elemanların gösterileceğini belirlememizi sağlar. Örneğin şöyle bir kullanım yapabiliriz.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', 'tarih']
Kitabın yazarını almak için şöyle bir yol izleyebiliriz.
def yazari(obj):
    return  obj.yazar.isim
yazari.short_description = 'Kitap Yazarı'

class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', yazari, 'tarih']
veya,
class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', 'yazari', 'tarih']

    def yazari(self, obj):
        return  obj.yazar.isim
    yazari.short_description = 'Kitap Yazarı'
Eğer göstermek istediğimiz alan ForeignKey bir alan ise Model içerisindeki __unicode__() fonksiyonu çağrılır. Bizim yukarıda yaptığımız işlemi şu şekilde de yapabiliriz.
class Yazar(models.Model):
    isim = models.CharField(max_length=100)

    def __unicode__(self):
        return self.isim

class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', 'yazar', 'tarih']
list_display ManyToManyField alanlarını desteklemez. Bu alanları göstermek için yine özel fonksiyon yazmalıyız. Önceki Kitap Yazar arasıdanki ForeignKey ilişkisini ManyToManyField haline getirelim.
def yazarlari(obj):
    return ",".join(yazar.isim for yazar in obj.yazar.all())
yazarlari.short_description="Kitap Yazarları"

class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', yazarlari, 'tarih']
ManyToManyField alanları göstermek için ise bu şekilde bir yöntem kullanabiliriz.
Eğer alan BooleanField veya NullBooelanField ise list_display ile gösterilirken Django'nun iconları ile gösterilirler. Daha önce oluştmuş olduğumuz Yazi modeli üzerinden gösterelim.
class YaziAdmin(admin.ModelAdmin):
    list_display = ('baslik','taslak' )
Eğer isterse alanlar gösterilirken html tagleri kullanılabilir ve stil verilebilir. Örneğin kitap isimlerini renkli gösterelim.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['stil_baslik', yazarlari, 'tarih']

    def stil_baslik(self,obj):
        return '%s'%obj.baslik
    stil_baslik.allow_tags = True
Herhangi bir fonksiyon yazabilir ve bu fonksiyonun dönüşüne göre Django'nun Boolean alan ikonlarını gösterebiliriz.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['stil_baslik', yazarlari, 'yil']

    def yil(self,obj):
        return int(obj.tarih.strftime('%Y')) < 2000
    yil.boolean = True
Eğer kitap 2000 yılından önceye ait ise Django'nun True ikonu gösterilecektir.
Örneğin kitap yazarlarını gösterirken yazara tıkladığında düzenleme sayfasına yönlendiren bir fonksiyon yazalım. Böylece her yazar düzenleme sayfasına giden bir link bağlamış olacağız.
def yazarlari(obj):
    yazarlar=""
    for yazar in obj.yazar.all():
        yazarlar = yazarlar + ''+yazar.isim+''+" "
    return  yazarlar
yazarlari.allow_tags = True
yazarlari.short_description="Kitap Yazarları"
Burada site adını veri tabanından çekmek daha doğru olacaktır.
list_display_links
Model elemanları listelenirken değiştirme sayfasına gidilmesi için varsayılan olarak list_display ile belirtilen ilk alana link bağlanmıştır. Eğer bunun değiştirilmesi isteniyorsa kullanılır.
    list_display = ['stil_baslik', yazarlari, 'yil']
    list_display_links = ['stil_baslik',yazarlari]
Not : list_display_links içerisinde tanımlanan her alanın list_display içerisinde tanımlanmış olması gerekir.
list_editable
Model elemanları listelenirken aynı zamanda düzenlenebilme özelliği sağlar. Bu şekilde örneğin text içeriği olan bir eleman text input'una dönüşür ve eleman düzenlenebilir.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik', yazarlari, 'tarih']
    list_display_links = [yazarlari]
    list_editable = ['baslik','tarih']
Not : list_editable içerisinde tanımlanmış alanın list_display içerisinde tanımlanmış list_display_links içerisinde tanımlanmamış olması gerekir.
list_filter
list_filter listeleme yapılırken sağ tarafta filtreleme alanının çıkmasını sağlar. Bu alana Django'nun Kullanıcılar modelinden aşınayız zaten.
Bu şekilde bir alan çıkmasını sağlar. Örneğin daha önce oluşturmuş olduğumuz Yazi modelinde taslak alanı bulunmaktadır. Buradaki taslak alanına göre filtreleme yaptırabiliriz.
class YaziAdmin(admin.ModelAdmin):
    list_display = ('baslik','taslak', )
    list_filter = ('taslak', 'kategori', )
list_filter'a yazılan alanlar BooleanField, CharField, DateField, DateTimeField, IntegerField, ForeignKey veya ManyToManyField olabilir. Bizim yaptığımız örnekte taslak alanı BooleanField iken kategori alanı ManyToManyField. Şu şekilde görünecektir.
Filtreleme için kendi fonksiyonlarınızı yazabilirsiniz. Yazdığınız fonksiyona göre filtreleme olacaktır. Bunun için Django'nun kendi sitesinde çok güzel bir örnek bulunmaktadır. Buradan ulaşarak inceleyebilirsiniz.
list_per_page
Sayfalama yapılarak her sayfada kaç eleman gösterileceğini belirtir. Varsayılan olarak 100 gelmektedir.
class YaziAdmin(admin.ModelAdmin):
    list_per_page = 1

prepopulated_fields
slug veya başka söyleyiş ile sef_link alanı oluşturmak için kullanılır. slug kısaca herhangi bir textin tarayıcı url'ine uygun haline getirilmiş halidir. Örneğin;
Text = bu bir yazıdır.
Slug = bu-bir-yazidir
Yazi modelimizi baslik_slug alanı ekledik.
class Yazi(models.Model):
    baslik  = models.CharField(max_length=100)
    baslik_slug = models.CharField(max_length=100)
    ...
Bu özelliği kullandığımız zaman baslik alanı doldurulurken bir Javascript kodu çalışacak ve eş zamanlı olarak baslik_slug alanı otomatik olarak doldurulacaktır.
class YaziAdmin(admin.ModelAdmin):
    prepopulated_fields = {
        'baslik_slug':('baslik',)
    }

radio_fields
ForeignKey bir alan seçilirken default olarak bir seçim kutusundan seçim yapılır. Eğer bir böyle olmasını değil de seçim düğmeleri ile seçim yapılmasını istersek bu özelliği kullanabiliriz. Daha önceki Yazar,Kitap modellerini hatırlayalım.
class Yazar(models.Model):
    isim = models.CharField(max_length=100)

    def __unicode__(self):
        return self.isim

class Kitap(models.Model):
    yazar = models.ForeignKey(Yazar)
    baslik = models.CharField(max_length=100)
    tarih = models.DateField()
Şimdi buradaki Kitap içerisindeki ForeignKey için bu özelliği uygulayalım.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik','yazar', 'tarih']
    radio_fields = {'yazar':admin.HORIZONTAL}
Buradaki HORIZONTAL yerine VERTICAL kullanabilirsiniz. Tek fark olarak seçim düğmeleri alt alta görünecektir.
raw_id_fields
Az önce belirttiğimiz gibi seçim alanı olarak bir seçim kutusu gelir. Bu seçim kutusunu seçim düğmelerine çevirdik. Bu seçenek ise seçim yapma işlemini id üzerinden yapılmasını sağlar. Örnek üzerinden gösterelim. Daha önceki Yazı modeli içerisindeki kategori kısmını bu seçenek içerisinde kullanalım.
class YaziAdmin2(admin.ModelAdmin):
    raw_id_fields = ['kategori']
raw_id_fields ManyToManyFields ve ForeignKey alanları için kullanılabilir.
readonly_fields
Alanları sadece okunabilir yapmak için kullanılır. Buraya girilen alanlara müdahale edilemez. Örnek olarak Kitap modelinde id alanını gösterelim ama kullanıcı müdahale edemesin.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['baslik','yazar', 'tarih']
    readonly_fields = ['id']


save_as
Varsayılan olarak False gelir. Bir modele eleman eklenirken veya düzenlenirken kaydet kısmında üç buton bulunur. Bunlar "Kaydet", "Kaydet ve düzenlemeye devam et" ve "Kaydet ve yenisini ekle". Eğer bu seçenek True yapılırsa var olan bir eleman düzenlenirken "Kaydet ve yenisini ekle" butonu "Yeni olarak kaydet" haline gelir. Bu butona tıklandığında ise yapılan değişiklikler yeni eleman olarak kaydedilir ve eskisi de kalır.
class KitapAdmin(admin.ModelAdmin):
    save_as = True

save_on_top
Varsayılan olarak False gelir. Bahsettiğimiz bu üç buton sayfanın altında bulunur. Eğer sayfanın üstünde de bulunması istenirse bu seçenek kullanılır.
class KitapAdmin(admin.ModelAdmin):
    save_on_top = True

search_field
Model elemanlarının listeleme sayfasına arama formu eklenmesini sağlar. Arama işlemi yapılırken search_field ile belirtilen listedeki sütunlar baz alınarak yapılır.
class KitapAdmin(admin.ModelAdmin):
    list_display = ['id','baslik','yazar', 'tarih']
    search_fields = ['baslik','yazar__isim']
Bu aramanın sorgusu şu şekildedir;
WHERE (baslik ILIKE '%dijital%' OR yazar__isim ILIKE '%dijital%')
AND (baslik ILIKE '%brown%' OR yazar__isim ILIKE '%brown%')
Sorgu içerisindeki like ifadesinde '%ifade%' kullanımı dikkatinizi çekmiştir. Eğer 'ifade%' veya '%ifade' şeklinde olmasını istersek ^ ifadesini kullanmalıyız.
class KitapAdmin(admin.ModelAdmin):
    search_fields = ['^baslik','^yazar__isim']
Sorgunun yeni hali şöyledir;
WHERE (baslik ILIKE 'dijital%' OR yazar__isim ILIKE 'dijital%')
AND (baslik ILIKE 'brown%' OR yazar__isim ILIKE 'brown%')
Eğer tam eşleşme yapmak istersek = ifadesini kullanmalıyız.
class KitapAdmin(admin.ModelAdmin):
    search_fields = ['=baslik','=yazar__isim']
Sorgunun yeni hali şöyledir;
WHERE (baslik ILIKE 'dijital' OR yazar__isim ILIKE 'dijital')
AND (baslik ILIKE 'brown' OR yazar__isim ILIKE 'brown')
Sorgulardaki ManyToManyField alanı olan yazar için sorgu bu şekilde değildir. Sadece göstermek amacı ile bu şekilde kullandık. Kaynakça

14 Temmuz 2014 Pazartesi

Hata Raporlama

Django'da bir proje oluşturduğumuz o zaman varsayılan olarak hata ayıklama(DEBUG) kipi açık olur. Bu biz kodlarımızı yazarken yazım hatalarını bulabilmemizi sağlaması içindir. Yani var olan hata ile ilgili Django bizi bilgilendirir. Biz de bu bilgilendirme sayesinde nerede hata yaptığımız anlayabiliriz. Ama yaptığımız projeyi yayınlarken bu şekilde olması güvenlik açısından problem teşkil etmektedir. Bu şekilde kullanıcılar projemiz hakkında bilgi sahibi olabilir, yazdığımız kodların bir kısmını görebilirler. Kullanıcılar bu bilgileri kullanarak istemediğimiz sonuçlara sebep olabilir. Bu yüzden hata ayıklama kipini kapatıyoruz. Bunun için settings.py dosyası içerisindeki DEBUG değişkeninin değerini False yapıyoruz.
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
Gördüğünüz gibi Django da bize hata ayıklama kipi açıkken projemizi yayınlamamız konusunda güvenlik uyarısı veriyor. Bu ayarı yaptıktan sonra projenizi başlatmak isterseniz şöyle bir hata alacaksınız.
$ python manage.py runserver
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
Django bizi yine uyarıyor. Proje için izinli hostları belirtmemiz gerekiyor. Bunun yüzden settings.py dosyası içerisinde ALLOWED_HOSTS değişkenini içerisinde izinleri vermeliyiz.
ALLOWED_HOSTS = ['*']
İster bu şekilde tüm host'lara izin verebilirsiniz. Veya sadece kendi belirlediklerinizi izin verebilirsiniz.
ALLOWED_HOSTS = ['127.0.0.1','localhost']
Güvenlik açısından tavsiye edilen yöntem budur. Bunu yaptıktan sonra herhangi bir sayfayı açmaya çalıştığınızda style dosyalarının yüklenmediğini göreceksiniz. Bu ayarlamaları yaptıktan sonra statik (CSS dosyaları da dahil) dosyaların yüklenebilmesi için projemizi --insecure parametresi ile çalıştırmalıyız.
python manage.py runserver --insecure
Sunucu Hataları (500)
Sunucudan kaynaklanan hatalar. Projeyi geliştirirken yazdığımız kodlarda bir hata oluşursa Django bunu tarayıcıda bize basıyordu. Bu şekilde hata ile ilgili bizi bilgilendiriyordu. Örneğin;
Bu durum DEBUG = True olduğu durum için geçerlidir. Biz projemizde False yaptığımız için ekrana bu bilgi basılmayacaktır. Onun yerine ekranda sadece Server Error (500) yazar. Buradan da anlayabileceğiniz gibi hatanın kodu 500'dür. Yani aslında kullanıcıya 500.html sayfası gösterilir ve ardından settings.py dosyasında belirtilen ADMINS kullanıcılarına hata e-posta olarak gönderilir.
Şimdi settings.py dosyasında ADMINS değişkenine yönetici kullanıcıları belirtiyoruz.
ADMINS = (
    ('Mazlum Agar','mazlum.agar@gmail.com'),
)
Django tarafından gönderilen postaların kimin tarfından gönderilmesini istiyorsak SERVER_EMAIL değişkenine belirtiyoruz.
SERVER_EMAIL = 'Django Uyari <django@mail.com>'
Artık hata oluştuğunda gösterilecek 500.html sayfasını oluşturabiliriz. Bu sayfayı oluşturduktan sonra istediğiniz bir şablon dizine atabilirsiniz. Biz hastahane/sablonlar dizini içerisine atıyoruz.
hastahane/sablonlar/500.tml
<!DOCTYPE html>
<html>
<head>
    <title>Erişilemez</title>
</head>
<body>

<h2>Teknik hata</h2>


<p>Üzgünüz,sunucuda oluşan teknik bir hatadan dolayı istediğiniz sayfa gösterilemiyor.</p>

<img src="/dosyalar/500.jpg" />
</body>
</html>
Artık kullanıcılar şöyle bir sayfa görecekler.
Sayfa Bulunamadı (404)
Projede var olmayan bir url girildiğini Django bize yukarıdaki gibi sayfanın olmadığına dair bir hata basıyordu. Bu sayfada var olan tüm urlleri gösteriliyordu. Herhangi bir kullanıcının bu urlleri görmesi tehlikeli olabilir. Normal kullanıcılar için ise bu sayfa mantıklı olmayacaktır. Bu yüzden kendi 404 sayfamızı oluşturacağız. İlk önce şablon içerisinde request nesnesini ve django yönetim panelini kullanmak istersek settings.py dosyasına şu satırları ekliyoruz.
TEMPLATE_CONTEXT_PROCESSORS = (
     #Oluşturacağımız 404 sayfasında request nesnesini kullanmak için
    'django.core.context_processors.request',
     #Yönetim panelini kullanmak için
    'django.contrib.auth.context_processors.auth'
)
Son olarak 404.html sayfasını oluşturarak aynı dosya içerisine kaydedelim.
hastahane/sablonlar/500.tml
<!DOCTYPE html>
<html>
<head>
    <title>Sayfa Bulunamadı</title>
    <link rel="stylesheet" type="text/css" href="/static/base.css" />
</head>
<body>

<img src="/static/404.jpg" width="300" height="300"/>

<h2>Sayfa Bulunamadı</h2>

<p>Aradığınız sayfa buluanamadı.</p>

<br>Erişmek istediğiniz sayfa : <b>{{ request.build_absolute_uri }}</b>

</body>
</html>
Kullanıcı olmayan bir sayfaya ulaşmaya çalıştığı zaman;
Kaynakça

10 Temmuz 2014 Perşembe

Başka URL Dosyası İçermek

Şimdiye kadar tüm url tanımlamalarını hastahane/urls.py dosyasında yaptık. Bizim yaptığımız projede bu durum önemli olmayabilir. Ama büyük projelerde uygulama sayısı arttıkça karmaşıklıkta artacaktır. Bu yüzden her uygulamanın kendi url dosyası oluşturularak. hastahane/urls.py dosyasında bu dosyalar içe aktarılabilir. Bu şekilde çok daha anlaşılır tanımlamalar yapmış olabiliriz. Şu ana kadar yaptığımız tüm görünümler yonetim/views.py dosyasında ama biz görmemiz açısından şu link tanımlamalarımızı yonetim/urls.py dosyasına taşıyalım.
Daha önceden hastahane/urls.py dosyasında tanımladığımız bazı urller.
    url(r'^yonetim/$',yonetim.views.yonetim),
    url(r'^yonetim/profil/$','yonetim.views.kullanici_profili'),
    url(r'yonetim/profil/profil_resimleri/(.*)','yonetim.views.profil_resimleri'),
Gördüğünüz gibi tüm linkler yonetim ile başlıyor. yonetim/urls.py dosyasını şu şekilde oluşturalım.
from django.conf.urls import patterns,url


urlpatterns = patterns('yonetim.views',
                        url(r'^$','yonetim'),
                        url(r'^profil/$','kullanici_profili'),
                        url(r'^profil/profil_resimleri/$','profil_resimleri'),
                       )
Burada yonetim kısmını yazmadığımıza dikkat edin. Onu hastahane/urls.py dosyasında belirteceğiz. Görünümleri yüklerken yonetim.views.yonetim şeklinde yazmamıza da gerek kalmadı. patterns() fonksiyonuna verdiğimiz ilk parametre olan yonetim.views tüm görünümlerin bu dosya içinde aranacağını belirtiyor.
Son olarak yonetim/urls.py dosyasını hastahane/urls.py dosyasında içe aktaralım.
url(r'^yonetim/',include('yonetim.urls')),
Kaynakça

7 Temmuz 2014 Pazartesi

Kullanıcı Profili

Projemizde kullanıcılarımız bulunmaktadır. Fakat bu kullanıcıların herhangi bir profilleri yok. Bu bölümde bunu yapalım. Her kullanıcının bir profili olsun. Bu profilde resim,telefon,şehir gibi bilgilerini alalım. Django'da resim yani ImageField alanını kullanabilmek için PIL kütüphanesinin kurulu olması gereklidir. Bu kütüphanenin nasıl kurulacağını CAPTCHA Kullanımı yazısında anlatmıştık. Django'da yüklenecek dosyaların yüklenebilmesi için settings.py dosyasında MEDIA_ROOT değişkeninde yüklenecek yerin belirtilmesi gereklidir. Hatırlarsanız daha önceden statik dosyaları tuttuğumuz statik_dosyalar dizinimiz vardı. Resimleri de bu dizin içerisinde resimler adında oluşturacağımız bir dizinde saklayacağız. Bunun için hastahane/settings.py dosyasına şu satırı ekliyoruz.
MEDIA_ROOT = os.path.join(anadizin,'statik_dosyalar/resimler')
Django'da resimler yüklendikten sonra nesne üzerinde bir değişiklik olduğunda veya silindiğinde bulunduğu dizinde kalmaktadır. Bu da proje için gereksiz yük demektir. Ya bu şekilde kalan resimleri silme işlemini kendimiz yapacağız yada Django'nun bize sağlamış olduğu django-cleanup modülünü kullanacağız. İkinci yol daha mantıklı olduğu için biz de bu yolu seçeceğiz. django-cleanup modülünü http://github.com/un1t/django-cleanup adresinden indirebilirsiniz. İndirdiğiniz dosyayı açtıktan sonra bu dizine girerek şu komutu vermeniz gereklidir.
python setup.py install
Windows kullananlar için;
pythonunkuruluoldugudizin/python.exe setup.py install
şeklinde olacağını söylememize gerek yok sanırız. Modülü kurduğumuza göre artık projemize dahil edebiliriz. Bunun için hastahen/settings.py dosyasındaki INSTALLED_APPS değişkeninin her zaman en sonunda olacak şekilde şu satırı ekliyoruz.
'django_cleanup',
Artık kullanıcı profiline ait modeli tanımlayabiliriz. yonetim/models.py dosyasına yazıyoruz.
import os

def resim_patikasi_belirle(instance,dosyaadi):
    uzanti = os.path.splitext(dosyaadi)[-1]
    dosyanin_kayit_adi = os.path.join('profil_resimleri',str(instance.user.id)+uzanti)
    return dosyanin_kayit_adi

class KullaniciProfili(models.Model):
    user = models.OneToOneField(User,unique=True,related_name='KullaniciProfili')
    resim = models.ImageField(upload_to=resim_patikasi_belirle)
    sehir = models.CharField(max_length=30,blank=True)
    telefon = models.CharField(max_length=11,blank=True)
    okul = models.CharField(max_length=40,blank=True)
Modeldeki user satırında OneToOneField alanını kullandığımıza dikkat edin. Bu alan sayesinde bu modeldeki her veri bir kullanıcı ile ilişkili olacaktır. Profil modelinin hangi kullanıcılara uygulanacağını related_name özelliği ile belirtiyoruz. Eğer bir profilin tüm kullanıcılara uygulanmasını istiyorsanız related_name argümanı yerine,hastahane/settings.py dosyasına şu satırı ekleyebilirsiniz.
AUTH_PROFILE_MODULE = 'yonetim.KullaniciProfili'
Bu yöntemi kullanmıyoruz. Bizim kullandığımız yöntem sayesinde birden fazla kullanıcı profili oluşturabiliriz.
Dosya içerisinde resmin yükleneceği yeri belirleyen bir işlev tanımladık. Bu işlev iki iki parametre almaktadır. instance parametresi sayesinde yüklenen resmin hangi kullanıcı tarafından yüklendiğini öğrenebiliriz. instance.user nesnesi kullanıcı ile ilgili her bilgiyi barındırır. dosyaadi ise yüklenen dosyanın adıdır. Dosyanın adını alarak uzantısını elde etmiş oluyoruz. Burada işlev dosyanın kayıt edileceği yer + kullanıcı numarası + resim uzantısı şeklinde resmi geri döndürüyor. Yani 10 numaralı kullanıcı jpg türünden bir resim yüklediğinde statik_dosyalar/resimler/profil_resimleri/10.jpg şeklinde resim kayıt edilecektir.
Oluşturduğumuz modeli kullanarak formu oluşturalım. yonetim/forms.py dosyasına ekliyoruz.
from django.db.models.signals import post_save

class KullaniciProfiliFormu(forms.ModelForm):
    class Meta:
        model = KullaniciProfili
        exclude = ('user',)

def kullanici_profili_olustur(sender,instance,created,**kwargs):
    if created:
        KullaniciProfili.objects.create(user=instance)

post_save.connect(kullanici_profili_olustur,sender=User)
İlk önce KullaniciProfili modelini kullanarak formu oluşturduk. Buradaki kullanici_profili_olustur işlevi her kullanıcı için KullanıcıProfili tablosundan kullanıcı için bir satır oluşturacaktır. Buradaki form kaydedildiğinde post_save sinyali yayımlanır. Bu sinyali ise post_save.connect işlevi sayesinde yakalıyoruz. Bu işleve şunu diyoruz: Form kayıt sinyalini yakaladığın anda kullanici_profili_olustur() işlevini çağır. Bu işlev çağrılır ve kullanıcı için bir satır oluşturulur. Eğer bir kullanıcı bu şekilde oluşturulmamış ise profili oluşturulamayacaktır. Bunun kontrolünü görünüm içerisinde yapacağız.
yonetim/views.py dosyasına ekliyoruz.
def kullanici_profili(request):
    from models import KullaniciProfili
    if not KullaniciProfili.objects.filter(user=request.user.id):
        KullaniciProfili.objects.create(user=request.user)

    if request.method == "POST":
        form = KullaniciProfiliFormu(request.POST,request.FILES,instance=request.user.KullaniciProfili)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/yonetim/')
    else:
        form = KullaniciProfiliFormu(instance=request.user.KullaniciProfili)
    return render_to_response('kullanici_profili.html',locals(),context_instance=RequestContext(request))
Az önce görünüm içerisinde kontrolünü yapacağımızı söylemiştik. 2,3,4. satırlarda bu kontrolü yapıyoruz. Eğer kullanıcı bu şekilde oluşturulmamış ise KullaniciProfili tablosunda o kullanıcıya satır oluşturulamamış demektir. Biz de oluşturma işlemini burada yapıyoruz.
3.satır: Eğer kullanıcı için ZiyaretciProfili tablosunda satır oluşturulmamışsa
4.satır: Bu kullanıcıya satır oluştur.
Son olarak bu görünüm için şablonumuzu oluşturalım. /yonetim/sablonlar/kullanici_profili.html oluşturuyoruz ve ekliyoruz.
<!DOCTYPE html>
<html>
<head>
    <title>Kullanıcı Profili</title>
    <link rel="stylesheet" type="text/css" href="/dosyalar/style.css" />
</head>
<body>

<h1>Profilim</h1>


<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <table border="1">
        {{ form }}
    </table>
    <input type="submit" value="Profil Kayıt" />
</form>


</body>
</html>
Şablonu da oluşturduğumuza göre url tanımlamamızı yapabiliriz. hastahane/urls.py dosyasına ekliyoruz.
url(r'profil/profil_resimleri/(.*)','yonetim.views.profil_resimleri'),
Her şey tamam yapmamız gereken tek şey oluşturduğumuz model için tabloların oluşmasını sağlamak. Bunun için komut satırında şu komutu veriyoruz.
python manage.py syncdb
Daha önce oluşturduğumuz yonetim_giris.html şablonuna şu linki ekliyoruz.
<div><a href="/profil/"/>Profilim</div>
Şimdi sunucuyu yeniden başlatın ve bir kullanıcı ile giriş yaptıktan sonra. Eklediğimiz linke tıklayın veya tarayıcıdan http://localhost/profil adresine gidin.
Bu pencerenin boş hali ile karşılaşacaksınız. Kullanıcı için bu bilgileri doldurduğunu zaman ise bu pencere gelecek karşınıza. Dikkat ederseniz var olan resim için bir link verilmiş. Fakat bu link şu anda çalışmayacaktır. Çünkü bu link ile ilgili görünümü yazmadık. Şimdi onu yazalım.
yonetim/views.py dosyasına ekliyoruz.
from django.conf import settings
def profil_resimleri(request,resim_adi):
    dosya_yolu = os.path.join(settings.MEDIA_ROOT,'profil_resimleri',resim_adi)
    if os.path.join(dosya_yolu):
        resim = os.path.join(settings.MEDIA_ROOT,'profil_resimleri',resim_adi)
        uzanti = os.path.splitext(resim_adi)[-1][1:]
        mime_tipi='image/%s' % uzanti
        resim_bilgi = open(resim,"rb").read()
        return HttpResponse(resim_bilgi,mimetype=mime_tipi)
    else:
        resim_bilgi = open(os.path.join(settings.MEDIA_ROOT,'resim_yok.png'),"rb").read()
        return HttpResponse(resim_bilgi,mimetype='image/png')
Görünümde resim_adi ile tarayıcıdan gelen resmin adını alıyoruz. Ve bu resmin yolunu dosya_yolu değişkenine atıyoruz. Eğer bu resim var ise resmi yok ise de resim_yok.png'i kullanıcıya gösteriyoruz. Resmin görülebilmesi için url'li de ekleyelim. hastahane/urls.py dosyasına ekliyoruz.
url(r'profil/profil_resimleri/(.*)','yonetim.views.profil_resimleri'),
Artık linke tıklandığında resmin açılacağı bir sayfaya gidilecek. Son olarak kullanıcıya yönetim sayfasında kendi resmini gösterelim. Bunun için yonetim/sablonlar/yonetim_giris.html şablonuna şu satırı ekliyoruz.
<img src="/dosyalar/resimler/{{request.user.KullaniciProfili.resim.url}}" width="150" height="150"/>
Tarayıcıdan http://localhost:8000/yonetim/ yazdığınızda kullanıcı resmini de görebilirsiniz.
Kullanıcı profili oluşturmak bu kadar.
Kullanıcı profili oluşturabilmek için veri tabanında yeni bir tablo oluşturduk. Bu tabloyu yonetim sayfasına eklemediğimiz için Djando Yönetim Panelinde bu tabloyu göremeyeceksiniz. Görebilmek için yonetim/admin.py dosyasına şu satırı ekleyelim.
admin.site.register(KullaniciProfili)
Artık Yönetim Panelinden de kullanıcı profillerini yönetebiliriz. Tablonun daha anlaşılır olabilmesi için KullaniciProfili modeline şu satırları ekleyebilirsiniz.
    def __unicode__(self):
  return u'%s' % (self.user)
Yönetim Paneline Kullanıcı Profilleri alanı geldi.
Artık projemizdeki kullanıcılar profile sahip olmuş oldu.
Kaynakça