• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nens / ThreeDiToolbox / #2589

19 Sep 2025 08:50AM UTC coverage: 35.01% (-0.1%) from 35.146%
#2589

push

coveralls-python

web-flow
Merge 38792c162 into f6f4be1e7

62 of 260 new or added lines in 40 files covered. (23.85%)

6 existing lines in 5 files now uncovered.

4859 of 13879 relevant lines covered (35.01%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.85
/models/base.py
1
from qgis.PyQt.QtCore import QAbstractTableModel
1✔
2
from qgis.PyQt.QtCore import QModelIndex
1✔
3
from qgis.PyQt.QtCore import QSize
1✔
4
from qgis.PyQt.QtCore import Qt
1✔
5
from threedi_results_analysis.models.base_fields import CHECKBOX_FIELD
1✔
6
from threedi_results_analysis.models.base_fields import COLOR_FIELD
1✔
7
from threedi_results_analysis.models.base_fields import VALUE_FIELD
1✔
8

9
import inspect
1✔
10
import logging
1✔
11

12

13
logger = logging.getLogger(__name__)
1✔
14

15

16
class BaseModelRow(object):
1✔
17
    """Row inside a BaseModel.
18

19
    TODO: please, remove this horrible stuff. We have a feature freeze now, so
20
    I can only do some minimal renaming to clear it up a bit.
21
    [reinout, 2019-06-24]
22

23
    - Nothing subclasses this BaseModelRow.
24

25
    - BaseModelRow is dynamically generated with ``type()`` in BaseModel, but
26
      that makes no sense if there's just one class.
27

28
    - BaseModelRow calls BaseModel all the time. As a class, you should keep
29
      your fingers out of another class.
30

31
    """
32

33
    def __init__(self, model=None, **kwargs):
1✔
34

35
        self.model = model
1✔
36
        self._plots = {}
1✔
37
        # ^^^ TODO: this *might* be used in tool_water_balance and tool_graph,
38
        # though using such a private attribute is a bit weird.
39

40
        for field_name, field_class in self._fields:
1✔
41
            value = None
1✔
42
            if field_name in list(kwargs.keys()):
1✔
43
                value = kwargs[field_name]
1✔
44

45
            setattr(
1✔
46
                self, field_name, field_class.create_row_field(row=self, value=value)
47
            )
48

49
        # for function_name, function in self._functions:
50
        #    setattr(self, function_name, function)
51

52
    def get_row_nr(self):
1✔
53
        """
54
        get rownr of this item in the model
55
        :return: int: row number
56
        """
57

58
        return self.model.rows.index(self)
1✔
59

60
    def __getitem__(self, item):
1✔
61

62
        name = self.model.columns[item].name
1✔
63
        return getattr(self, name)
1✔
64

65
    def get_fields(self, show_only=False):
1✔
66

67
        if show_only:
×
68
            return [
×
69
                (name, column_field)
70
                for name, column_field in self._fields
71
                if column_field.show
72
            ]
73
        else:
74
            return self._fields
×
75

76

77
# TODO: nobody knows what BaseModel actually does. And whether we (still) need
78
# it. So.... one day, try to remove it.
79
class BaseModel(QAbstractTableModel):
1✔
80
    """Customized QAbstractTableModel with more pythonic way of field
81
    declaration and storage of settings and values in ModelItems, ItemFields
82
    and Fields"""
83

84
    _base_model_item_class = BaseModelRow
1✔
85
    class_name = "BaseModel"
1✔
86

87
    def __init__(self, ts_datasources=None, initial_data=[], parent=None):
1✔
88
        """
89
        initialisation.
90
        :param data: initial data. Array of item data.
91
            item format is dictionary with {
92
                '<field_id>': <field_value>,
93
                ...
94
            }
95
        :param: parent: some parent object used by Qt
96
        :return: -
97
        """
98
        self._rows = []
1✔
99

100
        super().__init__(parent)
1✔
101

102
        self.ts_datasources = ts_datasources
1✔
103

104
        # create item class
105
        self._fields = sorted(
1✔
106
            [
107
                (name, column_field)
108
                for name, column_field in inspect.getmembers(
109
                    self.Fields, lambda a: not (inspect.isroutine(a))
110
                )
111
                if not name.startswith("__") and not name.startswith("_")
112
            ],
113
            key=lambda item: item[1]._nr,  # Sort on column_field number
114
        )
115
        self.columns = [column_field for name, column_field in self._fields]
1✔
116

117
        self.item_class = type(
1✔
118
            self.class_name + "Row",
119
            (self._base_model_item_class, self.Fields),
120
            {"_fields": self._fields},
121
        )
122

123
        # initiate fields with fieldname, link to model and column_nr
124
        for i in range(0, len(self._fields)):
1✔
125
            name, field = self._fields[i]
1✔
126
            if hasattr(field, "contribute_to_class"):
1✔
127
                field.contribute_to_class(name, self, i)
1✔
128

129
        # process initial data
130
        self.insertRows(initial_data, signal=False)
1✔
131

132
    def _create_item(self, *args, **kwargs):
1✔
133
        """
134

135
        :param args: all (initial) values of the fields. See Fields class for
136
                     available fields
137
        :param kwargs: all (initial) values of the fields. See Fields class
138
                       for available fields
139
        :return: created item
140
        """
141
        return self.item_class(self, *args, **kwargs)
1✔
142

143
    def rowCount(self, index=QModelIndex):
1✔
144
        """
145
        get number of rows (nr of items). Required function for
146
        QAbstractTableModel
147
        :param index: QModelIndex
148
        :return:
149
        """
150
        return len(self._rows)
1✔
151

152
    def columnCount(self, index=QModelIndex):
1✔
153
        """
154
        get visible columns for display in table. Required function for
155
        QAbstractTableModel
156
        :param index: QModelIndex
157
        :return:
158
        """
159
        return len(self.columns)
1✔
160

161
    def index(self, row_nr, col_nr, parent=None):
1✔
162
        """
163
        Creates index for this model. Required function for QAbstractTableModel
164
        :param row_nr: int row number
165
        :param col_nr: int column number
166
        :param parent: parent of index
167
        :return: QModelIndex instance
168
        """
169
        return self.createIndex(row_nr, col_nr)
1✔
170

171
    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
1✔
172
        """Qt function to get data from items for the visible columns"""
173

174
        if not index.isValid():
1✔
175
            return None
×
176

177
        row = self.rows[index.row()]
1✔
178

179
        if role == Qt.ItemDataRole.DisplayRole:
1✔
180
            if row[index.column()].field_type == VALUE_FIELD:
1✔
181
                return row[index.column()].value
1✔
182
        elif role == Qt.ItemDataRole.BackgroundRole:
1✔
183
            if row[index.column()].field_type == COLOR_FIELD:
×
184
                return row[index.column()].qvalue
×
185
        elif role == Qt.ItemDataRole.TextAlignmentRole:
1✔
NEW
186
            return Qt.AlignmentFlag.AlignVCenter
×
187
        elif role == Qt.ItemDataRole.CheckStateRole:
1✔
188
            if row[index.column()].field_type == CHECKBOX_FIELD:
1✔
189
                return row[index.column()].qvalue
1✔
190
            else:
191
                return None
×
192

193
    def headerData(self, col_nr, orientation=Qt.Orientation.Horizontal, role=Qt.ItemDataRole.DisplayRole):
1✔
194
        """
195
        required Qt function for getting column information
196
        :param col_nr: column number
197
        :param orientation: Qt orientation of header Qt.Horizontal or Qt.Vertical
198
        :param role: Qt Role (DisplayRole, SizeHintRole, etc)
199
        :return: value of column, given the role
200
        """
201
        if orientation == Qt.Orientation.Horizontal:
1✔
202
            if role == Qt.ItemDataRole.DisplayRole:
1✔
203
                return self.columns[col_nr].column_name
1✔
204
        else:
205
            # give grey balk at start of row a small dimension to select row
NEW
206
            if Qt.ItemDataRole.SizeHintRole:
×
207
                return QSize(10, 0)
×
208

209
    def setData(self, index, value, role):
1✔
210
        """
211
        required Qt function for setting data, including sending of signals
212
        :param index: QtModelIndex instance
213
        :param value: new value for ItemField
214
        :param role: Qt role (DisplayRole, CheckStateRole)
215
        :return: was setting value successful
216
        """
217

218
        # dataChanged.emit is done within the ItemField, triggered by setting
219
        # the value
220
        self._rows[index.row()][index.column()].value = value
1✔
221
        return True
1✔
222

223
    def flags(self, index):
1✔
224

NEW
225
        flags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
×
226
        if self.columns[index.column()].field_type == CHECKBOX_FIELD:
×
NEW
227
            flags |= Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEditable
×
228

229
        return flags
×
230

231
    def insertRows(self, data_items, signal=True):
1✔
232
        """
233
        required Qt function for adding rows, including sending signals
234

235
        :param data_items: list with values as dictionaries
236
        :param signal: send signal, False will prevent function from sending signal
237
        """
238
        if signal:
1✔
239
            self.beginInsertRows(
1✔
240
                QModelIndex(), self.rowCount(), self.rowCount() + len(data_items) - 1
241
            )
242

243
        for data_item in data_items:
1✔
244
            item = self._create_item(**data_item)
1✔
245
            self._rows.append(item)
1✔
246

247
        if signal:
1✔
248
            self.endInsertRows()
1✔
249

250
    def removeRows(self, row, count, parent=QModelIndex()):
1✔
251
        """
252
        required Qt function to remove rows from model
253
        :param row: first number to remove (count starts with row 1)
254
        :param count: number of rows to remove
255
        :param parent: some Qt parameter
256
        """
257
        # signal
258
        self.beginRemoveRows(parent, row, row + count - 1)
1✔
259

260
        for i in range(row + count - 1, row - 1, -1):
1✔
261
            del self._rows[i]
1✔
262

263
        # signal
264
        self.endRemoveRows()
1✔
265

266
    @property
1✔
267
    def rows(self):
1✔
268
        """
269
        :return: list of model items of model
270
        """
271
        return self._rows
1✔
272

273
    def set_column_sizes_on_view(self, table_view):
1✔
274
        """Helper function for applying the column sizes on a view.
275

276
        :table_view: table view instance that uses this model
277
        """
278
        if table_view.model is None:
1✔
279
            raise RuntimeError("No model set on view.")
×
280

281
        for col_nr in range(0, self.columnCount()):
1✔
282
            width = self.columns[col_nr].column_width
1✔
283
            if width:
1✔
284
                table_view.setColumnWidth(col_nr, width)
1✔
285
            if not self.columns[col_nr].show:
1✔
286
                table_view.setColumnHidden(col_nr, True)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc