Loop wrong on Add Record. Getting 4 Records, should be 2

I’m trying to set up an add to database, there are 2 fields, and 2 input rows in this case, but I am getting 5 rows,

I know why I am getting it because I am looping through the function for each entry, but I am not sure how to fix it. Can anyone help

Here is my code:

def button_submit_traveller_details_click(self, **event_args):
    for c in self.column_panel_traveller_details.get_components():
          
          if type(c) is TextBox:  
            self.travname = (c.text.title())
          if type(c) is DatePicker:  
            self.travdob=c.date
          row = app_tables.travellers.add_row(TravellerName=self.travname,
                                              TravellerDateofBirth=self.travdob,
                                             )  
          print(self.travname, self.travdob)
            
    pass

We could try to find a solution for this use case, where there are many text boxes and date pickers, but I would like first to know if the current problem is caused by the approach used to create the form.

I’m almost sure the solution to this problem is creating the form in another way so the problem doesn’t exist.

Hi

here is the creation, once the user enters how many travellers there are, it creates the boxes

self.tb just gets the age if the user wants a quote
self.tb2 and self.tb3 are created (but will be hidden) until the user says they want to proceed with the quote, when we need a bit more detail about the travellers.

Here is the code

def text_box_travellers_lost_focus(self, **event_args):
    
    self.trav = int(self.text_box_travellers.text)
    self.trav = self.trav + 1 #extra day added to allow for 0 index. 
    self.cp.clear() #clears traveller inputs if the value of trav changes
    self.cp2.clear() #clears traveller inputs if the value of trav changes    
    i = 1
    for i in range(1, self.trav):
     #Creates Boxes for the Traveller Ages
      self.tb[i] = TextBox(type="number",foreground="#000",background="#fff",placeholder=f"  traveller {i} age")
      self.tb[i].role = "input"
      self.cp.add_component(self.tb[i], row=1, col_xs=1, width_xs=1)#this adds the traveller inputs back in
      #Creates Boxes for Traveller Details
      self.tb1[i] = Label(spacing_above="large", spacing_below="large", foreground="#fff",  background="#2b4c80", text=f"  Traveller {i} Details")
      self.tb1[i].role = "headerlabel"  
      self.cp2.add_component(self.tb1[i], row=2, col_xs=2, width_xs=1)#this adds the traveller name boxes
      self.tb2[i] = TextBox(type="text",foreground="#000",background="#fff",placeholder="Enter Full Name(firstname,lastname)")
      self.tb2[i].role = "input"  
      self.cp2.add_component(self.tb2[i])#this adds the box where the ages chosen will go
      self.tb3[i] = DatePicker(format="YYYY-MM-DD",min_date="1900-01-01",placeholder="Enter as YYYY-MM-DD")
      self.tb3[i].role = "input"
      self.cp2.add_component(self.tb3[i]) #this adds the traveller date of birth boxes

Here I am keeping the date pickers and text boxes in a list of dictionaries self.travellers. I add them when I create them, so they are readily reachable later.

I also removed the self. from variables that are local variables (don’t need to become instance members).

def text_box_travellers_lost_focus(self, **event_args):
    
    trav = int(self.text_box_travellers.text)
    self.cp.clear() #clears traveller inputs if the value of trav changes
    self.cp2.clear() #clears traveller inputs if the value of trav changes
    self.travellers = []
    for i in range(trav):
      #Creates Boxes for the Traveller Ages
      tb = TextBox(type="number",foreground="#000",background="#fff",placeholder=f"  traveller {i+1} age")
      tb.role = "input"
      self.cp.add_component(tb, row=1, col_xs=1, width_xs=1)#this adds the traveller inputs back in
      #Creates Boxes for Traveller Details
      lb = Label(spacing_above="large", spacing_below="large", foreground="#fff",  background="#2b4c80", text=f"  Traveller {i+1} Details")
      lb.role = "headerlabel"  
      self.cp2.add_component(lb, row=2, col_xs=2, width_xs=1)#this adds the traveller name boxes
      tb2 = TextBox(type="text",foreground="#000",background="#fff",placeholder="Enter Full Name(firstname,lastname)")
      tb2.role = "input"  
      self.cp2.add_component(tb2)#this adds the box where the ages chosen will go
      dp = DatePicker(format="YYYY-MM-DD",min_date="1900-01-01",placeholder="Enter as YYYY-MM-DD")
      dp.role = "input"
      self.cp2.add_component(dp) #this adds the traveller date of birth boxes```
      self.travellers.append({'tb': tb, 'tb2': tb2, 'dp': dp}

def button_submit_traveller_details_click(self, **event_args):
    for traveller in self.travellers:
          row = app_tables.travellers.add_row(TravellerName=traveller['tb2'].text,
                                              TravellerDateofBirth=traveller['dp'].date)  

EDIT

I very rarely create components from code.
In a case like this I would very likely create a form with the 4 components, then add the form from code. Yes, the form would be a component created from code, but would be much cleaner.

So the whole form creation loop would be:

def text_box_travellers_lost_focus(self, **event_args):
    self.cp.clear()
    for i in range(int(self.text_box_travellers.text)):
        self.travellers.add_component(TravellerForm(i + 1))

def button_submit_traveller_details_click(self, **event_args):
    for traveller_form in self.cp.get_components:
        row = app_tables.travellers.add_row(
            TravellerName=traveller_form.traveller_name.text,
            TravellerDateofBirth=traveller_form.date_picker.date)  

This will work if the TravellerForm has the following signature:

class TravellerForm(TravellerFormTemplate):
    def __init__(self, n_traveller, **properties):

and contains components named traveller_name and date_picker.

3 Likes

Thank you so much for doing that, its perfect!

RE: Your Edit:-yes @rickhurlbatt mentioned this yesterday on another thread, but I didnt know how to do it. Its certainly much cleaner than the essay in code I wrote. I I think I should change it really and make it look as Anvil intends things should be done. But thank you for providing both solutons as they are both helpful in expanding my python knowledge.

I edited my answer, then I noticed that you had already set it as solution. Please have a look at the bottom, it may help you.

1 Like

Hi Stefano,

A quick question. In my original code there are two sections of boxes added,
Initial Quote
self.tb - n number of boxes for Traveller Ages (which just gets the initial quote)
then if the customer proceeds, two further inputs:-
self.tb2 - which is the customer name
self.tb3 - which is the customer date of birth

I now have the second section working thank you with the changes you suggested, but I still kept my original code for the top self.tb box.

Even though that section is only one box, would you still apply your same process of creating those boxes with a component form?
just curious.

Your original code adds 4 components per traveller.

If the 4 components always sit next to each other, then I would put them in the same form. If they need to be visually separated, but they will always sit next to each other, the form will have a separator or something that does the separation job.

If each traveller has two groups of components and the position of each group needs to be independent from the previous in the UI, then I would still make two forms.

Obviously if we go down to one component making a form with one component, adding code in the form initialization to manage that one component and writing the code to instantiate the form, is a bit overkilling. But when tomorrow you will want to change how that form looks, it will be faster to do it from the IDE.

Other users will disagree with me. I have seen posts where they suggest to completely ditch the form editor in the IDE and add all the components from code.

There are good reasons to do it that way too. There is no right answer.

I think that for someone that is learning to use Anvil the best way is to use the form editor to make as much UI as possible and only use code to put it together when you really need it.

1 Like

Thanks yes I see what you mean.

Just one more thing, I just ran my button click and got this error

'method object is not iterable on this line

for traveller_form in self.cp2.get_components:

I did change the column panel to cp2 as this is what the boxes are being put in, not cp.

EDIT: I fixed it, just needed the () at the end of the line.

1 Like