PyGTK + SQLAlchemy: пишем каталогизатор фильмов

pygtk Давно хотел попробовать Python в качестве инструмента разработки приложений под декстоп. Выбор GUI-библиотеки из доступных GTK, QT, wxPython и Tkinter был не сложен, т.к. рабочим столом у меня является GNOME и как известно он построен на GTK+, то я решил остановится на PyGTK - прослойки между GTK+ и Python. Приложения написанные на GTK+ могут жить не только под одними Linux, если поставить необходимые библиотеки то можно будет запускать приложения под MacOS и Windows, один плюсов библиотеки - кроссплатформенной.

В качестве примера напишем приложение-каталогизатор фильмов для киномана. Все элементы интерфейса будут на GTK+, фильмы будут храниться в SQLite, для общения с базой данных будет использоваться ORM - SQLAlchemy.

Коротко про GTK+

GTK+ (GIMP Toolkit) представляет собой инструментарий, набор библиотек, которые используются для разработки GUI-приложений под Linux, OSX, Windows и другие платформы на которых доступна библиотека GTK+. Думаю не открою Америку, если скажу, что на GTK+ написаны такие известные приложения, как GIMP (растровый графический редактор) и GNOME (среда рабочего стола для Unix-подобных операционных систем). Сама библиотека написана на C, используемая лицензия - LGPL.

PyGTK является оберткой над GUI-библиотекой GTK+ и предоставляет возможности творить GUI-приложения на Python. PyGTK состоит из нескольких компонентов:

  • GObject - базовый класс, предоставляющий общие свойства и методы для PyGTK классов;
  • Glib предоставляет основную объектную систему, используемую в GNOME, реализацию основного цикла, а также обширный набор вспомогательных функций для строк и типовых структур данных. источник
  • GDK (GIMP Drawing Kit) - обертка над низкоуровневыми библиотеками для рисования и управления окнами. Находится между GTK+ и X-сервером.
  • Pango - отвечает за локализацию и работу с шрифтами;
  • ATK - набор инструментов для реализации интерфейса для людей с ограниченным физическими возможностями;
  • Cairo - библиотека для создание 2D графики;

Если у вас рабочий стол GNOME, то в системе уже установлен PyGTK, иначе идем на страницу загрузки и качаем версию для вашей платформы.

Проверим установленные версии GTK+ и PyGTK

>>> import gtk
>>> gtk.pygtk_version
>>> (2, 17, 0)
>>> gtk.gtk_version
>>> (2, 20, 1)

Все приложения на PyGTK состоят из визуальных компонентов - виджетов. Которые могут быть либо видимыми (кнопки, комбобоксы и т.д.) и не видимыми (например, лейауты). Большинство виджетов могут реагировать на определенные события, генерируемые X-сервером. Когда пользователь кликает по кнопке - генерируется сигнал, который порождает событие - clicked, программисту остается только написать обработчик на событие. Всеми событиями в приложение управляет главный цикл обработки событий, запускаемый во время инициализация приложения.

Для построения интерфейса приложения можно использовать два подхода: вручную описывать все виджеты и порядок их размещение на форме, либо воспользоваться Glade - так называемый RAD (rapid application development), GUI-инструмент для визуального проектирования интерфейса. Процесс сводится к набрасыванию виджетов на форму (как в Delphi или QT Designer) и назначению имен для обработчиков событий. После этого весь результат сохраняется в XML-файл который парсится с помощью libglade в конструкторе главного окна приложения. В результате мы получаем готовый интерфейс. Хорошее описание работы с Glade есть тут.

Я использовал первый подход - вручную описывал и размещал виджеты на форме, так лучше формируется понимание внутренней кухни, а уже потом можно переходить к Glade для минимизирования времени на проектирование интерфейса.

Если с виджетами все понятно, есть кнопка - нажимай, есть комбобокс - выбирай что-то из списка, то остается вопрос как их размещать на форме, для этого существуют разные лейауты (layout), или еще PyGTK документация называет этот процесс - упаковка виджетов. Каждый из этих лейаутов умеет размещать на себе виджеты в определенной последовательности. Список возможных лейаутов в PyGTK:

  • HBox - виджеты размещаются горизонтально, один за другим;
  • VBox - виджеты размещаются вертикально, один за другим;
  • Fixed - виджеты размещаются на заданных x и y координатах
  • Table - виджеты размещаются внутри не видимой таблицы
  • Alignment - позволяет регулировать позицию и размеры вложенных виджетов относительно родительского виджета

Рассмотрим пример простого приложения на PyGTK, каждое действие прокомментировано:

# coding=utf-8
import gtk

# создадим класс основного окна, которое будет отображаться при запуске приложения
class App(gtk.Window):
  def __init__(self):
        super(App, self).__init__()

        # параметры основного окна

        # установим размер окна
        self.set_size_request(200, 150)

        # установим title
        self.set_title('Hello world')

        # установим внутренние отступы
        self.set_border_width(20)

        # установим расположение окна по центру экрана
        self.set_position(gtk.WIN_POS_CENTER)

        # назначим обработчик закрытия окна, без него не будет реакции на попытки закрыть окно
        self.connect('destroy', gtk.main_quit)

        # создадим виджет Button
        self.button = gtk.Button("Click me")

        # назначим обработчик на событие 'clicked'
        self.button.connect('clicked', self.helloworld, None)

        # добавим кнопку на форму основного окна
        self.add(self.button)

        # дадим команду отобразить все виджеты
        self.show_all()

  def helloworld(self, widget, data=None):
        """
        Обработчик события clicked для кнопки self.button
        """

        # изменим текст на кнопке 
        widget.set_label('Hello world')

# создадим экземпляр класса 
App()

# запустим главный цикл обработки событий
gtk.main()

Коротко про SQLAlchemy

SQLAlchemy — это программное обеспечение с открытым исходным кодом для работы с базами данных при помощи языка SQL. Оно реализует технологию программирования ORM (Object-Relational Mapping), которая связывает базы данных с концепциями объектно-ориентированных языков программирования. SQLAlchemy позволяет описывать структуры баз данных и способы взаимодействия с ними прямо на языке Python. источник

Установим SQLAlchemy

pip install SQLAlchemy

Проверим результат установки

>>> import sqlalchemy
>>> sqlalchemy.__version__
>>> '0.6.3'

Для создания ORM с существующей БД будем использовать декларативный подход, который сводит к минимуму процесс установки связи. Рассмотрим пример работы SQLAlchecmy и SQLite на примере модели будущего приложения.

Импортируем используемые методы и типы полей:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import Column, Integer, Unicode, Date, MetaData, Boolean

Создаем объект отвечающие за работу с БД и указываем выводить в консоль все SQL-запросы - удобно для отладки.

engine = create_engine('sqlite:///films.db', echo=True)

Создаем объект отвечающий за выполнение транзакциq и выполнение запросов

Session = scoped_session(sessionmaker(bind=engine))

Создадим базовый класс, от которого потом будем наследоваться модель 'Фильмы'

Base = declarative_base()

Собственно сама модель, в атрибуте tablename указывается имя таблицы, в которой будет хранится список фильмов. Остальные атрибуты представляют собой характеристики модели и по совместительству будут колонками таблицы:

class Film(Base):
    """
    Модель 'Фильм'
    """

    __tablename__ = 'films'

    fid = Column(Integer, primary_key=True)
    title = Column(Unicode)
    genre = Column(Unicode)
    release = Column(Date)
    is_viewed = Column(Boolean)

    def __init__(self, title, genre, release, is_viewed):
        self.title = title
        self.genre = genre
        self.release = release
        self.is_viewed = is_viewed

    def __repr__(self):
        return "" % (self.title)

Создадим файл с базой данных и необходимыми таблицами

Base.metadata.create_all(engine)

Попробуем создать запись о новом фильме

>>> from models import *  
>>> import datetime
>>> session = Session()
>>> film = Film('Iron Man', 'action', datetime.date.today(), True)
>>> session.add(film)
>>> session.commit()

А теперь выберем все фильмы из БД:

>>> from models import *  
>>> session = Session()
>>> films = session.query(Film).all()
>>> for film in films:
>>> ... print film.title

Приложение MyFilms

Полный исходник приложения можно скачать в самом низу поста, внутри исходников есть комментарии почти ко всем действия, думаю разобраться в самом процессе не составит большого труда. Ниже я опишу только пару интересных, на мой взгляд, моментов, но сначала внешний вид приложения:

myfilms

В начале я вскользь коснулся расположения виджетов на форме с помощью лейаутов, сейчас опишу чуть подробнее один из них - VBox, на нем все виджеты располагаются вертикально, один за другим.

Создается этот лейаут так:

vbox = gtk.VBox(False, 8)

первый параметр конструктора, homogeneous, значение которого в данном случае равно False, означает что каждый виджет, расположенный на этом лейауте, может занимать разную высоту по вертикале, если установить True то для всех виджетов будет установлена одинаковая высота (общая высота зависит от родителя). Второй параметр выставляет отступы, в пикселях, между вложенными блоками.

Виджеты могут добавляться на лейаут с двух сторон, в случаи VBox сверху (метод pack_start) и снизу (метод pack_end).

vbox.pack_start(toolbar, False, False, 0)

сигнатура метода pack_start:

box.pack_start(child, expand, fill, padding)
  • первый параметр (child) - добавляемый виджет;
  • второй (expand) - булевский параметр, отвечает за размеры размещаемого виджета и указывает будет ли (если True) виджет занимать все доступное место или выделенный блок ужмется до размеров виджета (False);
  • третий параметр (fill) - действителен если expand=True, если fill=True то виджет растянется до размеров выделенного блока, иначе - вокруг виджета будет пустое место;
  • последний параметр (padding) - внутренние отступы, в пикселях;

Для хранения табличных данных используется виджет gtk.TreeView с хранилищем данных (gtk.ListStore). Ниже мы создадим экземпляр виджета и передадим ему уже наполненное хранилище данных. Наполнение хранилища происходит банальной итерацией по всем записям в БД и добавлением в хранилище с помощью метода append, которому передается список значений (см. исходники в прикрепленном ниже файле).

self.treeView = gtk.TreeView(self.store)

Следующим этапом мы создадим колонки для виджета TreeView, за это отвечает метод create_columns

...
def create_columns(self, treeView):
    """
    Метод отвечает за создание колонок для грида.
    """

    rendererText = gtk.CellRendererText()
    column = gtk.TreeViewColumn("ID", rendererText, text=0)
    column.set_sort_column_id(0)
    treeView.append_column(column)
...

Для виджета TreeView назначены два обработчика события:

  • событие row-activated - двойной клик по строчке с фильмов, вызывается диалоговое окно для редактирования выбранного фильма;
  • событие cursor-changed - одинарный клик по строчке с фильмов, либо перемещение по списку с помощью курсоров - обновляется статус в строке состояния;

После выбора строки в гриде, мы можем получить выбранные значения, которые хранятся в хранилище данных (gtk.ListStore)

tree_model, tree_iter = self.treeView.get_selection().get_selected()

возвращается кортеж из двух объектов

  • tree_model, объект класса gtk.TreeModel - предоставляет интерфейс для работы с данными хранящимися в виджете gtk.TreeView;
  • tree_iter, объект класса gtk.TreeIter - указывает на выбранные данные в gtk.TreeModel;

Сорцы приложения MyFilms - myfilms_pygtk.tar.gz

Дополнительное чтиво

blog comments powered by Disqus