How to populate sub form fields with a database select

Hi

Could someone please help me with accessing database fields in the Anvil Database. So I will have a Sub Form that will have one row in it with fields matching the table fields, then I want to do something like this

for
i in (mytable)
add_component(mysubform)
then I want to map the fields to the subform fields

my table is called cover and the fields are covername, covericon, coversums and covertooltip

Are there any examples that someone can point to on the docs that just give an example I can follow?

In short, what you want is a DataGrid

The link provides a good introduction to the DataGrid, what it consists of, and how to use it to display data from a table.

DataGrids are quite powerful, and can be used to display data from tables with just a few clicks and a single line of code.

1 Like

Hi

I did look at datagrids an you are right they are very easy, but want to display the data in my own format and the datagrid seems like it may be a little restrictive. I am actually looking at that though so will see how flexible it is, in terms of styling.

For example, one of the fields is actually a tooltip so I dont want to actually display that data in a column, I want to direct the data in that field to a styled tooltip on mouseover.

In php I would create a function with sql and then call the function. I would create the layout with divs (css) and bits of php to display the data where I want, thats what I’d like to do.

Data grids give you a default way of displaying the data, but you can completely change that if you want. When you double click on a row in the data grid in the designer, it takes you to edit the form that is used for each row. You can put other sorts of components into each column if you want, or you can have your data grid be one column and structure the row template however you want.

The data grid will still handle creating all the row templates and data binding will populate them.


So in theory could I do something like this?

Yes, although with that wireframe it actually looks a bit easier than I’d been imagining. It looks like you have one data grid for the primary cover, and another data grid for the secondary cover. Inside each data grid you have three columns, one boolean, one text, and one for the sum insured. That’s all very doable.

2 Likes

Hi, just checking I am on the right track with this layout. See image, so I created 2 single line summary ā€œsub Formsā€ These will be used as components in Quotation.

I’ve added a boolean field to my cover table and then created 2 server queries as follows:


#gets the results where there is a bespoke entered sum insured
@anvil.server.callable 
def set_entered_insured_sums():
  results = app.tables.cover.search(CoverBespoke==1)
  return results

#gets the results where there is not a bespoke entered sum insured
@anvil.server.callable 
def set_standard_insured_sums():
  results = app.tables.cover.search(CoverBespoke==0)
  return results

I created a tooltip field above each row on the sub form which I thought to code as a custom role with a CSS relative position so it will sit on top of the line above when the info button is focus it will initate an event to show the tooltip and then when lost-focus it will hide it again.

I want to create an instance of those sub forms for each of the boolean results

Q1. With those queries, do I structure as ==0 or would I say is False?
Q2. If I want to bind the result to - e.g self.cancellation which is on Quotation, what do I put in data binding? Do I put self.item[ā€˜self.cancellation’]

There are more parts to this, but how is it looking so far? Please feel free to shred it :slight_smile: if I am completely wrong. I wont be offended :slight_smile:
coversummary|690x251

The image isn’t showing for me, so I can’t commment on that. On the query, it depends on what type your CoverBespoke field is. If it’s a number, then ==0 is fine. If it’s a boolean, then ==False would be best (assuming that you initialize the field to False when you add the row).

That last assumption is because any field you don’t initialize has a value of None, and ==False will not match a value of None even though both are technically false in Python terms. Best to initialize boolean fields to False when you add the row.

That will certainly not work. self.item will be a row from your data tables, the only things you can use as a key are field names from that data table.

There are a variety of ways to get non-data table row values into your data bindings. It really depends on how you have everything setup. IF your data binding is not in a data grid, then you should be able to do self.cancellation, since self at that point is the form the data binding is in.

1 Like

Thanks for the reply. Its a boolean field so I will use ==False. I dont know why the image isnt loading so will try again here.

So, the easier one to get working first, will probably be the standard values as is just straight database rows, but anyway, you can see the idea.

I’m coming up with an error. CoverBespoke is not defined.

Here is my code. I want it to do the following

  1. Server Code, find all the rows in the table cover that match the statement CoverBespoke==True (or False)
  2. Client Code, create a component for each of the rows,
  3. Client Code, populate each of the component rows with a result that corresponds to the correct self.
  4. I’m not sure if the image source code format is right either but hadnt tested yet due to this error.

The line its getting stuck on is:-

` self.cover_std = tuple(anvil.server.call('get_standard_insured_sums'))`

Server Code:

#gets the results where there is a bespoke entered sum insured
@anvil.server.callable 
def get_bespoke_insured_sums():
  results = app_tables.cover.search(CoverBespoke==True)
  return results

#gets the results where there is not a bespoke entered sum insured
@anvil.server.callable 
def get_standard_insured_sums():
  results = app_tables.cover.search(CoverBespoke==False)
  return results

Client Code:

#Populate the Quote Summary Page for Standard Cover Items
    self.cover_std = tuple(anvil.server.call('get_standard_insured_sums'))
    self.tooltip_std.text = [str['CoverToolTip'] for ct in self.cover_std]
    self.coverimage_std.source = [('CoverIcon') for ci in self.cover_std]
    self.sums_insured_std.text = [str['CoverAmount'] for ca in self.cover_std]

#Populate the Quote Summary Page for Standard Cover Items
    self.cover_bsp = tuple(anvil.server.call('get_bespoke_insured_sums'))
    self.tooltip_bsp.text = [str['CoverToolTip'] for ct in self.cover_bsp]
    self.coverimage_bsp.source = [('CoverIcon') for ci in self.cover_bsp]
    self.sums_insured_bsp.text = [str['CoverAmount'] for ca in self.cover_bsp]

and here is the bit of code creating the components

      for i in self.cover_std:
         sc = StandardCover(i + 1)
         self.card_summary_std.add_component(sc) 
      for i in self.cover_bsp:
         bc = BespokeCover(i + 1)
         self.card_summary_bsp.add_component(bc)

What are the names of your columns in your cover data table? It sounds like there isn’t one called CoverBespoke.

1 Like

Here is a screenshot of the cover table.

Sorry, totally missed the obvious.

When doing a data table search, it’s a single equals sign, not two.

1 Like

Ahh thanks of course, rookie mistake! EDIT (I mean my rookie mistake, not yours :smiley:

As you can see from the deleted posts I am struggling a bit. So my apologies if you have read my replies then seen them gone. I was doing it all wrong.

I have now created a RowTemplate and then have bound the fields to the rows. I am still trying to figure out some of it. but finally feel I am moving forwards.

I now have the repeating panel partially working, but want to populate 3 respective fields

      self.repeating_panel_cxl.coversums_cxl.text = "Ā£ {:,}".format(self.totalcancellation)
      self.repeating_panel_sav.coversums_sav.text = "Ā£ {:,}".format(self.totalarticle)
      self.repeating_panel.coversums_bag.text = "Ā£ {:,}".format(self.totalbaggage)

I know this is incorrect as its not populating the fields, but wanted to know the correct path, from the data_grid > repeating_panel>label.text.

Hopefully this makes sense…when working with a data grid, you automatically get a repeating panel and a row template form. You then:

  1. Populate the data grid’s repeating panel.items property with a list of dictionaries (or a search iterator, which acts like a list of dictionaries). e.g.:
self.repeating_panel_cxl.items = some_result
  1. The data grid automatically goes through that list of dictionaries and creates an instance of a row template form for each dictionary, assigning that row template’s .item property to the dictionary

  2. Inside the row template you write code/data bindings to use self.item to populate individual fields, e.g.:

self.coversums_cxl.text = self.item['a_key_in_the_dictionary']

If what you are trying to use in the row template is not in the self.item dictionary, then how you approach that depends on what you’re trying to use. It might be a variable on the form that contains the data grid, it might be a value that can be calculated out of the repeating panel’s list of dictionaries (e.g. a sum), etc.

1 Like

Hi

so, if its not a key but is another field(self) that was created on Quotation do I put the

self.coversums_cxl.text = self.item['self.thathasthevalueIwant']

On the Quotation form code? And if so what would be the path from:-

the datagrid>repeating panel>field on the repeating panel

that I would code into the self ending with coversums_cxl.text (hope that makes sense) :slight_smile:

There is no path to get from the Quotation form to the repeating panel row template instance. It has to go the other way, from the repeating panel row template instance back to the Quotation form.

Assuming:

  1. You have self.totalcancellation on the Quotation form
  2. The Quotation form includes a data grid
  3. You want to display self.totalcancellation in each of the repeating panel’s row templates

That last seems a bit suspect, since it’d be the same value in each row in the data grid. But without seeing a clone link it’s hard to know exactly the hierarchy of your repeating panel, row templates, and various widgets, so I’ll assume that’s what you want to do.

You need to get Quotation’s self.cancellation into a place where the repeating panel’s row template can get to it. I’ll describe one easy way, but it has limitations:

  1. In Quotation code, do something like self.repeating_panel_cxl.totalcancellation = self.totalcancellation

  2. In the repeating panel row template, use self.parent.totalcancellation to get to that value

The limitation is that in repeating panel row templates, self.parent is not set early enough to be used in the init method or in data bindings. form_show is the earliest I typically can use self.parent.

You could also use that technique to pass a reference to the entire Quotation instance, so that the repeating panel row template could access any variable on Quotation.

Another alternative, if what you are passing into the repeating panel .items property is an actual list of dictionaries, you could add the totalcancellation value to each dictionary. Then in the row template you’d use self.item['totalcancellation'] to access the value. I don’t think that works if what you’re passing is a search iterator, though, since then it thinks you’re trying to set a column in the data table.

I’m sure other people have their own techniques they’ve come up with to pass info into repeating panel row templates.

1 Like

Hi, I guess I cheated on an easy way to do different results in one field. I set up 3 datagrids, each only have one row.

So, I just need to pass the field I want to populate on one side, and the result I want to populate it to.

At the moment I am getting this error
AttributeError: 'RepeatingPanel' object has no attribute 'coversums_cxl'

where my self is:

self.repeating_panel_cxl.coversums_cxl.text = "Ā£ {:,}".format(self.totalcancellation)

so I am assuming this bit is wrong. I have tested the other side and if I do a print statement on it, it does give me the correct value.
self.repeating_panel_cxl.coversums_cxl.text

Okay, given the situation where you know that your data grids only have one row, there is a very nasty hack to go from the Quotation form into the row template instance. Your repeating panel is a container of instances of row template forms. In your case there’s only the one instance in each repeating panel. You can use:

self.repeating_panel_cxl.get_components()[0]

To get that instance, and then access components on that instance, e.g.:

self.repeating_panel_cxl.get_components()[0].coversums_cxl.text

This only works because you only have a single row in each data grid. It also puts code that is populating row template forms outside of the row template, so later on you’ll have trouble working out where that’s being set. But it should work.

1 Like