BearHarrison
BearHarrison

Reputation: 1

PyQT: Unable to populate QTTreeView Columns correctly

I have a dictionary listing of a file structure where the last five keys of each 'file' represent file metadata that I would like to show on various columns.

tree = {'C:': {'Windows': {'System32': {'drivers': {'etc': {'hosts': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2013-08-22T13:25:41.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}, 'lmhosts.sam': {'Attrs:Archive': {'Crtd:2013-08-22T15:39:39.000Z': {'Mdfd:2013-08-22T15:38:13.000Z': {'Accd:2013-08-22T15:38:13.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}, 'services': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2018-08-13T23:44:10.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2018-08-13T23:44:10.000Z': {}}}}}}, 'networks': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2013-08-22T13:25:41.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}, 'protocol': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2013-08-22T13:25:41.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}}}}}}}

My popluate tree function checks each key to see if it contains a keyword for each metadata attribute, strips it out if found and sets it to one of 6 variables depending on which attribute. If no attribute string is found, it treats it as a file and appends it as a row/new file with the the attribute data added.

    def fill_model_from_json(self, parent, d): 
        if isinstance(d, dict):
            for key, value in d.items():
                it = QtGui.QStandardItem(str()) 
                it2 = QtGui.QStandardItem(str())
                if 'Attrs:' in key:                                 #if metadata-specific string in the dict key
                    attrs_value = re.sub("^(.*Attrs:)" ,'', key)    #strip out some the tag leaving just the data
                    it2 = QtGui.QStandardItem(str(attrs_value))     #assign it to it2, should go on row2 of parent item
                else :               
                    it = QtGui.QStandardItem(str(key))
                    parent.appendRow([it, it2])   
                    self.fill_model_from_json(it, value)

Unfortunately this doesnt quite work and each attribute qstandarditem generates its own row/child instead of populating the parent row. This is my first PyQT project and I'm a bit stuck on how to fix to the population function to generate the correct qtree structure.

How it currently renders: How it currently renders

How it should render: How it should render

import os
import sys
from PyQt5 import QtGui,QtCore,QtWidgets
from PyQt5.QtGui import QIcon
import re

tree = {'C:': {'Windows': {'System32': {'drivers': {'etc': {'hosts': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2013-08-22T13:25:41.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}, 'lmhosts.sam': {'Attrs:Archive': {'Crtd:2013-08-22T15:39:39.000Z': {'Mdfd:2013-08-22T15:38:13.000Z': {'Accd:2013-08-22T15:38:13.000Z': {'Chngd:2015-10-13T20:48:04.000Z': {}}}}}}, 'services': {'Attrs:Archive': {'Crtd:2013-08-22T13:25:43.000Z': {'Mdfd:2018-08-13T23:44:10.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2018-08-13T23:44:10.000Z': {}}}}}}, 'networks': {'Attrs:Archive': {'Crtd:2017-05-21T14:23:21.000Z': {'Mdfd:2013-08-22T13:25:41.000Z': {'Accd:2013-08-22T13:25:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}, 'protocol': {'Attrs:Archive': {'Crtd:2012-08-22T07:25:31.000Z': {'Mdfd:2012-08-22T01:34:41.000Z': {'Accd:2012-08-22T14:17:41.000Z': {'Chngd:2016-10-13T20:40:04.000Z': {}}}}}}}}}}}}

class MainFrame(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        self.tree = QtWidgets.QTreeView(self)
        layout = QtWidgets.QHBoxLayout(self)
        layout.addWidget(self.tree)

        root_model = QtGui.QStandardItemModel()
        root_model.setColumnCount(6)
        self.tree.setModel(root_model)
        self.tree.model().setHorizontalHeaderLabels(["File", "Attribute", "Created", "Modified", "Accessed", "Changed"]) #labels columns
 
        self.fill_model_from_json(root_model.invisibleRootItem(), tree)
        self.tree.expandAll()        
    def fill_model_from_json(self, parent, d): 
        if isinstance(d, dict):
            for key, value in d.items():
                it = QtGui.QStandardItem(str(""))  
                it2 = QtGui.QStandardItem(str(""))  
                it3 = QtGui.QStandardItem(str(""))  
                it4 = QtGui.QStandardItem(str(""))  
                it5 = QtGui.QStandardItem(str(""))  
                it6 = QtGui.QStandardItem(str(""))  
                if 'Attrs:' in key:
                    attrs_value = re.sub("^(.*Attrs:)" ,'', key)
                    it2 = QtGui.QStandardItem(str(attrs_value))  
                if 'Crtd:' in key:
                    crtd_value = re.sub("^(.*Crtd:)" ,'', key)
                    it3 = QtGui.QStandardItem(str(crtd_value))   
                if 'Mdfd:' in key:
                    mdfd_value = re.sub("^(.*Mdfd:)" ,'', key)
                    it4 = QtGui.QStandardItem(str(mdfd_value))   
                if 'Accd:' in key:
                    accd_value = re.sub("^(.*Accd:)" ,'', key)
                    it5 = QtGui.QStandardItem(str(accd_value)) 
                if 'Chngd:' in key:
                    chngd_value = re.sub("^(.*Chngd:)" ,'', key)
                    it6 = QtGui.QStandardItem(str(chngd_value))   
                else :               
                    it = QtGui.QStandardItem(str(key))
                    parent.appendRow([it, it2, it3, it4, it5, it6])   
                    self.fill_model_from_json(it, value)

app = QtWidgets.QApplication(sys.argv)
main = MainFrame()
main.show()
sys.exit(app.exec_())

Upvotes: 0

Views: 43

Answers (1)

eyllanesc
eyllanesc

Reputation: 243965

The structure of your json fulfills a strange pattern so the method of creating the model will have similar characteristics. In this case I assume that if I find a key that meets the condition that they start with any of the elements of "Attrs:", "Crtd:", "Mdfd:", "Accd:", "Chngd:" then its children will comply the same. I also assume that they will only have one child.

Considering the above, the solution is:

def fill_model_from_json(self, parent, d):
    if isinstance(d, dict):
        for key, value in d.items():
            if self.find_value(key) is not None:
                items = []
                for i, value in enumerate(self.get_values(d), start=1):
                    it = QtGui.QStandardItem(value)
                    parent.parent().setChild(parent.row(), i, it)
            else:
                it = QtGui.QStandardItem(key)
                parent.appendRow(it)
                self.fill_model_from_json(it, value)

def get_values(self, d):
    values = [""] * 5

    while d:
        key = list(d.keys())[0]
        value = d[key]
        d = value
        t = self.find_value(key)
        if t is not None:
            i, value = t
            values[i] = value
    return values

def find_value(self, key):
    COLUMNS = ("Attrs:", "Crtd:", "Mdfd:", "Accd:", "Chngd:")
    for i, column in enumerate(COLUMNS):
        if column in key:
            value = re.sub(r"^(.*{})".format(column), "", key)
            return i, value

Upvotes: 1

Related Questions