Search code examples
pythonmodel-view-controllersqlalchemywxpythonobjectlistview

Use ObjectListView in the Model-View-Controller pattern with SQLAlchemy in Python


I think about how to use ObjectListView fitting the Model-View-Controller Pattern with wxPython & SQLAlchemy. And I am not sure about it so I created a simple example as a working basis not as a solution.

The concrete question related to the code below is: What should happen if a new MyData object is generated?

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import wx
import sqlalchemy as sa
import sqlalchemy.ext.declarative as sad
import ObjectListView as olv

_Base = sad.declarative_base()

class MyData(_Base):
    """the database table representing class"""
    __tablename__ = 'MyData'

    __name = sa.Column('name', sa.String, primary_key=True)
    __count = sa.Column('count', sa.Numeric(10, 2))

    def __init__(self, name, count):
        super(MyData, self).__init__()
        self.__name = name
        self.__count = count

    def GetName(self):
        return self.__name

    def GetCount(self):
        return self.__count


def CreateData():
    """
        helper creating data

        imagnine this as a SELECT * FROM on the database
    """
    return [
        MyData('Anna', 7),
        MyData('Bana', 6)
        ]


class MyView(olv.ObjectListView):
    def __init__(self, parent):
        super(MyView, self).__init__(parent, wx.ID_ANY, style=wx.LC_REPORT)
        self.SetColumns([
            olv.ColumnDefn('Name', valueGetter='GetName'),
            olv.ColumnDefn('Count', valueGetter='GetCount')
            ])
        data = CreateData()
        self.SetObjects(data)


    def ColDef(self):
        return 

class MyApp(wx.App):
    def OnInit(self):
        frame = wx.Frame(None)
        view = MyView(frame)
        frame.Show()
        return True


if __name__ == '__main__':
    app = MyApp()
    app.MainLoop()

What whould you think about... Create a controller "MyDataController" that handle all the sqlalchemy-stuff for MyData objects. e.g. GetAllMyDataObjects AddMyDataObjectToDatabase, QueryMyData, ... Related to the Observer-Pattern the ObjectListView observer the controller as the subject. I am not sure if this is an elegant solution. The point is the confusion about, what is the model? The one (and new) MyData instance or the list of all MyData instances? There is no intelligent list which could act like a model.


Solution

  • With regards to OLV. In my case when an update to the SA model happens I use pubsub to inform the world about the change add/update/delete.

    Then my OLV base class subscribes to the 'itemAdded', 'itemModified' and 'itemDeleted' messages and the following are the methods called:

    def pubListItemAdded(self, dbitem):
        """
        Add list if dbitem instance matches dbScKlass
    
        :param dbitem: an SA model instance
    
        If dbitem instance matches the list controls model it is added.
        """
        # protect from PyDeadObjectError
        if self:
            # E.g. Externalimp is a faked class and does not exist in db
            # so we need to protect for that
            if hasattr(db, self._klassName):
                tList = self.getList()
                cInst = getattr(db, self._klassName)
                if isinstance(dbitem, cInst):
                    log.debug("olvbase - added: %s", dbitem)
                    log.debug("olvbase - added: %s", self)
                    # for some reason this creates dups on e.g. rating/tasting/consumption
                    # so, lets check if it is there and only add if not
                    idx = tList.GetIndexOf(dbitem)
                    if idx == -1:
                        tList.AddObject(dbitem)
                    else:
                        tList.RefreshObject(dbitem)
    
                    # bring it into view
                    self.resetSelection()
                    tList.SelectObject(dbitem, deselectOthers=True,
                                       ensureVisible=True)
    
    def pubListItemModified(self, dbitem):
        """
        Update list if dbitem instance matches dbScKlass
    
        :param dbitem: an SA model instance
    
        If dbitem instance matches the list controls model it is updated.
        """
        # protect from PyDeadObjectError
        if self:
            # E.g. Externalimp is a faked class and does not exist in db
            # so we need to protect for that
            if hasattr(db, self._klassName):
                cInst = getattr(db, self._klassName)
                if isinstance(dbitem, cInst):
                    log.debug("olvbase - modified: %s", dbitem)
                    log.debug("olvbase - modified: %s", self)
                    tList = self.getList()
                    # need to refresh to ensure relations are loaded
                    wx.GetApp().ds.refresh(dbitem)
                    tList.RefreshObject(dbitem)
                    tList.SelectObject(dbitem, deselectOthers=True,
                                        ensureVisible=True)
                    # deselect all, so if we select same item again
                    # we will get a select event
                    tList.DeselectAll()
    
    def pubListItemDeleted(self, dbitem):
        """
        Delete from list if dbitem instance matches dbScKlass
    
        :param dbitem: an SA model instance
    
        If dbitem instance matches the list controls model it is updated.
        """
        # protect from PyDeadObjectError
        if self:
            # E.g. Externalimp is a faked class and does not exist in db
            # so we need to protect for that
            if hasattr(db, self._klassName):
                cInst = getattr(db, self._klassName)
                if isinstance(dbitem, cInst):
                    log.debug("olvbase - deleted: %s", dbitem)
                    log.debug("olvbase - deleted: %s", self)
                    tList = self.getList()
                    tList.RemoveObject(dbitem)
                    self.currentObject = None
                    self.currentItemPkey = None