Bu uygulamamızda bir kütüphane yönetim sistemi yapmaya çalışacağız. Bu uygulamada bazı kitaplarımız olacak ve bu kitapları ödünç alan insanlar olacak. Bu uygulamanın az bir eksiği de mevcut; bunları blogun sonunda "Geliştirme Önerileri" başlığı altında size söyleyeceğim ve gerisini size bırakacağım. Hazırsak başlayalım.
İlk önce bir adet library.py dosyası ve bir adet library.db dosyası açıyorum. Sonrasında library.py içerisine girip gerekli kütüphanelerimi tanımladıktan sonra bir class tanımlıyorum:

sqlite3 ve tkinter modüllerimin yanında class yapımız daha kısmen hazır. Şimdi, daha önce göstermediğim bir şeyi göstereceğim. O da class yapıları ile tkinter yapılarını birleştirerek kullanmak; bu konuda sıkıntı yaşayan birçok arkadaşımız var.
Bunun için ilk önce bir if __name__ == "__main__" bloğu açıyorum. İçerisine neler yazdığımı birlikte görelim:

root = Tk() ve root.mainloop() ifadelerinin ne olduğunu zaten biliyorsunuz. Bunlar, tkinter modülünün çalışabilmesi için gerekli yapılar. Bu iki yapı arasına bakarsanız rootObject = Library(root) dediğimizi görürsünüz. Aslına bakarsanız bu da bir obje oluşturma işlemi; farkı ise, bu objeyi class yapısının içine gömüyor olmamızdır. Library(root) diyerek aslında Library sınıfına root objesini referans veriyoruz. Peki, bitti mi? Hayır.
Şimdi, __init__ fonksiyonumu tanımlıyorum:

Şimdi, dikkatlice okumanızı istiyorum. Biz, if __name__ == "__main__" içerisine root objesini verdik. Hatta Library sınıfına da root objesini referans olarak verdik. Ancak, hatırlayacağınız üzere __init__ fonksiyonu, başlangıçta olan biten her şeyi kontrol eden yapıydı. Dolayısıyla burada milyon tane argüman vermek yerine yalnızca self ve root argümanlarını verdim. Bu sayede root objesi yani tkinter penceresi sorunsuz ve sürekli bir biçimde çalışacaktır.
Dikkat ederseniz root penceresinin özelliklerini de __init__ içerisine koydum ama bunları argüman olarak vermedim. Çünkü böyle bir şeye gerek yok; root'un kendisini __init__ fonksiyona verdiğimiz için özellikleri direkt olarak tanımlamamızda bir sakınca yok.
Şimdi, başlığımızı belirleyelim. Bunun için bir Label oluşturuyorum:

Label yapısı hazır; içerisine root değil, self.root değerini verdiğimi de görüyorsunuz. Artık root yok, self.root var. Ayrıca dikkat edelim; Label dahil birazdan yapacağımız her şeyi __init__ içerisine koyuyoruz. self.root yapısı __init__ ile başlatıldığı için içerisine koyacağımız nesneleri de __init__ altına girmemiz lazım. Dilerseniz bunu __init__ içerisinden çıkarıp ne olacağına bakalım.

self.root yapısına erişemediğimizi görüyorsunuz. Artık root yerine self.root olduğunu da söylemiştim. Dolayısıyla oraya self.root yerine root yazsanız da bu iki yapı birbirini görmeyecektir.
Peki, neden self.titleLabel yapmadık da sade bir şekilde titleLabel dedik? Çünkü bu statik bir yapı olarak kalacak; sonra başka yerde kullanmama gerek olmadığı için self demeye gerek yok. Ancak, self.titleLabel yapısını da kullanabilirsiniz; sorun olmayacaktır.
Nasıl göründüğüne bakalım:

Yani, belki biraz daha sağa kaydırmak gerekebilir ama bu da iş görür.
Devam etmeden önce veri tabanı bağlantımızı gerçekleştirelim. Bunun için class yapısının içine, __init__ fonksiyonunun dışına yazmaya başlıyorum:

Dikkat edelim; self kullanmadan, direkt olarak sınıfın en başına bu bağlantıyı yazdım. İlerleyen kısımlarda hep en son kısma connection.close() demek ve bağlantıyı sonlandırmak istemeyiz. Bunu, self.root penceresi yani tkinter penceresi kapandığında yapmak istiyoruz. Bunun için @classmethod kullanarak ve içerisine cls argümanını vererek return self.connection.close() demek yerine return cls.connection.close() dedim. Bu sayede bu bir sınıf fonksiyonu konumuna düştü ve ben bunu istediğim yerde kullanabilirim. Peki, nerede kullanacağım?

Library.connectionClose() derken aslında "Library sınıfındaki connectionClose() metodunu getir" demiş oluyoruz. Peki, burada ne oldu? tkinter penceresi açık olduğu sürece root.mainloop() satırı çalışır, öyle değil mi? tkinter penceresi kapanırsa root.mainloop() satırı da çalışmaz, değil mi? İşte, tam olarak bu sebepten ötürü Library.connectionClose() kodunu root.mainloop() satırından sonra yazdım. Bu sayede pencere kapandıktan hemen sonra veri tabanı bağlantısı da kesilecek ve işlerimiz sekteye uğramayacak.
Bunu da hallettiğimize göre şimdi, alanlarımızı yani LabelFrame yapılarımızı belirleyelim. Dediğim gibi kitapların listesi, kitap ödünç alanların listesi ve bir ödünç verme sistemimiz olacak. Dolayısıyla üç adet LabelFrame yapısına ihtiyacım var. İlk önce kitaplarımızın listeleneceği yapıyı kuralım.

Dikkat edelim; __init__ içerisindeyiz ve değişken ismi oldukça açıklayıcı; self.showBooksFrame. Geri kalan şeyleri zaten biliyorsunuz; konum, renk vesaire.
Ancak, bu noktada bizim bir sıkıntımız var. Kitapların sayısı çoğaldıkça LabelFrame yapısının içerisine sığmamaya başlayacak ve sorunlar olacak. O yüzden şimdi, daha önce görmediğiniz bir şey göstereceğim; Listbox yapıları.
Listbox yapıları, adından da anlayacağınız üzere liste kutusudur ve bir şeyleri liste halinde saklamamıza olanak tanır. O zaman nasıl olduğunu görelim.

Şimdi, bunları satır satır açıklamadan önce 30. satırdaki kodda bulunan ters slash işaretini silip o satırı düz hâle getirmenizi istiyorum. Ben bunu, kodun tamamını görmeniz için yaptım. Eğer düzeltmezseniz yüksek olasılıkla hata alırsınız.
28. satırda self.srcbar = Scrollbar(self.showBooksFrame) ifadesini görüyorsunuz. Bu ifade, kaydırma çubuğunu ifade eder. Genelde bir sitede sağ tarafta bulunan nesnedir. Burada tkinter içerisinde bulunan Scrollbar ifadesini çağırıp bunu self.showBooksFrame içerisine gömdüm. Aynı zamanda bu yapıyı, scrbar adı altında self.scrbar şeklinde tanımladım.
29. satırda bulunan self.scrbar.pack(side=RIGHT, fill=Y) ifadesi ise bu kaydırma çubuğunun ekranda görünmesini sağlıyor. Bu noktada side=RIGHT ifadesi, bu kaydırma çubuğunun sağ tarafta olmasını sağlarken fill=Y ifadesi, "y ekseni boyunca doldur" anlamına geliyor.
side: AlanRIGHT: Sağ taraffill: DoldurmakY: y ekseni (dikey)30. satırda self.myList = Listbox(self.showBooksFrame) diyerek Listbox yapısını LabelFrame içerisine koyuyoruz. Sonrasında gelen self.yscrollcommand=self.scrbar.set ifadesiyle, yscrollcommand= değişkenine biraz önce self.scrbar olarak tanımladığımız kaydırma çubuğunu .set diyerek atıyoruz. yscrollcommand= ifadesi, Button yapılarıdaki command= ifadesine benzer yalnızca ismi farklıdır. Bu noktada "y ekseninin scroll (kaydırma) özelliğinin command değeri (yapacağı iş)" olarak düşünebilirsiniz. İşte buna, biraz önceki self.scrbar ifadesini .set ile atıyoruz.
32. satırda self.myList.pack(side=LEFT) ifadesini görüyorsunuz. Burada myList isimli Listbox yapısının ekranda görünmesini sağlıyoruz. side=LEFT ifadesi ise, bunun sol taraftan başlayacağını ifade eder.
LEFT: Sol tarafSon olarak 33. satırda self.scrbar.config(command=self.myList.yview) ifadesini görüyorsunuz. Bu noktada biz, self.scrbar olarak tanımladığımız kaydırma çubuğunu config komutu ile düzenleyeceğimizi söylüyoruz ve bu düzenlemeyi parantezler içerisine yazıyoruz. Parantez içerisinde command=self.myListy.yview ifadesinde yview komutunun sarı renkte olduğunu görüyorsunuz; demek ki bu bir fonksiyon. Ancak, dikkat ederseniz yview() yerine sade bir şekilde yview demişiz. Demek ki biz, yview fonksiyonunun döndürdüğü değeri değil, fonksiyonun kendisini alıyoruz. Bu arada, yview ifadesi de "y ekseninin görünümü (view)" anlamına gelir. Dolayısıyla self.scrbar için yview fonksiyonunda ne yazılmışsa onun gibi görünmesini sağlıyoruz.
config: yapılandırmaview: görünümMadem öyle, nasıl göründüğüne bakalım.

Gördüğünüz üzere bir LabelFrame içerisinde bir adet Listbox ve Listbox içerisinde bir adet kaydırma çubuğu oluştu.
Şimdi, iki adet veri tabanı tablosu oluşturmamız lazım. Bunlardan biri mevcut kitapları gösterecek, diğeri ise ödünç alan kişileri gösterecek. Bunları Python ile yapmak yerine direkt olarak SQLite içerisinde yapacağım ama önce neye ihtiyacımız olduğunu bilelim.
Mevcut kitapların yer alacağı tablo için kitap ismi, yazar ismi, sayfa sayısı ve ödünç alınma durumu şeklinde sütunlar olmalı. Buradaki 'ödünç alma durumunu', True-False olarak değerlendireceğim; ödünç alınmışsa True, alınmamışsa False olacak. O yüzden SQLite içerisinde "Execute SQL" bölümüne geliyorum ve komutumu yazıyorum.

Gördüğünüz üzere tablomun ismini books olarak belirledim, komutumu yazdım ve içerisine sırasıyla isim, yazar, sayfa sayısı ve ödünç alınma durumunu verdim. Dilerseniz tablonun nasıl göründüğüne bakalım.

Bir de birkaç tane kitap ekleyelim, hazırda beklesinler. Bunun için INSERT INTO books VALUES() diyeceğim.

Kitapları bu şekilde ekleyebilirsiniz. Birkaç tane daha ekleyeceğim (sayfa sayısı doğru olmayabilir).

Bu şekilde bir sürü kitap ekledim ama temel olarak kırmızı kutu içerisindeki iki kitabı baz alacağım. Bu kadar çok eklememin sebebi, kaydırma çubuğunu test etmek.
Neyse, gayet güzel görünüyor. Şimdi, ödünç alınan kitaplar için de bir tablo oluşturmamız lazım. Bunun için gereken şeyler sırasıyla; ödünç alan kişinin ismi, ödünç alan kişinin telefon numarası, ödünç aldığı kitabın ismi, ödünç aldığı tarih, kitabı iade etmesi gereken tarih.
Bunları yapalım.

Tablomun ismini borrowedBooks olarak belirledim, biraz önce söylediğim şeylerin hepsini tanımladım ve kodu çalıştırdım. Dilerseniz tabloya da bakalım.

Oldukça güzel. Bunları yaptıktan sonra CTRL + S ile bu işlemleri kaydetmeyi unutmayın. İşimiz bittiğine göre kod sayfamıza geri dönebiliriz.
En son bir LabelFrame ve bir Listbox oluşturduk. Şimdi, books tablosu içerisindeki kitapları listelememiz ve Listbox içerisine koymamız lazım. Bu noktada size bir önceki uygulamamızda söylediğim şeyi yapacağım; başka bir Python dosyası açıp SQL sorgularımı oraya yazacağım. Ancak, size örnek olması açısından sadece 1-2 tane yapacağım, geri kalan sorguları direkt olarak library.py içerisine yazacağım.
Bunun için queries.py isimli bir Python dosyası açıyorum ve fonksiyonlarımı yazıyorum.

Veri tabanına bağlandıktan sonra mevcut kitapları göstermesi için showBooks(), ödünç alınan kitaplar için ise showBorrowed() isimli bir fonksiyon açtım ve sonucu return ettim. Birazdan bunları çekeceğiz.
Bunun için library.py dosyasına dönüyorum ve queries.py dosyasını import ediyorum:

Sonrasında, biraz önce tanımladığımız Scrollbar ve Listbox yapılarının üzerine gelip queries.py içerisindeki showBooks() fonksiyonunu çağırıyorum. Hatırlarsanız o fonksiyonda SELECT * FROM books sorgusu mevcuttu. Yani books tablosundaki kitapları çekeceğim.

self.booksList = queries.showBooks() komutu ile queries.py içerisindeki showBooks() fonksiyonunun döndürdüğü değeri self.booksList değişkenine atıyorum. Artık bunu kod sayfasının her köşesinde kullanabilirim.
Şimdi, self.booksList değişkeninin tuttuğu değerlerin, books tablosundaki kitaplar olduğunu biliyoruz. Ancak, daha önceki bloglardan bildiğiniz üzere bunlar bir liste içerisinde ve birer Tuple içerisinde tutuluyor. Dolayısıyla bu verilere tam olarak, güzel ve düzenli bir şekilde erişmem için bir for döngüsü kullanabilirim. Şöyle yapacağım:

Asıl yapımız; for rowBookList in self.booksList: şeklinde. Yalnızca bunu enumerate ile şekillendirdim ve her kayıt için bir sıra numarası verdim. Sonrasında for döngüsünün içine girerek self.myList.insert() dedim. Bu noktada myList isimli Listbox içerisine insert() metodunu kullanarak veri girişi yapacağız. Parantezler içerisinde ise (END, str(rowBooksList)) şeklinde bir yapı görüyorsunuz. Buradaki END ifadesi, 'son' anlamına gelirken str(rowBooksList) ifadesi, for döngüsü ile ayıklanan her ifadeyi ifade eder. Bu ikisinin birleşimi ile aslında şunu demiş oluyoruz: "myList içerisine, en sona ayıklanan verileri ekle".
Dilerseniz kodu çalıştırıp ne olacağını görelim.

Gördüğünüz gibi bütün kitaplar geldi ve kaydırma çubuğu gayet işlevli bir şekilde orada duruyor. Aynısını yazıp deneyebilirsiniz.
İsterseniz aşağıdaki gibi bu kod parçasını etiket ile belli edebilirsiniz; gereklidir.

Bu işi hallettiğimize göre artık ödünç verme sistemine girebiliriz. Hemen bir LabelFrame daha oluşturuyorum ve içerisine gerekli nesneleri ekliyorum.

Gerekli olan tüm nesneleri yerleştirdim. İlk önce nasıl göründüğünü görelim ondan sonra kısa bir açıklama yapayım.

Bu şekilde bir görünüm mevcut; kodlar ile senkron bir şekilde inceleyebilirsiniz. Book's ID: kısmı haricinde aldığımız bilgiler, borrowedBooks isimli tablodaki sütunlara eklenecek olan verilerdir. Book's ID: ise, kitap ile ilgili daha rahat işlem yapabilmemiz için eklediğim bir kısım.
Ayrıca, kitapları listelediğimiz gibi ödünç alınan kitapları da listelememiz lazım. Bunun için az önce yaptığım her şeyi tekrar yapıyorum.

Yalnızca değişken isimlerini değiştirerek aynı yapıyı kurdum ve konumunu ayarladım. Ayrıca Scrollbar ifadesinin üzerinde, self.borrowList = queries.showBorrowed() diyerek queries.py içerisinden aldığım SELECT * FROM borrowedBooks sorgusunu alıyorum. Bazı kodlar uzun olduğu için tam olarak gözükmüyor; blogun sonundan kodları alabilirsiniz.
Yapımızın tam olarak nasıl göründüğünü kontrol edip devam edelim:

Şimdi, Lend isimli Frame içerisine şimdilik işlevi olmayan bir buton koyalım ve biraz mantık konuşalım.

Görünüm:

Olayımız şu; yöneticimiz, Lend isimli Frame içerisinde bulunan Entry'lere gerekli bilgileri girecek ve o an hangi kitap ile işlem yapıyorsa onun sıra numarasını Book's ID: kısmına yazacak. Bu bilgileri girdikten sonra Lend To isimli butona tıklayacak. Bütün bu işlemlerden sonra ödünç alınan kitabın borrowedStatus kısmı yani True-False olan kısmı True olacak. Hatırlayın; True değerini ödünç alınmış kitaplar için verecektik. Sonrasında, mevcut kitaplar içerisinde hangi kitabın borrowedBooks değeri True ise yani hangi kitap ödünç alınmışsa onu Borrowed Books isimli Frame içerisine koyacağız.
Bu kütüphane sisteminde her kitaptan bir tane olduğunu varsayarak işlem yaptığımı unutmayın. Dolayısıyla yöneticimiz, Borrowed Books isimli Frame'de bulunan kitapları bir başkasına ödünç veremeyecek. Ancak, kitabı ödünç alan kişi, kitabı teslim tarihinden önce teslim edebilir. Ekranda sağ alt köşedeki boşluk da bunun için. Sizin göreviniz, teslim tarihinden önce teslim edilen kitapları el ile silen bir mekanizma inşa etmek.
Yapacağımız son işlem ise tarihler ile oynamak olacak. Kitap ödünç alan kişi, örneğin 15 gün sonra o kitabı geri getirmek zorunda olacak. 15 gün dolduktan sonra ödünç alınan kitap Borrowed Books isimli Frame'den silinecek ve var olan kitaplar kısmında ödünç alınan kitabın değeri False olacak. Bu sayede kitap bir başkasına ödünç verilebilir durumda olacak.
O zaman yavaştan başlayalım.
Her şeyden önce, kitapların yalnızca bir defa ödünç alınabilmesi için bir sayaç oluşturmam lazım.

Bunun için __init__ içerisine self.counterOfData = 0 şeklinde bir kod yazıyorum. Birazdan bu değeri değiştireceğiz.
Şimdi, butonumuzun işlevli olması için lendTo() isminde bir metot açıyorum:

lendButton olarak tanımladığımız butonun üzerine, bahsettiğim fonksiyonu açıyorum. Dikkat ederseniz self parametresi falan vermedim ve yine dikkat ederseniz bu fonksiyon, lendButton isimli buton ile aynı hizada. Demek ki bu, __init__ içerisinde tanımlanmış bir fonksiyon. Burada "iç içe fonksiyon" yapısını kullanıyoruz. Zaten __init__ içerisinde self parametresi olduğu için bu fonksiyona self parametresi vermemize gerek yok.
Butonun yapacağı ilk şey, Book's ID: içerisine gireceğimiz kitabın borrowedStatus değerini True olarak değiştirmektir. Hemen yazmaya başlayalım:

self.cursor.execute() içerisine güncelleme işlemini gerçekleştirecek sorguyu yazıyorum ve bunu commit() ediyorum. Sonrasında print("TRUE OLDU") diyerek, terminalde durumu bana göstermesini istiyorum. Sonuç olarak bu iki kod satırı çalışmazsa print() ifadesi de çalışmayacak ve sorunun neren kaynaklandığını hemencecik anlayacağım.
SQL sorgusunda şunu demiş oluyoruz: "books tablosunda ROWID değeri şu olan <yöneticimiz girecek> satırın borrowedStatus değerini True olarak güncelle".
Şimdi, kitap ismini çekmemiz lazım. Çünkü ödünç vereceğimiz zaman borrowedBooks tablosuna bu bilgiyi ekleyeceğiz. O yüzden ROWID değerine ait kitap ismini çeken SQL sorgumu giriyorum:

"books tablosunda, 'Şu ROWID'deki' name değerini seç" dedim ve fetchall() değerini bookName değişkenine eşitledim. Artık çekilen bu kitap ismi verisine bookName değişkeni ile erişeceğiz.
Biraz önce butona tıklandığında ait olan satırdaki borrowedStatus değerini True yapan kodu yazdık. Şimdi, borrowedStatus değeri True olan yani ödünç alınan kitabı Borrowed Books isimli Frame'e ekleyeceğiz. Bu noktada Borrowed Books isimli Frame, veri tabanındaki borrowedBooks isimli tablodaki verileri içereceği için borrowedBooks tablosuyla işlem yapmamız lazım. Şöyle yapıyorum:

Girilen ROWID değerine bağlı olarak books tablosundaki borrowedBook değerini alıyorum ve bunu allBorrowedStatus isimli bir değişkende saklıyorum. Bildiğiniz üzere fetchall() komutu, içerdiği verileri bir liste içerisinde birer Tuple'da saklıyordu. Bu noktada bir adet for döngüsü kullanırsak direkt olarak Tuple değerine erişiriz, evet. Ancak, bizim sade değere erişmemiz lazım. Yani Tuple değerine değil, string değere erişmemiz lazım. Bu yüzden iki tane for döngüsü kullanacağım ve borrowedStatus değerinin 'True' olması koşulunda bazı işlemler gerçekleştireceğim. Bakalım.

Gördüğünüz üzere iç içe for döngüsü yapısını kurdum. İlk for döngüsünde allBorrowedStatus değerindeki her eleman bir Tuple içerisinde gösterilecekken ikinci for döngüsü sayesinde Tuple içerisindeki değeri ayıklayıp string değere ulaşacağız.
Sonrasında if clearData == 'True' ile beraber if self.counterOfData == 0: ifadelerini de iç içe yazdım. Bu noktada veri tabanındaki borrowedStatus değeri gerçekten True ise ve class'ın başına tanımladığımız, bir kitabın kaç defa ödünç verileceğini kontrol etmemize yarayan self.counterOfData değeri 0 ise yani kitap şu an ödünç verilmiş durumda değilse işlemlerimizi yapacağız.
Peki, işlemimiz ne olacak? Elbette, aldığımız bilgileri borrowedBooks tablosuna "sırasıyla" ekleyeceğiz. Görelim.

Eğer borrowedStatus değeri True ise ve kitap şu an ödünç alınmış durumda değilse self.cursor.execute() ile yöneticimizden aldığımız bilgileri sırasıyla borrowedBooks isimli tabloya ekleyeceğiz. Bu noktada dikkat etmeniz gereken bazı noktalar mevcut.
Burada fstring yapısını kullanıyoruz. Gireceğiniz her veri (örneğin self.borrowerEntry.get()) tırnak içerisinde olmalıdır. Kod oldukça uzun olduğu ve görmenizi istediğim için ters slash ile bunları böldüm; hata alırsanız o slash işaretini silip o satırı tek bir satıra dönüştürün.
Dikkat etmeniz gereken bir diğer nokta ise '{bookName[0][0]}' ifadesi. fetchall() fonksiyonunun nasıl bir sonuç döndüreceğini artık biliyorsunuz; liste içerisinde tuple. Bizim derdimiz, kitabın ismini string bir şekilde alıp veri tabanına eklemek ve bu çıktı, tek bir elemandan oluşacağı için index kullandık.
Sade bookName ifadesi size şöyle bir çıktı verecektir: [('Suç ve Ceza', 'Dostoyevski')]
bookName[0] ifadesi size şöyle bir çıktı verecektir: ('Suç ve Ceza', 'Dostoyevski')
bookName[0][0] ifadesi ise şöyle bir çıktı verecektir: Suç ve Ceza
Yani kısaca; önce listenin sıfırıncı index'inde bulunan tuple yapısını aldık sonrasında tuple yapısının sıfırıncı index'ini aldık. Bu değer de name değerine denk geliyor.
Dikkat etmeniz gereken bir diğer nokta ise self.counterOfData = 1 kodudur. Ödünç alınan kitap burada True değerini aldığı ve sayacımız 0 olduğu için sayacın değerini 1 yapıyoruz. Böylelikle butona ikinci kez tıklandığında if self.counterOfData == 0: koşulu sağlanmadığı için ekleme işlemi gerçekleşmeyecek ve Python direkt olarak else: kısmına geçip kullanıcıyı uyaracaktır. Girinti sistemine dikkat edelim.
Şimdi, ödünç alma tarihi ile sürenin bittiği tarihleri ayarlamamız gerekiyor. Çünkü süre bittiğinde silme işlemleri gerçekleşecek. Bu yüzden datetime ve timedelta modüllerimi import ediyorum.

Şimdi, aşağıdaki kodlarla senkron bir şekilde yazdıklarımı okuyunuz.

İlk önce yeşil kutunun dışındaki kodlara bakalım. 27. satırda self.todayInfo değişkeni ile şu anki tarihi, saat bilgisi dahil olacak şekilde alıyorum. Ancak datetime modülünü anlatırken söylediğim gibi çıktı kısmında milisaniyeler bile görünüyor. Bu yüzden self.todayInfo = datetime.now() dedikten sonra karakter değiştiren fonksiyonumuz olan replace() fonksiyonunu kullanıp içerisine microsecond=0 diyorum. Bu sayede bahsettiğim milisaniyeler artık gözükmeyecektir.
28. satırda self.fullDate değişkeni ve strftime() fonksiyonu ile, self.todayInfo sayesinde aldığımız tarih ve saat bilgisini formatlıyorum. Diyorum ki: "Çıktı şöyle olsun 2023-02-03 04:35:00".
29. satırda bahsettiğim süreyi belirliyoruz. Kodu hızlıca denemek için self.future = timedelta(minutes=0.6) dedim. Ancak, sizler sürenin gerçekten 15 gün olmasını istiyorsanız bu kısma days=15 ifadesini verebilirsiniz.
Son olarak 30. satırda şu anki tarih ile gelecek tarihi (15 gün veya 0.6 dakika sonrasını) birbirine ekliyoruz. Bu sayede gelecek tarihi elde etmiş oluyoruz ve buna self.deadlineDate ismini veriyoruz.
Şimdi, yeşil kutucuğa bakabiliriz. Burada tam olarak "varsayılan değer atama işlemi" yapıyoruz. Hatırlarsanız Entry'ler içerisine varsayılan değer atayabiliyor ve bunu StringVar() ifadesiyle beraber set() fonksiyonu ile yapıyorduk. Buna self.defaultBorrowedDate yani 'varsayılan ödünç alma tarihi' ismini veriyoruz ve set() fonksiyonu içerisine biraz önce şu anın tarihini aldığımız self.fullDate değişkenini koyuyoruz. Hemen altındaki de tamamen aynı mantıkta işleyecektir; bu sefer sürenin bitiş tarihini koyuyoruz.
Bu noktada bir eksiğimiz var; bu Entry yapılarını bulup bu değerleri textvariable= değeri olarak atamamız lazım. Hemen yapalım.

Hemen bunun çalışıp çalışmadığını kontrol edelim.

Gördüğünüz gibi tam olarak şu anın zamanını ve 0.6 dakika sonrasını varsayılan olarak atadı.
Şimdi sıra, bu zaman dilimlerini kontrol etmeye geldi. Söylemeden geçmek istemiyorum. İsmini vermekten pek hoşlanmayan, benim için çok değerli olan bir arkadaşım bu kısımda bana oldukça yardımcı oldu. Burada onunla ilgili bir anı bırakmak istedim.
Her şeyden önce daha önce size anlatmadığım bir modülü import etmemiz gerekiyor; threading. Bu modül -kaba tabirle- bazı işlemleri eş zamanlı olarak yapmamıza olanak tanıyan bir modüldür. Eğer GoLang serisine baktıysanız bunu, oradaki Concurrency - go routine yapılarına benzetebilirsiniz. Belki ilerleyen zamanlarda bu modülü de anlatırım. Hemen import edelim.

lendTo() fonksiyonunun olduğu yere gidiyorum ve lendTo() fonksiyonunun üzerine getTime() diye bir fonksiyon açıyorum.

Bu fonksiyonun tek amacı şu anki zamanı döndürmektir. Başka bir amacı yoktur. Şimdi, timeControl() isminde bir fonksiyon daha açıyorum.

rightNow diyerek getTime() fonksiyonunun döndürdüğü değeri alıyorum ve threading vesilesiyle bir sayaç koyuyorum. Bu sayaca argüman olarak timeControl fonksiyonunu koyuyorum. Ancak, dikkat edelim; timeControl() değil, timeControl. Bu sayede timer değişkeni, içinde bulunduğu fonksiyonu çalıştıracak. Sonrasında timer.start() diyorum ve bu süreci başlatıyorum.
Burada, "eğer şu anki zaman (rightNow), sürenin bitişiyle (self.deadlineDate) eşitse..." şeklinde bir yapı kurmamız lazım ve eğer bu koşul sağlanıyorsa tekrardan bookName değişkeni ile kitabın ismini almamız lazım. Hem tekrardan kullanmak zorunda olduğum hem de daha önceki bookName değişkeni lendTo() fonksiyonu içerisinde olduğu için bunu tekrar yapıyorum; Burada scope kavramı söz konusu. O zaman yapalım.

Kitap ismini çektik. rightNow ile self.deadlineDate eşitse ödünç alma süresi dolmuştur. Dolayısıyla bizim, ödünç alınan kitabın borrowedStatus değerini önce False yapmamız, sonra da borrowedStatus değerini tekrardan almamız lazım. Dediğim gibi; bu iki kod tekrarı, scope kavramından kaynaklıdır. Hadi yapalım.

Önce name değerini çektik. Sonra bu değere ait olan borrowedStatus değerini False yaptık çünkü ödünç alma süresi bitti. Bunu anlamak için de terminal ekranına "FALSE OLDU" yazdırdık. Son olarak borrowedStatus değerini tekrardan aldık.
Madem süre doldu ve biz bu değeri False yaptık o zaman bunu, ödünç alınan kitaplar listesinden silmemiz lazım. Hemen yapalım.

Yine iç içe for döngüsü yapısı ile borrowedStatus değerini kontrol ettim ve False olması durumunda o kaydı sildim. bookName değeri (yöneticinin gireceği ROWID değerindeki name değerine bağlı olarak) şu olan kaydı sildik.
Son olarak timer.cancel() diyerek bu döngüyü sonlandırdık.
Bu noktada allBorrowedStatus değerini alıp for döngülerini yazmayabilir, sadece silme işlemini ve timer.cancel() kodunu yazabilirsiniz. Çünkü zaten değeri False yaptığımız için aşağıdaki silme işlemi de gerçekleşecektir. Ben, daha kontrollü ve adım adım bir kod istediğim için bu şekilde yaptım.
Şimdi, timeControl() isimli bu fonksiyonu bir yere yerleştirmemiz lazım:

lendTo() fonksiyonu içerisine, self.counterOfData = 1 satırının altına timeControl() fonksiyonunu çağırıyoruz. Peki, burada ne olduğunu genel olarak özetleyelim.
lendTo() içerisinde ROWID'si alınan kitabın borrowedStatus değerini True yaptık.ROWID'si alınan kitabın name değerini çektik.borrowedStatus değerini çektik.True ise girilen bilgileri borrowedBooks tablosuna sırasıyla ekledik.timeControl() fonksiyonunu çağırdık.timeControl() fonksiyonu, timer değişkeni sayesinde kendi kendini çalıştırdı.borrowedStatus değerini False olarak değiştirdik.name ve borrowedStatus değerlerini tekrar aldık çünkü bookName değeri tekrar lazım oldu ve artık borrowedStatus değeri True değil, False oldu.borrowedStatus değeri False ise silme işlemini gerçekleştirdik.timer değişkeninin işini cancel() ile sonlandırdık.Artık rötuşlarımızı yapalım.
SQLite veri tabanı, genelde birden fazla thread (iş parçacığı) işleyemez; bunu kontrol eden bir mekanizması vardır. İşte, bu mekanizmayı kapatmamız lazım. Bunun için veri tabanına bağlandığımız yere gidip şunu yapıyorum:

Bir virgül koyup check_same_thread=False diyorum ve bu sayede bahsettiğim kontrol mekanizmasını kapatıyorum.
Şimdi, lendTo() fonksiyonunu bir try-except bloğu içerisine alalım ve spesifik mesajlar verelim.

sqlite3.OperationalError hatası, kodlar işlenirken oluşabilecek bir sıkıntıda ortaya çıkar. Bizdeki kod yapısında sorun olabilecek tek yer Book's ID: kısmıdır. Çünkü diğer alanları boş geçsek bile veri tabanına boş değer eklenecektir. Ancak, ROWID boş girilemez. Dolayısıyla OperationalError alma olasılığı vardır. Böyle bir durumda kullanıcıya "Book's ID değeri girmemiş olabilirsin" diyoruz.
Kullanıcının girdiği ROWID değeri veri tabanında yoksa da "Girdiğin Book's ID mevcut olmayabilir" diyoruz.
Çok ufak bir sıkıntımız daha var; o da tkinter güncellemesi. Pencere açıkken listeye ekleme veya çıkarma işlemleri dinamik olarak gösterilmeyecektir. Bu yüzden bir tane "Yenile/Refresh" butonu yapmamız lazım. Hemen yapalım, siz de yeni bir şey daha öğrenin.

Yapmamız gereken tek şey self.__init__(root) demek. Bu sayede __init__ fonksiyonu tekrar tetiklenecek ve işlemler güncellenecek. Dilerseniz bunu lambda ile de yazabilirsiniz.
Bir de Lend To butonuna işlevini verelim:

Şimdi, eklediğimiz diğer kitapları silelim ve iki tane bırakalım.

Üçüncü satıra bir defa tıklayın, CTRL tuşuna basılı kalın ve son satıra bir defa tıklayın; o aralıktaki her satır seçilecektir. Sonra parmağınızı CTRL tuşundan çekip sağ tık yaparsanız ufak bir menü açılacaktır. Orada "Delete Records" yazan seçeneği seçerseniz hepsi silinecektir. Bu işlemden sonra CTRL + S yapmayı unutmayın.
Artık şu kodu çalıştıralım.

Değerleri girdim ve Lend to butonuna bastım. Ancak gördüğünüz gibi herhangi bir gelişme olmadı. Daha doğrusu oldu ama buraya yansımadı. O yüzden Refresh butonuna tıklıyorum:

Refresh butonuna bastım ve tkinter kendini güncelledi.
Şimdi, veri tabanına bakalım.

ROWID alanına 1 değerini verdik ve birinci satırdaki borrowedStatus True oldu. Bir de bilgiler eklenmiş mi diye bakalım.

Gayet başarılı bir şekilde eklendiğini görüyoruz. Peki, o kadar print() işlemi yaptık. Sürenin dolmasını bekliyorum ve terminaldeki duruma bakıyorum.

Süre doldu ve TIMER IS CANCELLED ifadesi yazdırıldı. Terminalin dediğine göre işlemler tamamlandı. Peki, veri tabanını kontrol edelim.

Değerimiz False olmuş. Peki, silinmiş mi?

İşlemlerimiz gerçekten de gayet başarılı bir şekilde gerçekleşti. Eğer tkinter üzerinden Refresh butonuna tekrar tıklarsanız verilerin aynı burada olduğu gibi değiştiğini göreceksiniz.
Uygulamamız bu kadardı.
queries.py içerisine toplanabilir. Bazı sorgular için fonksiyona parametre vererek çalışmak zorundasınız.borrowedBooks tablosu üzerinden ayrı bir ROWID alın.logs.txt dosyasına kaydedebilir, yönetici için kolaylık sağlayabilirsiniz.queries.pyimport sqlite3connection = sqlite3.connect("library.db")cursor = connection.cursor()def showBooks(): cursor.execute("SELECT * FROM books") return cursor.fetchall()def showBorrowed(): cursor.execute("SELECT * FROM borrowedBooks") return cursor.fetchall()
library.pyimport sqlite3from tkinter import *from tkinter import messageboxfrom datetime import datetimefrom datetime import timedeltaimport queriesimport threadingclass Library: connection = sqlite3.connect("library.db", check_same_thread=False) cursor = connection.cursor() @classmethod def connectionClose(cls): return cls.connection.close() def __init__(self,root): self.root = root # self.root.geometry("1920x1080") self.root.attributes('-fullscreen', True) self.root.title("Cyber Worm") self.root.iconbitmap("C:/Python/EMS/logo.ico") self.root.configure(bg="#005b96") self.counterOfData = 0 ### Get the date and time ### self.todayInfo = datetime.now().replace(microsecond=0) self.fullDate = self.todayInfo.strftime("%Y-%m-%d %H:%M:%S") self.future = timedelta(minutes=0.6) self.deadlineDate = self.todayInfo + self.future ### Set Entries' Default Value ### self.defaultBorrowedDate = StringVar() self.defaultBorrowedDate.set(f"{self.fullDate}") self.defaultDeadline = StringVar() self.defaultDeadline.set(f"{self.deadlineDate}") ### Set Entries' Default Value ### # Title titleLabel = Label(self.root, text="LIBRARY MANAGEMENT SYSTEM (LMS)", font=('Arial', 30, 'bold'), bg='#005b96', fg='red') titleLabel.place(relx=0.2, rely=0.02) ################################################################## Exist Books Frame ################################################################## self.showBooksFrame = LabelFrame(self.root, text="Exist Books", bg='#005b96', fg='red', font=('Arial', 14, 'bold'))#, width=1200, height=350) self.showBooksFrame.place(relx=0.01, rely=0.1) self.booksList = queries.showBooks() self.scrbar = Scrollbar(self.showBooksFrame) self.scrbar.pack(side=RIGHT, fill=Y) self.myList = Listbox(self.showBooksFrame, yscrollcommand=self.scrbar.set, width=110, height=15, bg='#005b96', fg='white', font=('Arial', 12, 'bold')) for rowBooksList in enumerate(self.booksList, 1): self.myList.insert(END, str(rowBooksList)) self.myList.pack(side=LEFT) self.scrbar.config(command=self.myList.yview) ################################################################## Exist Books Frame ################################################################## ######################################################## Lend Book and Show Borrowed List Frame ######################################################## self.lendBookFrame = LabelFrame(self.root, text="Lend", bg='#005b96', fg='red', font=('Arial', 12, 'bold'), width=450, height=720) self.lendBookFrame.place(relx=0.68, rely=0.1) self.borrowerLabel = Label(self.lendBookFrame, text="Borrower Name:", bg='#005b96', fg='white', font=('Arial', 12, 'bold')) self.borrowerLabel.place(relx=0.05, rely=0.03) self.borrowerEntry = Entry(self.lendBookFrame, border=5, bg='white', width=40) self.borrowerEntry.place(relx=0.4, rely=0.03) self.borrowerPhoneLabel = Label(self.lendBookFrame, text="Borrower Phone:", bg='#005b96', fg='white', font=('Arial', 12, 'bold')) self.borrowerPhoneLabel.place(relx=0.05, rely=0.1) self.borrowerPhoneEntry = Entry(self.lendBookFrame, border=5, bg='white', width=40) self.borrowerPhoneEntry.place(relx=0.4, rely=0.1) self.borrowedDateLabel = Label(self.lendBookFrame, text="Borrowed Date:", bg='#005b96', fg='white', font=('Arial', 12, 'bold')) self.borrowedDateLabel.place(relx=0.05, rely=0.17) self.borrowedDateEntry = Entry(self.lendBookFrame, textvariable=self.defaultBorrowedDate, border=5, bg='white', width=40) self.borrowedDateEntry.place(relx=0.4, rely=0.17) self.deadlineLabel = Label(self.lendBookFrame, text="Deadline:", bg='#005b96', fg='white', font=('Arial', 12, 'bold')) self.deadlineLabel.place(relx=0.05, rely=0.24) self.deadlineEntry = Entry(self.lendBookFrame, textvariable=self.defaultDeadline, border=5, bg='white', width=40) self.deadlineEntry.place(relx=0.4, rely=0.24) self.rowIDLabel = Label(self.lendBookFrame, text="Book's ID:", bg='#005b96', fg='white', font=('Arial', 12, 'bold')) self.rowIDLabel.place(relx=0.05, rely=0.31) self.rowIDEntry = Entry(self.lendBookFrame, border=5, bg='white', width=40) self.rowIDEntry.place(relx=0.4, rely=0.31) self.borrowedBooksFrame = LabelFrame(self.root, text="Borrowed Books", bg='#005b96', fg='red', font=('Arial', 12, 'bold'), width=1000, height=350) self.borrowedBooksFrame.place(relx=0.01, rely=0.55) self.borrowList = queries.showBorrowed() self.scrbarLend = Scrollbar(self.borrowedBooksFrame) self.scrbarLend.pack(side=RIGHT, fill=Y) self.myLendList = Listbox(self.borrowedBooksFrame, yscrollcommand=self.scrbarLend.set, width=110, height=15, bg='#005b96', fg='white', font=('Arial', 12, 'bold')) for rowBorrowList in enumerate(self.borrowList, 1): self.myLendList.insert(END, str(rowBorrowList)) self.myLendList.pack(side=LEFT) self.scrbarLend.config(command=self.myLendList.yview) def getTime(): presentTime = datetime.now().replace(microsecond=0) return presentTime def timeControl(): rightNow = getTime() timer = threading.Timer(1.0, timeControl) timer.start() if rightNow == self.deadlineDate: self.cursor.execute(f"SELECT name FROM books WHERE ROWID='{self.rowIDEntry.get()}'") bookName = self.cursor.fetchall() print("*"*50) self.cursor.execute(f"UPDATE books SET borrowedStatus='False' WHERE ROWID='{self.rowIDEntry.get()}'") self.connection.commit() print("FALSE OLDU") print("*"*50) self.cursor.execute(f"""SELECT borrowedStatus FROM books WHERE ROWID="{self.rowIDEntry.get()}" """) allBorrowedStatus = self.cursor.fetchall() for i in allBorrowedStatus: for j in i: if j == 'False': print("*"*50) self.cursor.execute(f"DELETE FROM borrowedBooks WHERE borrowedBookName='{bookName[0][0]}'") self.connection.commit() print("SİLİNDİ") print("*"*50) timer.cancel() print("TIMER IS CANCELLED") def lendTo(): try: self.cursor.execute(f"UPDATE books SET borrowedStatus='True' WHERE ROWID='{self.rowIDEntry.get()}'") self.connection.commit() print("TRUE OLDU") self.cursor.execute(f"SELECT name FROM books WHERE ROWID='{self.rowIDEntry.get()}'") bookName = self.cursor.fetchall() self.cursor.execute(f"""SELECT borrowedStatus FROM books WHERE ROWID="{self.rowIDEntry.get()}" """) allBorrowedStatus = self.cursor.fetchall() for check in allBorrowedStatus: for clearData in check: if clearData == 'True': if self.counterOfData == 0: self.cursor.execute(f"INSERT INTO borrowedBooks VALUES('{self.borrowerEntry.get()}', '{self.borrowerPhoneEntry.get()}', '{bookName[0][0]}', '{self.borrowedDateEntry.get()}', '{self.deadlineEntry.get()}')") self.connection.commit() print("EKLENDİ") self.counterOfData = 1 timeControl() else: return messagebox.showerror("System Message", "A book can only be loaned once. You can request the book from the borrower.") except sqlite3.OperationalError: return messagebox.showerror("System Message", """You may not have entered the "Book's ID".""") except IndexError: return messagebox.showerror("System Message", """The "Book's ID" you entered may not be valid.""") lendToButton = Button(self.lendBookFrame, text="Lend to", fg='red', font=('Arial', 10, 'bold') ,width=50, height=4, command=lendTo) lendToButton.place(relx=0.05, rely=0.37) ######################################################## Lend Book and Show Borrowed List Frame ######################################################## ############################################################# Refresh Button ############################################################# def refresh(): self.__init__(root)
refreshButton = Button(self.lendBookFrame, text="Refresh", fg='red', font=('Arial', 10, 'bold') ,width=50, height=4, command=refresh) refreshButton.place(relx=0.05, rely=0.50) ############################################################# Refresh Button ############################################################# self.borrowerLabel = Label(self.lendBookFrame, text="Buraya el ile kayıt silme işlemini yapabilirsiniz.", bg='#005b96', fg='red', font=('Arial', 14, 'bold')) self.borrowerLabel.place(relx=0.02, rely=0.8)if __name__ == "__main__": root = Tk() rootObject = Library(root) root.mainloop() Library.connectionClose()
Yayınlanma Tarihi: 2023-02-02 23:37:41
Son Düzenleme Tarihi: 2023-02-28 10:36:23