Сначала рассмотрим более сложную задачу - очистка поля внешнего ключа модели из пользовательского интерфейса. Для редактирования внешних ключей я использую QComboBox, связанный посредством QDataWidgetMapper и QSqlRelationalDelegate c QSqlRelationalTableModel. К сожалению, родной комбобокс не умеет очищать значение, поэтому придется его научить. Пусть он очищается по нажатию кнопки Delete. Очевидно, что комбобокс очистится, если установить его текущий индекс в -1. Напишем виджет:
class ResettableComboBox(QComboBox): def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: self.setCurrentIndex(-1) else: QComboBox.keyPressEvent(self, event)
и в дальнейшем будем использовать именно его.
К сожалению, это работает совсем не так, как ожидалось. Комбобокс действительно благополучно очищается, но при потере фокуса в модель пустое значение не попадает, восстанавливается предыдущее. В чем же дело?
Расследование показало, что дело в делегате и великом классе QVariant.
Здесь необходимо небольшое лирическое отступление, посвященное некоторым особенностям QVariant. Как известно, это контейнер, способный содержать значения разных типов, в том числе и пустое значение (NULL). Так вот великим изобретением разработчиков Qt являются типизированные пустые значения. Без осознания данного факта решить задачу не получилось бы. Проиллюстрирую:
>>> from PyQt4.QtCore import QVariant >>> QVariant().isNull() True >>> QVariant().isValid() False >>> QVariant(QVariant.Int).isNull() True >>> QVariant(QVariant.Int).isValid() True >>> QVariant(QVariant.String) == QVariant(QVariant.Int) FalseКак видно из примера, конструктор без параметров возвращает невалидное значение, а конструкторы с указанием типа - валидные NULL, причем не равные друг другу. Никогда бы не подумал, что отсутствие значение может иметь тип :)
Вернемся к нашим баранам. QSqlRelationalDelegate поступает просто: по текущему индексу комбобокса достает ключ связанной модели и вставляет полученное значение в поле основной модели. По индексу строки -1 в связанной модели нет значения и метод data возвращает невалидный QVariant(). Основная модель в свою очередь не принимает невалидное значение, ей подавай валидный NULL (QVariant(Type)). Вот и получается, что модель сохраняет старое значение.
Победить ситуацию можно, переписав метод QSqlRelationalDelegatе.setModelData. Нужно просто проверить: если из связанной модели пришло невалидное значение, просто подменить его пустым значением соответствующего типа:
class NullRelationalDelegate(QSqlRelationalDelegate): def setModelData(self, widget, model, index): if not index.isValid(): return childModel = None if isinstance(model, QSqlRelationalTableModel): childModel = model.relationModel(index.column()) if not childModel or not isinstance(widget, QComboBox): QItemDelegate.setModelData(self, widget, model, index) return currentItem = widget.currentIndex() childColIndex = childModel.fieldIndex(model.relation(index.column()).displayColumn()) childEditIndex = childModel.fieldIndex(model.relation(index.column()).indexColumn()) displayData = childModel.data(childModel.index(currentItem, childColIndex), Qt.DisplayRole) displayData = displayData if displayData.isValid() else QVariant(QVariant.String) editData = childModel.data(childModel.index(currentItem, childEditIndex), Qt.EditRole) editData = editData if editData.isValid() else QVariant(QVariant.Int) model.setData(index, displayData, Qt.DisplayRole) model.setData(index, editData, Qt.EditRole)
Из кода поместить пустое значение в поле внешнего ключа модели (как и в любое другое) еще проще. Методу setData нужно передавать не QVariant(), а QVariant(Type) соответствующего типа.
Комментариев нет:
Отправить комментарий