My Assessments Function

Now that we have cached the user data, our web app is fasters, but there are still some places where accessing the database slows it down.

If we look at the assessment_service we will notice two functions which retrieve data from the database:

  • get_assessment

  • get_chart

This implies that there are two more sets of data that we can cache. In this tutorial we will look to cache the get_assessment data, and we will deal with the get_chart data next tutorial.

Planning

The impact of using the get_assessment method is not as significant as get_user. It is only used in the HomeComponent. Never-the-less, this will be called every time the user goes to the HomeComponent, so it will still have an impact.

We will use the same process that we used for the get_user method:

  1. Create a variable to store assessment data in the data_access module.

  2. Create a frontend method that caches the assessment data if it is not currently cached, and then returns the cached data.

  3. Replace calls to get_assessment with calls to the new frontend method.

Lets get to it.

Code

Create the caching method

First we need to make a new variable to cache the assessment data.

  1. Open the data_access module,

  2. At the top, under the cached values section add the highlighted code

7# cached values
8__user = None
9__assessments = None

Code explaination

  • line 9 → creates the private variable __assessments to store the assessment data

Now to create the method

  1. At the bottom of the data_access module add the following highlighted code.

35def my_assessment():
36  global __assessments
37
38  if __assessments:
39    print("Using cached assessments")
40    return __assessments
41
42  print("Accessing assessments from database")
43  __assessments = anvil.server.call('get_assessment', user)
44  return __assessments

Code explaination

  • line 35 → creates the my_assessment method

  • line 36 → allows the method to edit the value of __assessments

  • line 38 → checks if the assessment data is already cached

  • line 39 → informs the developer that cached data is being used

  • line 40 → returns the cached data and ends the method

  • line 42 → informs the developed that the database is being accessed

  • line 43 → retrieves the assessment data from the database and caches it

  • line 44 → returned the cached data

Replace calls to get_assessment

Now we need to replace the calls to get_assessment

  1. Open HomeComponent in Code mode

  2. In the import section, add the highlighted code

1from ._anvil_designer import HomeComponentTemplate
2from anvil import *
3import anvil.server
4import anvil.tables as tables
5import anvil.tables.query as q
6from anvil.tables import app_tables
7import anvil.users
8from .. import data_access

Code explaination

  • line 8 → gives HomeComponent access to the data_access methods

  1. Insert the highlighted code at the bottom of the __init__ method

11  def __init__(self, **properties):
12    # Set Form properties and Data Bindings.
13    self.init_components(**properties)
14
15    # Any code you write here will run before the form opens.
16    self.repeating_panel_1.items = data_access.my_assessment()

Code explaination

  • line 16 → uses our new my_assessment method to provide the data for the repeating panels.

Testing

Time to test your web site and see if our changes worked.

Testing:

  1. Launch your web app

  2. Wait for the Home page to load - this should take some time

  3. Go to the Account page - this should load instantly

  4. Go back to the Home page - this should load instantly

  5. Go to the Add page - this should load instantly

  6. Add a new assessment item

  7. Go back to the Home page

  8. Check if the assessment item is there - spoiler, it won’t be

testing

Fixing the add assessment problem

The problem with adding assessment, is similar to the one we had with changing user data. When we add a new assessment, it gets written to the database, but the cached assessment data does not change. We need to fix this.

  1. Open the data_access module

  2. Insert the highlighted code into the bottom of the module

46def add_assessment(subject, details, start_date, due_date):
47  global __assessments
48  
49  print("Writing assessment details to the database")
50  anvil.server.call('add_assessment', subject, details, start_date, due_date)
51  __assessments = None
52  my_assessment()

Code explaination

  • line 46 → creates the add_assessment method, which requires subject, details, start_date and due_date to be passed

  • line 47 → allows the method to edit the value of __assessments

  • line 49 → informs the developed that the database is being accessed

  • line 50 → writes the new assessment details to the database

  • line 51 → resets the __assessment value to None

  • line 52 → caches the updated assessment list

Now we need to replace the call to the database, with a call to our new method.

  1. Open AddComponent in Code mode

  2. Replace line 47 with the highlighted code below

31  def button_add_click(self, **event_args):
32    # validation
33    if not self.text_box_subject.text:
34      self.display_error("Subject name needed")
35    elif not self.text_box_details.text:
36      self.display_error("Assessment details needed")
37    elif not self.date_picker_start.date:
38      self.display_error("Start date needed")
39    elif not self.date_picker_due.date:
40      self.display_error("Due date needed")
41    else:
42      self.subject = self.text_box_subject.text
43      self.details = self.text_box_details.text
44      self.start = self.date_picker_start.date
45      self.due = self.date_picker_due.date
46      self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
47      data_access.add_assessment(self.subject, self.details, self.start, self.due)
48      self.reset_form()

Code explaination

  • line 47 → calls our new caching method and passes the required values

Second test

Lets check that this worked.

  1. Launch your website and navigate to the Add page.

  2. Add a new assessment

  3. Return to the Home page

  4. Check if your new assessment is shown

test 2

Good. But are there more places where our cache can cause problem? When else do we write data to the database? How about when we change assessment details?

  1. Launch your web app

  2. Edit the value of one of the assessments → good, it changes

  3. Navigate to the Add page

  4. Return to the Home → it’s the previous value

test 3

Again this is the result of changing the database values without updating the cache. So lets fix that too.

Fix update assessment problem

Just like before, we will create a method in the data_access module that writes to the database and then reloads the cache.

  1. Open the data_access module

  2. At the bottom of the module add the highlighted code

54def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
55  global __assessments
56
57  print("Updating assessment details on the database")
58  anvil.server.call('update_assessment',
59                    assessment_id,
60                    subject,
61                    details,
62                    start_date,
63                    due_date,
64                    completed
65                   )
66  __assessments = None
67  my_assessment()

Code explaination

  • line 54 → creates the update_assessment method with all the required data to be passed

  • line 55 → allows the method to change the __assessments value

  • line 57 → lets the developer know that the database is being accessed

  • line 58 - 65 → writes the changes to the database

  • line 66 → resets the __assessments value to None

  • line 67 → caches the assessments data

  1. Open the AssessmentPanel

  2. In the import section add the highlighted line

1from ._anvil_designer import AssessmentPanelTemplate
2from anvil import *
3import anvil.server
4import anvil.users
5import anvil.tables as tables
6import anvil.tables.query as q
7from anvil.tables import app_tables
8import datetime
9from ... import data_access

Code explaination

  • line 9 → gives HomeComponent access to the data_access methods

  1. In the button_save_click handler replace line 37 - 44 with the highlighted code below

35  def button_save_click(self, **event_args):
36    # write to server
37    data_access.update_assessment(self.item.get_id(),
38                                  self.text_box_subject.text,
39                                  self.text_box_details.text,
40                                  self.date_picker_start.date,
41                                  self.date_picker_due.date,
42                                  self.check_box_completed.checked
43                                 )
44
45    # update display
46    self.label_subject.text = self.text_box_subject.text
47    self.label_details.text = self.text_box_details.text
48    self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
49    self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
50    self.switch_components()

Code explaination

  • line 37-43 → uses our new caching method to make changes to the assessment data

Third test

Lets check our latest iteration of code

  1. Launch you website

  2. Change an assessment’s detail

  3. Navigate to another page

  4. Return to Home

  5. Check if your change is displayed.

test 4

It should all be working now.

Final code state

By the end of this tutorial your code should be the same as below:

Final data_access

 1import anvil.server
 2import anvil.users
 3import anvil.tables as tables
 4import anvil.tables.query as q
 5from anvil.tables import app_tables
 6
 7# cached values
 8__user = None
 9__assessments = None
10
11def the_user():
12  global __user
13
14  if __user:
15    print("Using cached user")
16    return __user
17
18  print("Accessing user from database")
19  __user = anvil.users.get_user()
20  return __user
21
22def logout():
23  global __user
24  __user = None
25  anvil.users.logout()
26
27def update_user(first_name, last_name):
28  global __user
29  
30  print("Writing user details to database")
31  anvil.server.call('update_user', first_name, last_name)
32  __user = None
33  __user = the_user()
34
35def my_assessment():
36  global __assessments
37  
38  if __assessments:
39    print("Using cached assessments")
40    return __assessments
41
42  print("Accessing assessments from database")
43  __assessments = anvil.server.call('get_assessment')
44  return __assessments
45
46def add_assessment(subject, details, start_date, due_date):
47  global __assessments
48  
49  print("Writing assessment details to the database")
50  anvil.server.call('add_assessment', subject, details, start_date, due_date)
51  __assessments = None
52  my_assessment()
53
54def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
55  global __assessments
56
57  print("Updating assessment details on the database")
58  anvil.server.call('update_assessment',
59                    assessment_id,
60                    subject,
61                    details,
62                    start_date,
63                    due_date,
64                    completed
65                   )
66  __assessments = None
67  my_assessment()

Final HomeComponent

 1from ._anvil_designer import HomeComponentTemplate
 2from anvil import *
 3import anvil.server
 4import anvil.tables as tables
 5import anvil.tables.query as q
 6from anvil.tables import app_tables
 7import anvil.users
 8from .. import data_access
 9
10class HomeComponent(HomeComponentTemplate):
11  def __init__(self, **properties):
12    # Set Form properties and Data Bindings.
13    self.init_components(**properties)
14
15    # Any code you write here will run before the form opens.
16    self.repeating_panel_1.items = data_access.my_assessment()

Final AddComponent

 1from ._anvil_designer import AddComponentTemplate
 2from anvil import *
 3import anvil.server
 4import anvil.tables as tables
 5import anvil.tables.query as q
 6from anvil.tables import app_tables
 7import anvil.users
 8from .. import data_access
 9
10
11class AddComponent(AddComponentTemplate):
12  def __init__(self, **properties):
13    # Set Form properties and Data Bindings.
14    self.init_components(**properties)
15    self.subject = ""
16    self.details = ""
17    self.start = None
18    self.due = None
19
20    # Any code you write here will run before the form opens.
21    self.label_message.visible = False
22    if data_access.the_user():
23      self.card_details.visible = True
24      self.card_error.visible = False
25      self.button_add.visible = True
26    else:
27      self.card_details.visible = False
28      self.card_error.visible = True
29      self.button_add.visible = False
30
31  def button_add_click(self, **event_args):
32    # validation
33    if not self.text_box_subject.text:
34      self.display_error("Subject name needed")
35    elif not self.text_box_details.text:
36      self.display_error("Assessment details needed")
37    elif not self.date_picker_start.date:
38      self.display_error("Start date needed")
39    elif not self.date_picker_due.date:
40      self.display_error("Due date needed")
41    else:
42      self.subject = self.text_box_subject.text
43      self.details = self.text_box_details.text
44      self.start = self.date_picker_start.date
45      self.due = self.date_picker_due.date
46      self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
47      data_access.add_assessment(self.subject, self.details, self.start, self.due)
48      self.reset_form()
49
50  def display_error(self, message):
51    self.label_message.visible = True
52    self.label_message.foreground = "#ff0000"
53    self.label_message.icon = "fa:exclamation-triangle"
54    self.label_message.bold = True
55    self.label_message.text = message
56
57  def display_save(self, message):
58    self.label_message.visible = True
59    self.label_message.foreground = "#000000"
60    self.label_message.icon = "fa:save"
61    self.label_message.bold = False
62    self.label_message.text = message
63
64  def reset_form(self):
65    self.subject = ""
66    self.details = ""
67    self.start = None
68    self.due = None
69    self.text_box_subject.text = ""
70    self.text_box_details.text = ""
71    self.date_picker_start.date = None
72    self.date_picker_due.date = None

Final AssessmentPanel

from ._anvil_designer import AssessmentPanelTemplate from anvil import * import anvil.server import anvil.users import anvil.tables as tables import anvil.tables.query as q from anvil.tables import app_tables import datetime from … import data_access

 1class AssessmentPanel(AssessmentPanelTemplate):
 2  def __init__(self, **properties):
 3    # Set Form properties and Data Bindings.
 4    self.init_components(**properties)
 5
 6    # Any code you write here will run before the form opens.
 7    self.check_box_completed.checked = self.item['completed']
 8    self.label_subject.text = self.item['subject']
 9    self.label_details.text = self.item['details']
10    self.label_start.text = self.item['start_date'].strftime('%d/%m/%Y')
11    self.label_due.text = self.item['due_date'].strftime('%d/%m/%Y')
12
13  def check_box_completed_change(self, **event_args):
14    new_value = self.check_box_completed.checked
15    anvil.server.call('update_assessment_completed', self.item.get_id(),new_value)
16
17  def button_edit_click(self, **event_args):
18    self.text_box_subject.text = self.item["subject"]
19    self.text_box_details.text = self.item["details"]
20    self.date_picker_start.date = self.item["start_date"]
21    self.date_picker_due.date = self.item["due_date"]
22    self.switch_components()
23
24  def button_save_click(self, **event_args):
25    # write to server
26    data_access.update_assessment(self.item.get_id(),
27                                  self.text_box_subject.text,
28                                  self.text_box_details.text,
29                                  self.date_picker_start.date,
30                                  self.date_picker_due.date,
31                                  self.check_box_completed.checked
32                                 )
33
34    # update display
35    self.label_subject.text = self.text_box_subject.text
36    self.label_details.text = self.text_box_details.text
37    self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
38    self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
39    self.switch_components()
40  
41  def switch_components(self):
42    # display elements
43    self.label_subject.visible = not self.label_subject.visible
44    self.label_details.visible = not self.label_details.visible
45    self.label_start.visible = not self.label_start.visible
46    self.label_due.visible = not self.label_due.visible
47    self.button_edit.visible = not self.button_edit.visible
48    
49    # edit elements
50    self.text_box_subject.visible = not self.text_box_subject.visible
51    self.text_box_details.visible = not self.text_box_details.visible
52    self.date_picker_start.visible = not self.date_picker_start.visible
53    self.date_picker_due.visible = not self.date_picker_due.visible
54    self.button_save.visible = not self.button_save.visible