title: abstractTableModel to display two sets of interdependent data in PySide
body: <p>I'm building a python/PySide tool for cinematography. I created an object which represents a shot. It has properties for start time, end time, and a list of references to actor objects. The actor object has simple properties (name, build, age, etc) and can be shared between shots.</p>
  
  <p>I want to display this in two table views in PySide. One table view lists the shots (and properties in columns), while the other displays actors referenced in the <strong>selected</strong> shots. If no shots are selected, the second table view is empty. If multiple shots are selected, all referenced actors are displayed in the actor table view.</p>
  
  <p>I created an abstractTableModel for my shot data and everything is working correctly for the shot data in its corresponding table view. However I'm not sure how to even approach the table view for the actors. Should I use another abstractTableModel for the actors? I can't seem to figure out how to feed/connect the data to the second table view for actors contained in selected shots using a abstractTableModel.</p>
  
  <p>I think part of my issue is that I only want to display the actor information once, regardless of whether multiple selected shots reference the same actor. After multiple failed attempts, I'm thinking that I need to redirect and parse the selected shot information (their actor list property) into a custom property of the main window to contain a list of <strong>all</strong> referenced actor indices and make an abstractTableModel which uses this to fetch the actual actor properties for display. I'm not fully confident this will work, not to mention my gut tells me this is a messy approach, so I've come here for advice.</p>
  
  <p>Is this the correct approach? If not, what's the 'correct' way of setting this up in PySide/python.</p>
  
  <p>Please keep in mind this is my first foray in data models and PySide.</p>
  
  <p>Here's the shot model.</p>
  
  <pre><code>class ShotTableModel(QtCore.QAbstractTableModel):
      def __init__(self, data=[], parent=None, *args):
          super(ShotTableModel, self).__init__(parent)
          self._data = data
  
      def rowCount(self, parent):
          return len(self._data.shots)
  
      def columnCount(self, parent):
          return len(self._data._headers_shotList)
  
      def getItemFromIndex(self, index):
          if index.isValid():
              item = self._data.shots[index.row()]   
              if item:
                  return item
          return None
  
      def flags(self, index):
          if index.isValid():
              item = self.getItemFromIndex(index)
              return item.qt_flags(index.column())
  
      def data(self, index, role):
          if not index.isValid():
              return None
  
          item = self.getItemFromIndex(index)
  
          if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
              return item.qt_data(index.column())
  
          if role == QtCore.Qt.CheckStateRole:
              if index.column() is 0:
                  return item.qt_checked
  
  #         if role == QtCore.Qt.BackgroundColorRole:
  #             return QtGui.QBrush()
  
  #         if role == QtCore.Qt.FontRole:
  #             return QtGui.QFont()
  
          if role == QtCore.Qt.DecorationRole:
              if index.column() == 0:
                  resource = item.qt_resource()
                  return QtGui.QIcon(QtGui.QPixmap(resource))
  
          if role == QtCore.Qt.ToolTipRole:
              return item.qt_toolTip()
  
          return None
  
      def setData(self, index, value, role = QtCore.Qt.EditRole):
          if index.isValid():
              item = self.getItemFromIndex(index)
              if role == QtCore.Qt.EditRole:
                  item.qt_setData(index.column(), value)
                  self.dataChanged.emit(index, index)
                  return value
  
              if role == QtCore.Qt.CheckStateRole:
                  if index.column() is 0:
                      item.qt_checked = value
                      return True
          return value
  
      def headerData(self, section, orientation, role):
          if role == QtCore.Qt.DisplayRole:
              if orientation == QtCore.Qt.Horizontal:
                  return self._data._headers_shotList[section]
  
      def insertRows(self, position, rows, parent = QtCore.QModelIndex()):
          self.beginInsertRows(parent, position, position + rows - 1)
          for row in range(rows):
              newShotName = self._data.getUniqueName(self._data.shots, 'New_Shot')
              newShot = Shot(newShotName)
              self._data.shots.insert(position, newShot)
          self.endInsertRows()
          return True
  
      def removeRows(self, position, rows, parent = QtCore.QModelIndex()):
          self.beginRemoveRows(parent, position, position + rows - 1)
          for row in range(rows):
              self._data.shots.pop(position)   
          self.endRemoveRows()
          return True
  </code></pre>
  
  <p>Here's the data block containing the shot and actor instances. This is what I pass to the shot model.</p>
  
  <pre><code>class ShotManagerData(BaseObject):
      def __init__(self, name='', shots=[], actors=[]):
          super(ShotManagerData, self).__init__(name)
          self._shots = shots # Shot(name="New_Shot", start=0, end=100, actors=[])
          self._actors = actors # Actor(name="New_Actor", size="Average", age=0)
  
          self.selectedShotsActors = [] #decided to move to this data block
  
          self._headers_shotList = ['Name', 'Start Frame', 'End Frame']
          self._headers_actorList = ['Name', 'Type', 'RootNode', 'File']
  
      def save(self, file=None):
          mEmbed.save('ShotManagerData', self)
  
      @classmethod
      def load(cls, file=None):
          return(mEmbed.load('ShotManagerData'))
  
      @staticmethod
      def getUniqueName(dataList, baseName='New_Item'):
          name = baseName
          increment = 0
          list_of_names = [data.name if issubclass(data.__class__, BaseObject) else str(data) for data in dataList] 
          while name in list_of_names:
              increment += 1
              name = baseName + '_{0:02d}'.format(increment)
          return name
  
      @property
      def actors(self):
          return self._actors
  
      @property
      def shots(self):
          return self._shots
  
      def actorsOfShots(self, shots):
          actorsOfShots = []
          for shot in shots:
              for actor in shot.actors:
                  if actor not in actorsOfShots:
                      actorsOfShots.append(actor)
          return actorsOfShots
  
      def shotsOfActors(self, actors):
          shotsOfActors = []
          for actor in actors:
              for shot in self.shots:
                  if actor in shot.actors and actor not in shotsOfActors:
                      shotsOfActors.append(shot)
          return shotsOfActors
  </code></pre>
  
  <p>Finally the main tool.</p>
  
  <pre><code>class ShotManager(form, base):
      def __init__(self, parent=None):
          super(ShotManager, self).__init__(parent)
          self.setupUi(self)
  
          #=======================================================================
          # Properties
          #=======================================================================
  
  
          self.data = ShotManagerData() #do any loading if necessary here
          self.actorsInSelectedShots = []
  
          #test data
          actor1 = Actor('Actor1')
          actor2 = Actor('Actor2')
          actor3 = Actor('Actor3')
  
          shot1 = Shot('Shot1', [actor1, actor2])
          shot2 = Shot('Shot2', [actor2, actor3])
          shot3 = Shot('Shot3', [actor1])
  
          self.data.actors.append(actor1)
          self.data.actors.append(actor2)   
          self.data.actors.append(actor3)
  
          self.data.shots.append(shot1)
          self.data.shots.append(shot2)
          self.data.shots.append(shot3)
  
          #=======================================================================
          # Models
          #=======================================================================
          self._model_shotList = ShotTableModel(self.data)
          self._proxyModel_shotList = QtGui.QSortFilterProxyModel()
          self._proxyModel_shotList.setSourceModel(self._model_shotList)
          self.shotList.setModel(self._proxyModel_shotList)  #this is the QTableView
          self._selModel_shotList = self.shotList.selectionModel()
          self.shotList.setSortingEnabled(True)
          self._proxyModel_shotList.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
  
  
          self._model_actorList = SelectedShotsActorTableModel(self.data)
          self._proxyModel_actorList = QtGui.QSortFilterProxyModel()
          self._proxyModel_actorList.setSourceModel(self._model_actorList)
          self.actorList.setModel(self._proxyModel_actorList)
          self._selModel_actorList = self.actorList.selectionModel()
  
          #=======================================================================
          # Events
          #=======================================================================
          self.addShot.clicked.connect(self.addShot_clicked)
          self.delShot.clicked.connect(self.delShot_clicked)
  
  
          self._selModel_shotList.selectionChanged.connect(self.shotList_selectionChanged)
  
      #===========================================================================
      # General Functions    
      #===========================================================================
      def getSelectedRows(self, widget):
          selModel = widget.selectionModel()
          proxyModel = widget.model()
          model = proxyModel.sourceModel()
          rows = [proxyModel.mapToSource(index).row() for index in selModel.selectedRows()]
          rows.sort()
          return rows
  
      def getSelectedItems(self, widget):
          selModel = widget.selectionModel()
          proxyModel = widget.model()
          model = proxyModel.sourceModel()
          indices = [proxyModel.mapToSource(index) for index in selModel.selectedRows()]
          items = [model.getItemFromIndex(index) for index in indices]
          return items
      #===========================================================================
      # Event Functions    
      #===========================================================================
      def addShot_clicked(self):
          position = len(self.data.shots)
          self._proxyModel_shotList.insertRows(position,1)
  
      def delShot_clicked(self):
          rows = self.getSelectedRows(self.shotList)
          for row in reversed(rows):
              self._proxyModel_shotList.removeRows(row, 1)
  
      def shotList_selectionChanged(self, selected, deselected):
          selectedShots = self.getSelectedItems(self.shotList)
          print 'SelectedShots: {}'.format(selectedShots)
          self.data.selectedShotsActors = self.data.actorsOfShots(selectedShots)
          print 'ActorsOfShots: {}'.format(self.data.selectedShotsActors)
  
          self._proxyModel_actorList.setData() # this line reports missing variables
  </code></pre>
  
  <p>This is the selectedShotActors model:</p>
  
  <pre><code>class SelectedShotsActorTableModel(QtCore.QAbstractTableModel):
      def __init__(self, data=[], headers=[], parent=None, *args):
          super(SelectedShotsActorTableModel, self).__init__(parent)
          self._data = data
  
      def rowCount(self, parent):
          return len(self._data.selectedShotsActors)
  
      def columnCount(self, parent):
          return len(self._data._headers_actorList)
  
      def getItemFromIndex(self, index):
          if index.isValid():
              item = self._data.selectedShotsActors[index.row()]   
              if item:
                  return item
          return None
  
      def flags(self, index):
          if index.isValid():
              item = self.getItemFromIndex(index)
              return item.qt_flags(index.column())
  
      def data(self, index, role=QtCore.Qt.DisplayRole):
          if not index.isValid():
              return None
  
          item = self.getItemFromIndex(index)
          if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
              return item.qt_data(index.column())
  
          if role == QtCore.Qt.CheckStateRole:
              if index.column() is 0:
                  return item.qt_checked
  #         
  #         if role == QtCore.Qt.BackgroundColorRole:
  #             return QtGui.QBrush()
  #         
  #         if role == QtCore.Qt.FontRole:
  #             return QtGui.QFont()
  
          if role == QtCore.Qt.DecorationRole:
              if index.column() == 0:
                  resource = item.qt_resource()
                  return QtGui.QIcon(QtGui.QPixmap(resource))
  
          if role == QtCore.Qt.ToolTipRole:
              return item.qt_toolTip()
  
      def setData(self, index, value, role = QtCore.Qt.EditRole):
          if index.isValid():
              item = self.getItemFromIndex(index)
              if role == QtCore.Qt.EditRole:
                  item.qt_setData(index.column(), value)
                  self.dataChanged.emit(index, index)
                  return value
              if role == QtCore.Qt.CheckStateRole:
                  if index.column() is 0:
                      item.qt_checked = value
                      return True
          return value
  
      def headerData(self, section, orientation, role):
          if role == QtCore.Qt.DisplayRole:
              if orientation == QtCore.Qt.Horizontal:
                  return self._data._headers_actorList[section]
  </code></pre>