Each participant choses his votes - than on screen shall be one the voted color.
How shall I code that?
What is the best way to save the votes (have a table with all 19 kpis as fields and count up OR just have 4 fields Green Lightgreen Orange Red and enter chosen as list)
What would be best for calculating afterwards (Average, Standard-Dev. etc.)
Thanks for your help.
Aaron
It would probably be easiest to use a repeating panel or data grid to display questions. This could be loaded from the data tables service.
A second form could then be shown with the averages selected answers once the participant has submitted their answers.
That really depends on much you want to be able to examine the data - my thoughts are always to save as much data so if you need it you have it. On this case I would have a data table with 19 columns to represent each question. I would use number columns to represent the choice ie 1 = green, 2 = yellow etc. This way averaging the data should be simple.
There is a good tutorial by @brooke to get you started which should have most of what you need in it.
Another disadvantage of using simple objects would be that you would keep repeating the description of each KPI.
Another advantage of using simple objects would be that you always repeat the description of each KPI, which will allow you to keep the record of old data with old descriptions.
Elaborating on Rick’s answer, you could add a repeating panel to the form and assign to self.repeating_panel.items a list containing the 19 KPIs.
Then edit the repeating panel’s template form so it looks just like the snapshot on your question: one label for the title, one for the description and 4 links. Each link can have icon set to fa:circle and the foreground set to the color you like. If you really need a number with a colored round background, then you will need to play with the css and create one role per color.
I haven’t played with it much, but the docs do say you can search by the contents of simple object columns. A trivial example is on the blog post announcing them: Simple Object Storage
Thank you very much Rick, Stefano and Jay.
Very inspiring and leading.
I will build both ways (per KPI a column AND Simple Object Storage) and experiment what works best for calculations (& skip the other later) - I will let you know.
Cheers Aaron
Now I need a counter in the form, how many rows in the repeating panels have been answered. I cannot figure out, where to count (guess in the repeating panel code) and to put that variable to the form and refresh it continously.
Best Aaron
Hi Stefano, Quick question: when arranging data into a simple object, does your chosen IDE have auto-completion for the fields? This would save some time, especially with 19 fields!
No autocompletion with simple objects, because the IDE doesn’t know what’s inside.
I often use simple objects to store data in the database, but I deserialize them into well designed classes as soon as I get them out of the database and never interact with them. I only interact with my objects.
I usually create one class (or hierarchy of classes) that takes care of the logic. In the class there is one function that (calls a server function and) gets the values from the database, then all the code uses objects, not dictionaries. My workflow is described here: [Wiki] Best practices: Test-driven development with Anvil
I would create something like this, one container class:
class KPIs(AttributeToKey):
def __init__():
items = anvil.server.call('get_kpis')
self.items = [
KPI.from_dict(item)
for item in items
]
def __getitem__(self, index):
return self.items[index]
@property
def counter(self):
# add properties to the collection
return sum(1 for item in self.items if item.has_answer)
And one KPI class:
class KPI(AttributeToKey):
@classmethod
def from_dict(self, d):
kpi = KPI()
kpi._dict = d
# I get some properties in the constructor
kpi.prop1 = d.get('prop1', default_prop1)
return kpi
@property
def prop2(self):
# I calculate other properties in the accessor (usually the ones that are more complex than this one)
return self.d['prop2']
With this setup, then you can do:
kpis = KPIs()
self.repeating_panel.items = kpis.items
print(kpis.counter) # this works with code completion
print(kpis['counter']) # no code completion, but it works with DataGrid databinding (which expects a dictionary like object)
print(kpis[0].prop1, kpis[0]['prop1'])
print(kpis[0].prop2, kpis[0]['prop2'])
Very cool and well done.
I store them in an own table with all data for localisation (i.e. description_en, description_de)
Question decorators:
Right now I only use @anvil.server.callable - why do you need @property?
I have defined kpi.prop1 = d.get('prop1', default_prop1) to show how a read-and-write property can be easily added.
I have decorated the prop2 function with the @property decorator to show that it is possible. You may want to use it for different reasons:
because you want the property to be read-only
because you need to calculate the value of the property every time it is accessed, rather than pre-calculate it and store it (for example the property Line.length could return the calculated distance between Line.start_point and Line.end_point so it’s correct even after the points have changed)
because the property is rarely used, calculating its value is expensive and you want to do it lazily, only when really required
because you want to cache it (calculate it once and store its value, so the second time it’s called you just return the calculated value)
because you want to define the @prop.setter to execute some code to invalidate the cache or to initialize other fields when the property value is changed
etc.
At the end of the day, your form sees a class with a bunch of properties. It doesn’t know if they are simple variables defined in some function like prop1 or they are fully fledged functions exposed as simple properties like prop2, but they just work. And if you use the workflow I describe in the test driven development post, they can be used both as properties and as dictionary values.