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