- I have a layer with two attributes: "text" (string) and "int" (integer).
- I used drag'n'drop form design to have a form with just the "text" attribute in it.
- I added Python Init Code:
from functools import partial
from PyQt5.QtWidgets import QPushButton
def on_ok(layer, feature):
field_idx = layer.fields().indexOf("int")
attribute_changed = layer.changeAttributeValue(feature.id(), field_idx, 12345)
if not attribute_changed:
raise Exception("Attribute value could not be changed!")
def my_form_open(dialog, layer, feature):
ok_button = dialog.findChild(QPushButton)
ok_button.clicked.connect(partial(on_ok, layer, feature))
So the form does not show an input widget for the "int" field. But when pressing OK, the code should change the edited feature's "int" value to 12345.
This works if I edit existing features.
But if I create a new feature, the "int" value will be empty/NULL instead of 12345. Any pointers on how to properly update a new feature's attribute fields, that exist on a layer, but are not shown the user with the QGIS' widgets in the attribute form?
I have already tried
attribute_changed = feature.setAttribute(field_idx, 12345)
and
feature[attribute_name] = 12345
with no success either.
In my real scenario the value would be dynamic and defined elsewhere, the 12345
is just an example.
2 Answers 2
If you add a print statement in on_ok
to check the feature.id()
you will probably find that it is wildly different to what you expect.
I was able to get the correct feature id by querying the editBuffer
of the layer for the most recently added feature. While editing, features receive an incrementally decreasing negative feature id, so the most recent is the lowest value.
from PyQt5.QtWidgets import QMessageBox, QPushButton
from functools import partial
def on_ok(layer, feature):
field_idx = layer.fields().indexOf("int")
print(feature.id()) # <--- not what you expect
edit_buffer = layer.editBuffer()
added_feats = edit_buffer.addedFeatures()
# get the id of the most recently added feature in the edit buffer
last_added_feat_id = min(list(added_feats.keys()))
attribute_changed = layer.changeAttributeValue(last_added_feat_id, field_idx, 12345)
if not attribute_changed:
raise Exception("Attribute value could not be changed!")
def my_form_open(dialog, layer, feature):
ok_button = dialog.findChild(QPushButton)
ok_button.clicked.connect(partial(on_ok, layer, feature))
-
Nicely done for new features but
ValueError: min() arg is an empty sequence
on the update feature if the feature wasn't created in the edit block or would modify the wrong feature (change last added feature instead of the feature selected for update).Kalak– Kalak2024年01月11日 10:51:30 +00:00Commented Jan 11, 2024 at 10:51 -
1Good catch, but I will leave it like this for now. I have every faith that @bugmenot123 is skilled enough to add the relevant checks specific to their use case. The
editBuffer
object also has anattributeValueChanged
signal which will emit the feature id of the edited feature.Matt– Matt2024年01月11日 10:57:19 +00:00Commented Jan 11, 2024 at 10:57 -
Thank you! This would break as soon as a second feature was added before the form of the first one was closed though, right?bugmenot123– bugmenot1232024年01月11日 12:01:49 +00:00Commented Jan 11, 2024 at 12:01
-
I just tested and this doesn't seem to be the case. I added a few features (not closing the feature form between edits) and after confirming by clicking 'OK' on all open forms, every feature received
12345
in the "int" field. It would probably require a little more robust testing, however.Matt– Matt2024年01月11日 12:07:43 +00:00Commented Jan 11, 2024 at 12:07 -
1Indeed, I understood that from your question. But, as I have no idea how that should be calculated, I cannot say for sure whether my solution will work or fail. Are you able to test and provide feedback?Matt– Matt2024年01月11日 13:59:42 +00:00Commented Jan 11, 2024 at 13:59
A minimal solution, based on @Matt's great answer:
from functools import partial
from qgis.PyQt.QtWidgets import QPushButton, QSpinBox
def on_ok(layer, feature, spinbox):
value = spinbox.value()
field_idx = layer.fields().indexOf("int")
feature_id = feature.id()
if feature_id == -9223372036854775808:
# this feature was newly added in this "form session"
edit_buffer = layer.editBuffer()
added_feats = edit_buffer.addedFeatures()
# get the id of the most recently added feature in the edit buffer
feature_id = min(list(added_feats.keys()))
# otherwise we are looking at an existing feature, committed or not
attribute_changed = layer.changeAttributeValue(feature_id, field_idx, value)
if not attribute_changed:
raise Exception("Attribute value could not be changed!")
def my_form_open(dialog, layer, feature):
layout = dialog.layout()
spinbox = QSpinBox()
layout.insertWidget(0, spinbox)
ok_button = dialog.findChild(QPushButton)
ok_button.clicked.connect(partial(on_ok, layer, feature, spinbox))
Explanation: When a new feature is "about to be created", the form receives a blank feature with the ID -9223372036854775808
(-2**63). In that case we have to get the actual feature's ID from the edit buffer after it has been created, which happens when the user OKs the form.
Explore related questions
See similar questions with these tags.
id(feature)
is always different when I look at it in either function which suggests the object got copied (by thepartial()
call?). The same is true forfeature.id()
. As a hack I tried using a global instead of passing the feature toon_ok()
.12345
in the example) to the layer/feature?