Introduction

Today I am going to inaugurate a new serie of articles about QGIS forms. QGIS is perhaps the best GIS software that ease the most attributes edition. You can build nearly complete GIS applications just by triggering some parameters on the QGIS fields dialog (and with a little bit of code, of course). I've made a whole application with QGIS forms. On the GIS side, it was not so hard: 8 layers to edit with simple geometries. But on the attributes side, it was a real challenge. Some facts about the database and the application:

  • About one hundred attributes tables stored on Oracle Database.
  • Some layers can have about 80 form controls.
  • n,n relations.
  • Specific tables to store tree data (more on this later).
  • Complete custom form controls.
  • We needed subforms to handle photos.
  • Of course you need a true search engine on all of the attribute fields.

Building such an application was not very easy even if you already know QGIS well. At the beginning of the project I was not really sure that QGIS could match the trick... ...But at last, I just want to say that QGis works perfectly well on this application even with a lot of data loaded.

When I started to implement features into this QGis project, I often faced QGIS lack of features on the forms. I've made some bug or features report on http://hub.qgis.org. In this serie of articles, I will try to show you how to circumvent the different problems I faced.

About QGIS forms

Before diving into code, let's have some basic informations about QGIS forms...

Forms are built by QGIS in order to edit (non-geometric) attributes for an object on a defined layer. You can read more about the process in the official QGis documentation.

You need to understand how forms are working in QGIS. To my mind, there are three parts on this subject:

  • Data fields: the goal of the form is to edit the values of the attributes in the layer. Data fields are named with the attributes name of the layer.
  • GUI: This is the part that QGIS show to the user to edit alphanumerical data. You can choose between three types: auto-generated (QGIS build everything, for basic forms), drag'n drop designed (you build the form using tabs and groupboxes inside QGIS) or customised (you need to use QtCreator to build a .ui file that will be shown by QGIS). To make link between form controls and fields values, there is a trivial rule: form controls are named like the field they represent.
  • Python code: QGIS allows you to add Python logic to the GUI part of the form. Whenever the form opens, you can call a Python function. This function can access to GUI objects using Qt functions but you also have the full power of QGIS Python API.

For Data fields definition, you have nothing to do: QGIS will use the layer definition to name attributes. For GUI, you have to configure (per-layer) the method (auto-generated/drag'n drop/custom) on the Fields tab of the layer properties dialog. You can read this introduction article to have further details...

For Python code, you just have to name the Python file (module) that will be used and the function that will be launched. Read this reference article to dig a little bit more.

Show a clickable URL on the form

Introduction

In QGIS forms, you can specify that a field will store a path to a file. You just have to use "File Name" for the Edit Widget. There is no option for this type of widget:

Fields dialog with File Name Edit Widget for DOCUMENT field

When you create a new feature on the layer, you will see the following control:

Choose a file dialog

Push the "..." button and you open a file choosing dialog (like every Qt file dialog). When you have chosen the file, the path (/home/medspx/clint_eastwood.ods) is stored in the line edit (a QLineEdit) right to the field name (DOCUMENT). But what if you want to open the file and not just store the path ? You have to trick QGIS a little bit.

Build a custom form like this

First of all you have to use custom form. Otherwise, QGis is using a QLineEdit to show the file path. We need to use a QLabel as only QLabel are able to show a URL link. In your .ui file just add the following to your layout:

QtCreator UI file for URL

As you can see, we have a QLabel to name the field ("Document"). Then you have another QLabel which contains "No file selected". Name this QLabel with a dedicated name (the objectName property will be DOCUMENT_URL). This QLabel will make the job of link URL presentation.Be careful to have a field which is named like the objectName otherwise, QGIS will not save the data into the layer.

You have to check two properties for this QLabel:

  • openExternalLinks: checked (otherwise, you will not be able to open the file with a click).
  • textInteractionFlags: LinksAccessibleByMouse.

QLabel properties

Then we have the QPushButton which will be named DOCUMENT_URL_B. And that's all for the ui part. Now, it is time to dive into Python...

First, the code:

def manageURL(dialog, layerid=None, featureid=None):
    '''General function to manage URL in subforms'''
    # Manage URL buttons
    for child in [f for f in dialog.findChildren(QPushButton) if u"URL_B" in f.objectName()]:
        try:
            child.clicked.disconnect()
        except:
            pass
        child.clicked.connect(partial(chooseFile, dialog, child.objectName()[:-2]))

    # Make URL fields always clickable (even in non-edit mode)
    for child in [f for f in dialog.findChildren(QLabel) if f.objectName()[-3:] == u"URL"]:
        child.setEnabled(True)

def chooseFile(dialog, field):
    '''Open a dedicated file picker form'''
    lineEdit = dialog.findChild(QLabel, field)
    if lineEdit is None:
        QgsMessageLog.logMessage(u"chooseFile: There is no QLabel for field {0} !".format(field), "DBPAT", QgsMessageLog.INFO)

    filename = QtGui.QFileDialog.getOpenFileName(dialog, u'Choisir un fichier...')
    if filename is not None:
        # parse the result to make a link
        basename = os.path.basename(filename)
        link = u"<a href=\"file:///{0}\">{1}</a>".format(filename,basename)
        lineEdit.setText(link)

When you want to display links, you just have to use manageURL function to do the job.

This function will try to find all of the QPushButton that are named like "SOMETHING_URL_B". Those buttons will be disconnected from their previous "clicked" signal. Then, we will re-connect the "clicked" signal to our "chooseFile" function. This function will do the job of building the link from the file path. manageURL ends with another trick: if you are not in edit mode, QLabel with links are not clickable. So we have to manually enable every QLabel that are links (ends with "URL" in our case).

chooseFile function opens a file chooser dialog (a standard QFileDialog from Qt). If the file path is valid, we extract the filename (the basename, only the last part of the path) from the file path. And then we build a link which is a simple <a>. At last, we update the QLabel with the link content.

Here is a rendered thing of the link:

URL link to open the file

You can click on the link and it will open the file with the default operating system defined viewer.

Conclusion

This first article just demonstrate that QGIS is so wide open to external code that you can do nearly everything you want with the forms. Embedding PyQt4 and giving access to the full QGIS API from Python is very interesting because you can go further than standard QGIS forms without recompiling QGIS.

As you can see, once you have written a dedicated function to handle URL links for File controls, it is very easy to expand it to any layer you want: just correctly name a form control on your custom form file and add the correct path to the function.

In the following article, we will focus on Photo edit widget and we will try to use it in a custom form with a little bit of code as an extra...