Статьи

Приложения wxPython, которые могут представлять базу данных SQLite.


недавно связались с тем, есть ли здесь какие-либо приложения wxPython, которые могут представлять базу данных SQLite.
Насколько я понял, они хотели иметь возможность анализировать базу данных и просматривать таблицы, возможно, используя виджет wx.grid.Grid. Я нахожу Grid-виджет очень мощным и довольно сложным в использовании. Поэтому я потратил некоторое время на написание очень простого приложения,
которое вместо этого использует
виджет ObjectListView .

Начиная

 

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

from sqlalchemy import create_engine
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String
 
engine = create_engine('sqlite:///example.db', echo=True)
Base = declarative_base()
 
########################################################################
class Book(Base):
    """"""
    __tablename__ = "books"
 
    id = Column(Integer, primary_key = True)
    title = Column(String)
    author = Column(String)
 
    #----------------------------------------------------------------------
    def __init__(self, title, author):
        """Constructor"""
        self.title = title
        self.author = author
 
 
########################################################################
class Character(Base):
    """"""
    __tablename__ = "characters"
 
    id = Column(Integer, primary_key = True)
    first_name = Column(String)
    last_name = Column(String)
    book_id = Column(ForeignKey("books.id"))
    book = relationship("Book", backref=backref("characters", order_by=id))
 
    #----------------------------------------------------------------------
    def __init__(self, first_name, last_name):
        """Constructor"""
        self.first_name = first_name
        self.last_name = last_name
 
 
    #----------------------------------------------------------------------
    @property
    def fullname(self):
        """"""
        return "%s %s" % (self.first_name, self.last_name)
 
    #----------------------------------------------------------------------
    def __repr__(self):
        """"""
        return "<Character('%s')>" % self.fullname
 
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
print
new_char = Character("Hermione", "Granger")
new_char.book = Book("Harry Potter", "JK Rowling")
session.add(new_char)
new_char = Character("Sherlock", "Holmes")
new_char.book = Book("The Adventure of the Creeping Man", "Arthur Conan Doyle")
session.add(new_char)
session.commit()

Я предполагаю, что вы понимаете SQLAlchemy достаточно хорошо, чтобы следовать этому. Если нет, у них есть лучшая документация из всех проектов Python, которые я когда-либо использовал.

Создание зрителя

 

Теперь нам просто нужно создать просмотрщик таблицы базы данных. Это заняло у меня немного переделок, но я в конце концов понял это. Обратите внимание, что это в основном альфа-качество и в нем нет ошибок.

import os
import wx
from ObjectListView import ObjectListView, ColumnDefn
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import mapper, sessionmaker, clear_mappers
 
########################################################################
class GenericDBClass(object):
    """"""
    pass
 
########################################################################
class MainPanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.db_data = []
        self.current_directory = os.getcwd()
 
        self.dataOlv = ObjectListView(self, wx.ID_ANY, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.dataOlv.Hide()
 
        # Allow the cell values to be edited when double-clicked
        self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
 
        # load DB
        loadDBBtn = wx.Button(self, label="Load DB")
        loadDBBtn.Bind(wx.EVT_BUTTON, self.loadDatabase)
        self.table_names = []
        self.tableCbo = wx.ComboBox(self, value="", choices=self.table_names)
        self.tableCbo.Bind(wx.EVT_COMBOBOX, self.loadTable)
 
        # Create some sizers
        mainSizer = wx.BoxSizer(wx.VERTICAL)
 
        mainSizer.Add(loadDBBtn, 0, wx.ALL|wx.CENTER, 5)
        mainSizer.Add(self.tableCbo, 0, wx.ALL|wx.CENTER, 5)
        mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
 
        self.SetSizer(mainSizer)
 
    #----------------------------------------------------------------------
    def loadTable(self, event):
        """"""
        print
        current_table = self.tableCbo.GetValue()
        metadata = MetaData(self.engine)
        table = Table(current_table, metadata, autoload=True, autoload_with=self.engine)
        self.columns = table.columns.keys()
 
        clear_mappers() #http://docs.sqlalchemy.org/en/rel_0_6/orm/mapper_config.html#sqlalchemy.orm.clear_mappers
        mapper(GenericDBClass, table)
 
        Session = sessionmaker(bind=self.engine)
        session = Session()
        self.db_data = session.query(GenericDBClass).all()
 
        self.setData()
        self.dataOlv.Show()
        self.Layout()
 
    #----------------------------------------------------------------------
    def loadDatabase(self, event):
        """"""
        wildcard = "All files (*.*)|*.*"
        dlg = wx.FileDialog(
            self, message="Choose a file",
            defaultDir=self.current_directory,
            defaultFile="",
            wildcard=wildcard,
            style=wx.OPEN | wx.CHANGE_DIR
            )
        if dlg.ShowModal() == wx.ID_OK:
            db_path = dlg.GetPath()
            dlg.Destroy()
        else:
            dlg.Destroy()
            return
 
        self.engine = create_engine('sqlite:///%s' % db_path, echo=True)
 
        self.table_names = self.engine.table_names()
        self.tableCbo.SetItems(self.table_names)
        self.tableCbo.SetValue(self.table_names[0])
        self.loadTable("")
 
    #----------------------------------------------------------------------
    def setData(self, data=None):
        olv_columns = []
        for column in self.columns:
            olv_columns.append(ColumnDefn(column.title(), "left", 120, column.lower()))
        self.dataOlv.SetColumns(olv_columns)
 
        self.dataOlv.SetObjects(self.db_data)
 
########################################################################
class MainFrame(wx.Frame):
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
                          title="Database Viewer", size=(800,600))
        panel = MainPanel(self)
 
########################################################################
class GenApp(wx.App):
 
    #----------------------------------------------------------------------
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
 
    #----------------------------------------------------------------------
    def OnInit(self):
        # create frame here
        frame = MainFrame()
        frame.Show()
        return True
 
#----------------------------------------------------------------------
def main():
    """
    Run the demo
    """
    app = GenApp()
    app.MainLoop()
 
if __name__ == "__main__":
    main()

Давайте потратим немного времени, чтобы разобраться, как это работает. Вы заметите, что после того, как мы создадим виджет ObjectListView, мы его скрываем, так как еще не знаем, что с ним происходит. Этого не произойдет, пока пользователь не нажмет кнопку « Загрузить БД» . В обработчике loadDatabase мы открываем диалоговое окно файла, чтобы позволить пользователю выбрать файл базы данных SQLite, который он хочет загрузить. Для этого теста я бы рекомендовал использовать базу данных, которую мы создали ранее. После того, как он выбран, мы создаем движок SQLALchemy, извлекаем из него имена таблиц, устанавливаем раскрывающийся список со списком в списке имен таблиц, а затем загружаем первую таблицу в списке, вызывая наш метод loadTable .

В loadTable мы используем удобную функцию автозагрузки SQLAlchemy, чтобы «отразить» данные из базы данных в объект Table. Мы вызываем clear_mappers,
потому что нам нужно убедиться, что в данный момент ничего не сопоставлено с нашим фиктивным классом, а затем мы сопоставляем нашу новую таблицу с классом. Наконец, мы создаем сеанс SQLAlchemy и делаем простой запрос SELECT *, чтобы извлечь все записи из базы данных и передать их в виджет ObjectListView, который мы затем показываем.

Завершение

 

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