diff --git a/.travis.yml b/.travis.yml index b0f04e2..b187f88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,21 @@ language: python + python: - 2.7 + +sudo: required # install.sh requires sudo apt-get + +install: +- echo "Install.." +- source .travis/install_linux.sh +- source .travis/install.sh +- pip install coverage nose +- git clone https://github.com/pyblish/pyblish-rpc +- git clone https://github.com/pyblish/pyblish-base +- export PYTHONPATH=$(pwd)/pyblish-rpc:$(pwd)/pyblish-base + script: -- git clone https://github.com/pyblish/pyblish-qml.git +- nosetests -c .noserc + +after_success: +- coveralls diff --git a/.travis/install.sh b/.travis/install.sh new file mode 100644 index 0000000..77bef2f --- /dev/null +++ b/.travis/install.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +echo "Building in $TRAVIS_OS_NAME" + +echo "Fetching data.." +wget http://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/PyQt-gpl-5.5.1.tar.gz +wget http://sourceforge.net/projects/pyqt/files/sip/sip-4.17/sip-4.17.tar.gz + +echo "Building sip.." +tar xzf sip-4.17.tar.gz +cd sip-4.17 +python configure.py +make +sudo make install +cd .. + +echo "Building PyQt5.." +tar xzf PyQt-gpl-5.5.1.tar.gz +cd PyQt-gpl-5.5.1 +python configure.py --confirm-license +make +sudo make install +cd .. + +echo "Finished install.sh" \ No newline at end of file diff --git a/.travis/install_linux.sh b/.travis/install_linux.sh new file mode 100644 index 0000000..0b087b0 --- /dev/null +++ b/.travis/install_linux.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sudo apt-add-repository -y ppa:beineri/opt-qt551 +sudo apt-get update +sudo apt-get install -y qt-latest + +source /opt/qt55/bin/qt55-env.sh >> POST_COMMAND + +export QT_ROOT=/opt/qt55 +export SIP=/usr/bin/sip + diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 439d32e..807e311 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -51,7 +51,7 @@ class Controller(QtCore.QObject): state_changed = QtCore.pyqtSignal(str, arguments=["state"]) - # PyQt Properties + # Qt Properties itemModel = pyqtConstantProperty(lambda self: self.item_model) itemProxy = pyqtConstantProperty(lambda self: self.item_proxy) recordProxy = pyqtConstantProperty(lambda self: self.record_proxy) @@ -64,19 +64,27 @@ class Controller(QtCore.QObject): def __init__(self, parent=None): super(Controller, self).__init__(parent) - self._temp = [1, 2, 3, 4] - self.item_model = models.ItemModel() self.result_model = models.ResultModel() - self.instance_proxy = models.InstanceProxy(self.item_model) - self.plugin_proxy = models.PluginProxy(self.item_model) - self.result_proxy = models.ResultProxy(self.result_model) + self.instance_proxy = models.ProxyModel(self.item_model) + self.instance_proxy.add_inclusion("itemType", "instance") + + self.plugin_proxy = models.ProxyModel(self.item_model) + self.plugin_proxy.add_inclusion("itemType", "plugin") + self.plugin_proxy.add_exclusion("hasCompatible", False) + + self.result_proxy = models.ProxyModel(self.result_model) + self.result_proxy.add_exclusion("levelname", "DEBUG") + self.result_proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) # Used in Perspective self.item_proxy = models.ProxyModel(self.item_model) - self.record_proxy = models.RecordProxy(self.result_model) - self.error_proxy = models.ErrorProxy(self.result_model) + self.record_proxy = models.ProxyModel(self.result_model) + self.record_proxy.add_inclusion("type", "record") + + self.error_proxy = models.ProxyModel(self.result_model) + self.error_proxy.add_inclusion("type", "error") self.changes = dict() self.is_running = False @@ -591,6 +599,16 @@ def reset(self): A reset completely flushes the state of the GUI and reverts back to how it was when it first got launched. + Pipeline: + ______________ ____________ ______________ + | | | | | | + | host.reset() |-->| on_reset() |-->| on_context() |-- + |______________| |____________| |______________| | + _______________ __________ _______________ | + | | | | | | | + | on_finished() |<--| on_run() |<--| on_discover() |<-- + |_______________| |__________| |_______________| + """ if not any(state in self.states for state in ("ready", "finished")): @@ -614,26 +632,9 @@ def on_finished(plugins, context): plugin) plugin.compatibleInstances = list(i.id for i in instances) else: - plugin.compatibleInstances = ["Context"] + plugin.compatibleInstances = [context.id] - # Reorder instances in support of "cooperative collection" - self.item_model.beginResetModel() - - items = dict() - for instance in self.item_model.instances: - items[instance.id] = instance - self.item_model.items.remove(instance) - - # TODO: Clean this up. Instances are cached for - # brevity but this is where we are forced to fight it. - self.item_model.instances[:] = [] - self.item_model.items.append(items["Context"]) - self.item_model.instances.append(items["Context"]) - for instance in context: - self.item_model.items.append(items[instance.id]) - self.item_model.instances.append(items[instance.id]) - - self.item_model.endResetModel() + self.item_model.reorder(context) # Report statistics stats["requestCount"] -= self.host.stats()["totalRequestCount"] @@ -663,7 +664,7 @@ def on_discover(plugins, context): collectors = list() for plugin in plugins: - self.item_model.add_plugin(plugin) + self.item_model.add_plugin(plugin.to_json()) # Sort out which of these are Collectors if not pyblish.lib.inrange( @@ -680,8 +681,9 @@ def on_discover(plugins, context): def on_context(context): context.data["pyblishQmlVersion"] = version - self.item_model.add_context(context) - self.result_model.add_context(context) + self.item_model.add_context(context.to_json()) + self.result_model.add_context(context.to_json()) + util.async( self.host.discover, callback=lambda plugins: on_discover(plugins, context) @@ -825,7 +827,7 @@ def update_context(ctx): continue context.append(instance) - self.item_model.add_instance(instance) + self.item_model.add_instance(instance.to_json()) util.async(iterator.next, callback=on_next) diff --git a/pyblish_qml/models.py b/pyblish_qml/models.py index 34ef643..c6c455b 100644 --- a/pyblish_qml/models.py +++ b/pyblish_qml/models.py @@ -6,101 +6,87 @@ from pyblish_qml import settings -item_defaults = { - "id": "default", - "name": "default", - "isProcessing": False, - "families": list(), - "familiesConcatenated": "", - "isToggled": True, - "optional": True, - "hasError": False, - "actionHasError": False, - "actionPending": True, - "succeeded": False, - "processed": False, - "currentProgress": 0, - "duration": 0, # Time (ms) to process pair - "finishedAt": 0, # Time (s) when finished - "amountPassed": 0, # Number of plug-ins/instances passed - "amountFailed": 0, # Number of plug-ins/instances failed - "optional": False, -} - -plugin_defaults = { - "doc": "", - "order": None, - "hasRepair": False, - "hasCompatible": False, - "hosts": list(), - "type": "unknown", - "module": "unknown", - "compatibleInstances": list(), - "contextEnabled": False, - "instanceEnabled": False, - "pre11": True, - "verb": "unknown", - "actions": list(), - "actionsIconVisible": False, - "path": "", - "__instanceEnabled__": False -} - -instance_defaults = { - "optional": True, - "family": None, - "niceName": "default", - "compatiblePlugins": list(), -} - -result_defaults = { - "type": "default", - "filter": "default", - "message": "default", - - # Temporary metadata: "default", until treemodel - "instance": "default", - "plugin": "default", - - # LogRecord - "threadName": "default", - "name": "default", - "thread": "default", - "created": "default", - "process": "default", - "processName": "default", - "args": "default", - "module": "default", - "filename": "default", - "levelno": 0, - "levelname": "default", - "exc_text": "default", - "pathname": "default", - "lineno": 0, - "msg": "default", - "exc_info": "default", - "funcName": "default", - "relativeCreated": "default", - "msecs": 0.0, - - # Exception - "fname": "default", - "line_number": 0, - "func": "default", - "exc": "default", - - # Context - "port": 0, - "host": "default", - "user": "default", - "connectTime": "default", - "pythonVersion": "default", - "pyblishVersion": "default", - "endpointVersion": "default", - - # Plugin - "doc": "default", - "path": "default", +defaults = { + "common": { + "id": "default", + "name": "default", + "isProcessing": False, + "families": list(), + "familiesConcatenated": "", + "isToggled": True, + "hasError": False, + "actionHasError": False, + "actionPending": True, + "succeeded": False, + "processed": False, + "currentProgress": 0, + "duration": 0, # Time (ms) to process pair + "finishedAt": 0, # Time (s) when finished + "amountPassed": 0, # Number of plug-ins/instances passed + "amountFailed": 0, # Number of plug-ins/instances failed + }, + "plugin": { + "doc": "", + "order": None, + "hasRepair": False, + "optional": True, + "hasCompatible": True, + "hosts": list(), + "type": "unknown", + "module": "unknown", + "compatibleInstances": list(), + "contextEnabled": False, + "instanceEnabled": False, + "pre11": True, + "verb": "unknown", + "actions": list(), + "actionsIconVisible": False, + "path": "", + "__instanceEnabled__": False + }, + "instance": { + "optional": True, + "family": None, + "niceName": "default", + "compatiblePlugins": list(), + }, + "result": { + "type": "default", + "filter": "default", + "message": "default", + + # Temporary metadata: "default", until treemodel + "instance": "default", + "plugin": "default", + + # LogRecord + "threadName": "default", + "name": "default", + "filename": "default", + "pathname": "default", + "lineno": 0, + "msg": "default", + "msecs": 0.0, + + # Exception + "fname": "default", + "line_number": 0, + "func": "default", + "exc": "default", + + # Context + "port": 0, + "host": "default", + "user": "default", + "connectTime": "default", + "pythonVersion": "default", + "pyblishVersion": "default", + "endpointVersion": "default", + + # Plugin + "doc": "default", + "path": "default", + } } @@ -265,7 +251,7 @@ def reset(self): def ItemIterator(items): for i in items: - if i.id == "Context": + if i.name == "Context": continue if not i.isToggled: @@ -283,13 +269,43 @@ def __init__(self, *args, **kwargs): self.plugins = util.ItemList(key="id") self.instances = util.ItemList(key="id") + def reorder(self, context): + # Reorder instances in support of "cooperative collection" + self.beginResetModel() + + items = dict() + for instance in self.instances: + items[instance.id] = instance + self.items.remove(instance) + + # TODO: Clean this up. Instances are cached for + # brevity but this is where we are forced to fight it. + self.instances[:] = [] + self.items.append(items[context.id]) + self.instances.append(items[context.id]) + + for instance in context: + self.items.append(items[instance.id]) + self.instances.append(items[instance.id]) + + self.endResetModel() + @QtCore.pyqtSlot(QtCore.QVariant) def add_plugin(self, plugin): + """Append `plugin` to model + + Arguments: + plugin (dict): Serialised plug-in from pyblish-rpc + + Schema: + plugin.json + + """ + item = {} - item.update(item_defaults) - item.update(plugin_defaults) + item.update(defaults["common"]) + item.update(defaults["plugin"]) - plugin = plugin.to_json() for member in ["pre11", "name", "label", @@ -341,37 +357,56 @@ def add_plugin(self, plugin): @QtCore.pyqtSlot(QtCore.QVariant) def add_instance(self, instance): - instance_json = instance.to_json() - item = {} - item.update(item_defaults) - item.update(instance_defaults) - item.update(instance_json["data"]) - item.update(instance_json) + """Append `instance` to model + + Arguments: + instance (dict): Serialised instance + + Schema: + instance.json + + """ + + assert isinstance(instance, dict) + + item = defaults["common"].copy() + item.update(defaults["instance"]) + + item.update(instance["data"]) + item.update(instance) - item["name"] = instance.data.get("name") item["itemType"] = "instance" - item["isToggled"] = instance.data.get("publish", True) + item["isToggled"] = instance["data"].get("publish", True) item["hasCompatible"] = True # Visualised in Perspective - item["familiesConcatenated"] = instance.data.get("family", "") + item["familiesConcatenated"] = instance["data"].get("family", "") item["familiesConcatenated"] += ", ".join( - instance.data.get("families", [])) + instance["data"].get("families", [])) item = self.add_item(item) self.instances.append(item) @QtCore.pyqtSlot(QtCore.QVariant) def add_context(self, context, label=None): - item = {} - item.update(item_defaults) - item.update(instance_defaults) + """Append `context` to model + + Arguments: + context (dict): Serialised to add + + Schema: + context.json + + """ + + assert isinstance(context, dict) - name = context.data.get("label") or settings.ContextLabel + item = defaults["common"].copy() + item.update(defaults["instance"]) + item.update(context) - item["id"] = "Context" item["family"] = None - item["name"] = name + item["label"] = context["data"].get("label") or settings.ContextLabel item["itemType"] = "instance" item["isToggled"] = True item["optional"] = False @@ -487,7 +522,7 @@ class ResultModel(AbstractModel): added = QtCore.pyqtSignal() def add_item(self, item): - item_ = result_defaults.copy() + item_ = defaults["result"].copy() item_.update(item) try: @@ -496,8 +531,8 @@ def add_item(self, item): self.added.emit() def add_context(self, context): - item = result_defaults.copy() - item.update(context.to_json()["data"]) + item = defaults["result"].copy() + item.update(context["data"]) item.update({ "type": "context", "name": "Pyblish", @@ -526,30 +561,30 @@ def update_with_result(self, result): self.add_item(error) def parse_result(self, result): - plugin_id = result["plugin"]["id"] + plugin_name = result["plugin"]["name"] try: - instance_id = result["instance"]["id"] + instance_name = result["instance"]["name"] except: - instance_id = None + instance_name = None plugin_msg = { "type": "plugin", - "message": plugin_id, - "filter": plugin_id, + "message": plugin_name, + "filter": plugin_name, - "plugin": plugin_id, - "instance": instance_id + "plugin": plugin_name, + "instance": instance_name } instance_msg = { "type": "instance", - "message": instance_id or "Context", - "filter": instance_id, + "message": instance_name or "Context", + "filter": instance_name, "duration": result["duration"], - "plugin": plugin_id, - "instance": instance_id + "plugin": plugin_name, + "instance": instance_name } record_msgs = list() @@ -559,8 +594,8 @@ def parse_result(self, result): record["filter"] = record["message"] record["message"] = util.format_text(str(record["message"])) - record["plugin"] = plugin_id - record["instance"] = instance_id + record["plugin"] = plugin_name + record["instance"] = instance_name record_msgs.append(record) @@ -569,8 +604,8 @@ def parse_result(self, result): "message": "No error", "filter": "", - "plugin": plugin_id, - "instance": instance_id + "plugin": plugin_name, + "instance": instance_name } error_msg = None @@ -581,8 +616,8 @@ def parse_result(self, result): error["message"] = util.format_text(error["message"]) error["filter"] = error["message"] - error["plugin"] = plugin_id - error["instance"] = instance_id + error["plugin"] = plugin_name + error["instance"] = instance_name error_msg = error @@ -759,40 +794,3 @@ def filterAcceptsRow(self, source_row, source_parent): @QtCore.pyqtSlot(result=int) def rowCount(self, parent=QtCore.QModelIndex()): return super(ProxyModel, self).rowCount(parent) - - -class InstanceProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(InstanceProxy, self).__init__(*args, **kwargs) - self.add_inclusion("itemType", "instance") - - -class PluginProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(PluginProxy, self).__init__(*args, **kwargs) - self.add_inclusion("itemType", "plugin") - self.add_exclusion("hasCompatible", False) - - -class ResultProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(ResultProxy, self).__init__(*args, **kwargs) - self.add_exclusion("levelname", "DEBUG") - self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - - -class RecordProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(RecordProxy, self).__init__(*args, **kwargs) - self.add_inclusion("type", "record") - - -class ErrorProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(ErrorProxy, self).__init__(*args, **kwargs) - self.add_inclusion("type", "error") - - -class GadgetProxy(ProxyModel): - def __init__(self, *args, **kwargs): - super(GadgetProxy, self).__init__(*args, **kwargs) diff --git a/pyblish_qml/qml/Pyblish/ScrollHint.qml b/pyblish_qml/qml/Pyblish/ScrollHint.qml new file mode 100644 index 0000000..604281a --- /dev/null +++ b/pyblish_qml/qml/Pyblish/ScrollHint.qml @@ -0,0 +1,89 @@ +import QtQuick 2.0 + + +Item { + property Flickable flickable + + visible: flickable.visibleArea.heightRatio < 1.0 + + SequentialAnimation on opacity{ + loops: Animation.Infinite + + PauseAnimation { duration: 500 } + + NumberAnimation { + duration: 500 + from: 1 + to: 0.5 + easing.type: Easing.InOutCubic + } + NumberAnimation { + duration: 500 + from: 0.5 + to: 1 + easing.type: Easing.InOutCubic + } + } + + Label { + text: "Scroll" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + } + + AwesomeIcon { + id: arrow + + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + name: "caret-down" + size: 20 + opacity: 0 + + SequentialAnimation { + running: true + loops: Animation.Infinite + + ParallelAnimation { + NumberAnimation { + target: arrow + property: "anchors.bottomMargin" + from: -30 + to: 0 + duration: 0 + } + NumberAnimation { + target: arrow + property: "opacity" + from: 0 + to: 1 + duration: 200 + } + } + + PauseAnimation { duration: 1000 } + + ParallelAnimation { + NumberAnimation { + target: arrow + property: "anchors.bottomMargin" + from: 0 + to: -30 + easing.type: Easing.InQuint + duration: 1000 + } + NumberAnimation { + target: arrow + property: "opacity" + from: 1 + to: 0 + easing.type: Easing.InQuint + duration: 1000 + } + } + } + } +} diff --git a/pyblish_qml/qml/Pyblish/qmldir b/pyblish_qml/qml/Pyblish/qmldir index 06c265b..49d4c8f 100644 --- a/pyblish_qml/qml/Pyblish/qmldir +++ b/pyblish_qml/qml/Pyblish/qmldir @@ -18,6 +18,7 @@ ProgressBar 0.1 ProgressBar.qml SpinBox 0.1 SpinBox.qml Spacer 0.1 Spacer.qml Scrollbar 0.1 Scrollbar.qml +ScrollHint 0.1 ScrollHint.qml View 0.1 View.qml TabBar 0.1 TabBar.qml TabView 0.1 TabView.qml diff --git a/pyblish_qml/qml/app.qml b/pyblish_qml/qml/app.qml index 6f35a61..464401b 100644 --- a/pyblish_qml/qml/app.qml +++ b/pyblish_qml/qml/app.qml @@ -40,6 +40,9 @@ StackView { } initialItem: Overview { + width: stack.width + height: stack.height + onInstanceEntered: setup(app.instanceProxy.item(index)) onPluginEntered: setup(app.pluginProxy.item(index)) } diff --git a/pyblish_qml/qml/tests/tst_NewModel.py b/pyblish_qml/qml/tests/tst_NewModel.py new file mode 100644 index 0000000..930e915 --- /dev/null +++ b/pyblish_qml/qml/tests/tst_NewModel.py @@ -0,0 +1,93 @@ +import sys + +from PyQt5 import QtCore, QtGui, QtQuick + + +class Model(QtCore.QAbstractListModel): + def __init__(self, schema, parent=None): + super(Model, self).__init__(parent) + + # Each item is a dictionary of key/value pairs + self.items = list() + + # QML requires a model to define upfront + # exactly which roles it can supply. I refer + # to this as the models "schema". + self.schema = schema + + def append(self, item): + """Append item to end of model""" + self.beginInsertRows(QtCore.QModelIndex(), + self.rowCount(), + self.rowCount()) + + self.items.append(item) + self.endInsertRows() + + def data(self, index, role): + """Return value of item[`index`] of `role`""" + key = self.schema[role] + return self.items[index.row()].get(key) + + def setData(self, index, value, role): + """Set item[`index`] of `role` to `value`""" + key = self.schema[role] + self.items[index.row()][key] = value + self.dataChanged.emit(index, index) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self.items) + + def roleNames(self): + """Role names are used by QML to map key to role""" + return dict(enumerate(self.schema)) + + +app = QtGui.QGuiApplication(sys.argv) + +view = QtQuick.QQuickView() + +# These are the available keys per item from within QML. +schema = [ + "kLabel", + "kColor", +] + +model = Model(schema) + +items = [ + { + "kLabel": "First Item", + "kColor": "white", + }, + { + "kLabel": "Second Item", + "kColor": "white", + } +] + +for item in items: + model.append(item) + +engine = view.engine() +context = engine.rootContext() +context.setContextProperty("pyModel", model) + +view.setSource(QtCore.QUrl("tst_NewModel.qml")) +view.setResizeMode(view.SizeRootObjectToView) +view.show() + +# Appending to the model +QtCore.QTimer.singleShot(2000, lambda: model.append({ + "kLabel": "Third Item, added after 2 seconds", + "kColor": "yellow" +})) + +# Modifying an item in the model +QtCore.QTimer.singleShot(4000, lambda: model.setData( + model.createIndex(1, 0), # 1th item, 0th column + "Changed after 4 seconds", + schema.index("kLabel"), +)) + +app.exec_() diff --git a/pyblish_qml/qml/tests/tst_NewModel.qml b/pyblish_qml/qml/tests/tst_NewModel.qml new file mode 100644 index 0000000..fc0d2b6 --- /dev/null +++ b/pyblish_qml/qml/tests/tst_NewModel.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + + +Rectangle { + width: 300 + height: 300 + color: "brown" + + ListView { + clip: true + + model: pyModel + + anchors.fill: parent + anchors.margins: 5 + + delegate: Text { + text: kLabel + color: kColor + } + } +} diff --git a/tests/test_control.py b/tests/test_control.py index 20d3bf0..8750253 100644 --- a/tests/test_control.py +++ b/tests/test_control.py @@ -95,13 +95,14 @@ def validate(controller=None): def test_reset(): """Reset works""" - count = {"#": 0} + data = {"#": 0, "instances": list()} class MyCollector(pyblish.api.Collector): def process(self, context): instance = context.create_instance("MyInstance") instance.data["family"] = "myFamily" - count["#"] += 1 + data["#"] += 1 + data["instances"].append(instance) pyblish.api.register_plugin(MyCollector) @@ -110,8 +111,8 @@ def process(self, context): # At this point, the item-model is populated with # a number of instances. check_present("MyCollector", c.item_model) - check_present("MyInstance", c.item_model) - assert_equals(count["#"], 1) + check_present(data["instances"][0].name, c.item_model) + assert_equals(data["#"], 1) @with_setup(lib.clean) @@ -188,7 +189,7 @@ def process(self, instance): check_present("MyCollector", c.item_model) check_present("MyInstance", c.item_model) - c.item_model.plugins["MyValidator"].isToggled = False + c.item_model.plugins[MyValidator.id].isToggled = False publish(c) @@ -399,12 +400,15 @@ def process(self, context): def test_toggle_compatibility(): """toggle instance updates compatibility correctly""" + data = {"instance": None} + class Collector(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder def process(self, context): instance = context.create_instance("A") instance.data["family"] = "FamilyA" + data['instance'] = instance class Validate(pyblish.api.InstancePlugin): """A dummy validator""" @@ -430,7 +434,7 @@ def has_enabled_validator(c): return False - item = c.item_model.instances["A"] + item = c.item_model.instances[data['instance'].id] index = c.item_model.items.index(item) # Default state (enabled) @@ -480,9 +484,9 @@ def process(self, instance): c = reset() validate_fail_index = c.item_model.items.index( - c.item_model.plugins["ValidateFail"]) + c.item_model.plugins[ValidateFail.id]) validate_success_index = c.item_model.items.index( - c.item_model.plugins["ValidateSuccess"]) + c.item_model.plugins[ValidateSuccess.id]) validate_fail_actions = c.getPluginActions(validate_fail_index) @@ -495,7 +499,7 @@ def process(self, instance): assert len(validate_fail_actions) == 1, ( "ValidateFail should have had an action") - assert validate_fail_actions[0]["id"] == "ActionOnFailed", ( + assert validate_fail_actions[0]["id"] == ActionOnFailed.id, ( "ValidateFail had an unknown action") validate_success_actions = c.getPluginActions(validate_success_index)