1
  • 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.

asked Jan 10, 2024 at 9:33
4
  • I noticed that the both the id(feature) is always different when I look at it in either function which suggests the object got copied (by the partial() call?). The same is true for feature.id(). As a hack I tried using a global instead of passing the feature to on_ok(). Commented Jan 10, 2024 at 9:56
  • The QGIS attribute form (QgsAttributeForm) is made in such a way that only the fields that are included in the form are editable and this behavior doesn't look like it can be modified. Imo your best solution would be to attach to the signals of the QgsVectorLayer to update the value independently of the form. Which in a way is what you do when you change the value of the "int" field at the closing of the attribute form. Commented Jan 11, 2024 at 7:31
  • I don't understand. How does the form prevent other fields to be edited? Am I not side-stepping the form by using a method of the layer? Which signal would you suggest and how would I make sure to pass a value from the form (the 12345 in the example) to the layer/feature? Commented Jan 11, 2024 at 9:03
  • I mean that you can't use form functions directly from the "dialog" object if the field is removed from the form. So you have to edit directly the layer independently from any form "actions". the way @Matt does it is good (I didn't know about that use of editBuffer()) but it needs to handle update of feature too (probably depending if the id is >0 or not). Commented Jan 11, 2024 at 10:57

2 Answers 2

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))
answered Jan 11, 2024 at 10:37
9
  • 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). Commented Jan 11, 2024 at 10:51
  • 1
    Good 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 an attributeValueChanged signal which will emit the feature id of the edited feature. Commented 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? Commented 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. Commented Jan 11, 2024 at 12:07
  • 1
    Indeed, 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? Commented Jan 11, 2024 at 13:59
1

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.

answered Feb 15, 2024 at 9:55

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.