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