roam-qgis / Roam

Simple data collection built using QGIS.
http://roam-docs.readthedocs.org/en/latest/
GNU General Public License v2.0
166 stars 60 forks source link

Use of attribute($currentfeature,'name') in filters #414

Open TonyFMCMC opened 5 years ago

TonyFMCMC commented 5 years ago

Am I using the appropriate format for the following filter for a list in RoamConfig? This is from the form.config file.

- _id: f738cb26-b726-44cf-81b1-ecdc22b331f4
  config:
    allownull: true
    layer:
      filter: format_date(  "Task date" ,'yyyyMMdd')=format_date( attribute(  $currentfeature
        ,'Datestamp'),'yyyyMMdd')
      key: team-project
      layer: Allocations
      value: id
    orderbyvalue: false
  default: ''
  default_events:
  - capture
  field: teamallocationid
  hidden: false
  name: select team
  read-only-rules:
  - never
  rememberlastvalue: false
  required: true
  widget: List

I am trying to filter the list based on an earlier entered field on the form ('Datestamp'), but when the form is run in Roam, there are consistently no options in the list apart from 'No selection'. The expression attribute( $currentfeature,'Datestamp') works fine as an expression in a text field but I'm not sure what it is doing in the filter. The Allocations table is an SQL server view (non-spatial), and the table on which the form is built is an SQL server spatial table. Or is there a better way to do this?

Roam v2.6

she-weeds commented 5 years ago

I have a similar issue. Only been able to get the list filter to work if the parent field is already pre-populated - I can't get ROAM to detect entered values from other fields except in the Events tab (but that will only let you set values, rather than filter lists).

TonyFMCMC commented 5 years ago

Another user has informed me that he doesn't think Roam supports cascading list-boxes like I have described and that he has raised this limitation with the developers too. It seems to me to be the attribute() function failing.

TonyFMCMC commented 5 years ago

However, editing the form.py file to include a handler for (in this case) when the date changes, and in the handler changing a filter on a list effectively cascades the information. Just not through Roam Config Manager.

    def uisetup(self):
        """
        Called when the UI is fully constructed.  You should connect any signals here.
        """
        self.boundwidgets['Datestamp'].valuechanged.connect(self.changed_date_handler)

    def changed_date_handler(self,value):
        """
        When the date changes, limit the allocations available to those on that date
        """        
        myconfig=self.boundwidgets['TeamAllocationID'].config
        myconfig['layer']['filter'] =""" format_date(  "Task date" , 'yyyy-MM-dd')='""" +value[:10]+"'"
        self.boundwidgets['TeamAllocationID'].setconfig(myconfig)
        self.boundwidgets['TeamAllocationID'].updatefromconfig()
she-weeds commented 5 years ago

@TonyFMCMC , you are an absolute legend! Thank you so much for sharing that snippet, it works like a charm.

To anyone else reading who is as unfamiliar with Python as me, the filter allows for your whole range of standard QGIS 2.18 expressions as long as they're escaped appropriately. So you can use ILIKE or regexp_match or whatever to filter by partial matches e.g.

myconfig['layer']['filter'] =""" "field" ILIKE '%""" +value+"""%'"""

she-weeds commented 5 years ago

One issue I am running into with the suggested solution is that selected options don't "stick" when you edit the record in ROAM (you are required to re-select from the filtered list). I don't know enough to figure out what extra line(s) are required to overcome this.

TonyFMCMC commented 5 years ago

I had such a problem when the type of the field in the database and the result of the key field did not match exactly. But I am not sure in your case, sorry!

she-weeds commented 5 years ago

I found out why the selection from the cascaded list wasn't "sticking" - it was because I had used a MultiList control. It works using the List control (in both 2.6 and 2.7.1). It appears that when you go back to edit a feature, the previously saved value doesn't match up to the cascaded list options in the way it normally does with a MultiList control.

TonyFMCMC commented 5 years ago

Glad you found the solution to that! And Happy New Year!

TonyFMCMC commented 5 years ago

Hmm, Now I think I understand what you meant, although I don't understand why changing to a list control worked for you (it didn't for me). After creating a polygon, saving it as a draft, then selecting the polygon and editing it, the value in the TeamAllocationID field in my form is blank ( the value hadn't "stuck"), even though in my database the field was still populated. It seems that when the change handler is executed it prevents loading of the field value into the widget, so I had to add a class variable and use the load() form method to temporarily store the value from the database and then restore it into the widget value in the loaded() form method. Roam version 2.6.

    def __init__(self, *args, **kwargs):
        super(Form, self).__init__(*args, **kwargs)
        self.TeamAllocationIDtemp=None      #This is necessary it seems because resetting the filter 
                                            #in self.changed_date_handler seems to prevent loading the value into the widget

    def uisetup(self):
        """
        Called when the UI is fully constructed.  You should connect any signals here.
        """
        self.boundwidgets['Datestamp'].valuechanged.connect(self.changed_date_handler)

    def load(self, feature, layers, values):
        """
        Called before the form is loaded. And before uisetup(). This method can be used to do pre checks   
        and halt the loading of the form if needed.
        """
        self.TeamAllocationIDtemp=values['TeamAllocationID']      # Store the TeamAllocationID

    def featuresaved(self, feature, values):
        pass

    def deletefeature(self):
        return False

    def featuredeleted(self, feature):
        pass

    def loaded(self):
        """
        Called after the form is loaded into the UI.
        """
        self.boundwidgets['TeamAllocationID'].setvalue(str(self.TeamAllocationIDtemp))

    def accept(self):
        return True

    def changed_date_handler(self,value):
        """
        When the date changes, limit the allocations available to those on that date
        """
        myconfig=self.boundwidgets['TeamAllocationID'].config
        myconfig['layer']['filter'] =""" format_date(  "Task date" , 'yyyy-MM-dd')='""" +value[:10]+"'"
        self.boundwidgets['TeamAllocationID'].setconfig(myconfig)
        self.boundwidgets['TeamAllocationID'].updatefromconfig()
she-weeds commented 5 years ago

Fantastic Tony, that solves the issue even with a MultiList control! This has made a notable difference to our reporting workflow. Just as I'd finalised the forms on my last day at work before heading off for a while... :-) I'll be sure to incorporate it in the next version. This is really excellent. Thank you for sharing your solution with the community and happy new year to you too!

On Wed, 16 Jan 2019 at 13:50, TonyFMCMC notifications@github.com wrote:

Hmm, Now I think I understand what you meant, although I don't understand why it worked for you. After creating a polygon, saving it as a draft, then selecting it and editing it the value in the TeamAllocationID field in the form is blank (hadn't "stuck"), even though in the database the field was still populated. It seems that when the change handler is executed it prevents loading of the field value into the widget, so I had to add a class variable to temporarily store the value from the database and then restore it into the widget value in the loaded() form method.

def __init__(self, *args, **kwargs):
    super(Form, self).__init__(*args, **kwargs)
    self.TeamAllocationIDtemp=None      #This is necessary it seems because resetting the filter
                                        #in self.changed_date_handler seems to prevent loading the value into the widget

def uisetup(self):
    """
    Called when the UI is fully constructed.  You should connect any signals here.
    """
    self.boundwidgets['Datestamp'].valuechanged.connect(self.changed_date_handler)

def load(self, feature, layers, values):
    """
    Called before the form is loaded. And before uisetup(). This method can be used to do pre checks
    and halt the loading of the form if needed.
    """
    self.TeamAllocationIDtemp=values['TeamAllocationID']      # Store the TeamAllocationID

def featuresaved(self, feature, values):
    pass

def deletefeature(self):
    return False

def featuredeleted(self, feature):
    pass

def loaded(self):
    """
    Called after the form is loaded into the UI.
    """
    self.boundwidgets['TeamAllocationID'].setvalue(str(self.TeamAllocationIDtemp))

def accept(self):
    return True

def changed_date_handler(self,value):
    """
    When the date changes, limit the allocations available to those on that date
    """
    myconfig=self.boundwidgets['TeamAllocationID'].config
    myconfig['layer']['filter'] =""" format_date(  "Task date" , 'yyyy-MM-dd')='""" +value[:10]+"'"
    self.boundwidgets['TeamAllocationID'].setconfig(myconfig)
    self.boundwidgets['TeamAllocationID'].updatefromconfig()```

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/DMS-Aus/Roam/issues/414#issuecomment-454632134, or mute the thread https://github.com/notifications/unsubscribe-auth/AoB9ELZpAjJrRmz4zT5EFl1kShoIPkp8ks5vDpOQgaJpZM4VqB62 .