Skip to content

Latest commit

 

History

History
582 lines (469 loc) · 18.4 KB

34_my_assess_func.md

File metadata and controls

582 lines (469 loc) · 18.4 KB

My Assessments Function

- Implement a caching mechanism for assessment data in a web app.
- Modify existing methods to use cached data to improve performance.
- Test the caching mechanism to ensure the data is retrieved correctly.
- Address issues with adding or updating assessments, ensuring the cache is refreshed appropriately.

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
:linenos:
:lineno-start: 7
:emphasize-lines: 3
# cached values
__user = None
__assessments = None
:class: notice
- **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.
:linenos:
:lineno-start: 35
:emphasize-lines: 1 - 10
def my_assessment():
  global __assessments

  if __assessments:
    print("Using cached assessments")
    return __assessments

  print("Accessing assessments from database")
  __assessments = anvil.server.call('get_assessment', user)
  return __assessments
:class: notice
- **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
:linenos:
:lineno-start: 1
:emphasize-lines: 8
from ._anvil_designer import HomeComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access
:class: notice
- **line 8** → gives **HomeComponent** access to the **data_access** methods
  1. Insert the highlighted code at the bottom of the __init__ method
:linenos:
:lineno-start: 11
:emphasize-lines: 6
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run before the form opens.
    self.repeating_panel_1.items = data_access.my_assessment()
:class: notice
- **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
:linenos:
:lineno-start: 46
:emphasize-lines: 1 - 7
def add_assessment(subject, details, start_date, due_date):
  global __assessments
  
  print("Writing assessment details to the database")
  anvil.server.call('add_assessment', subject, details, start_date, due_date)
  __assessments = None
  my_assessment()
:class: notice
- **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
:linenos:
:lineno-start: 31
:emphasize-lines: 17
  def button_add_click(self, **event_args):
    # validation
    if not self.text_box_subject.text:
      self.display_error("Subject name needed")
    elif not self.text_box_details.text:
      self.display_error("Assessment details needed")
    elif not self.date_picker_start.date:
      self.display_error("Start date needed")
    elif not self.date_picker_due.date:
      self.display_error("Due date needed")
    else:
      self.subject = self.text_box_subject.text
      self.details = self.text_box_details.text
      self.start = self.date_picker_start.date
      self.due = self.date_picker_due.date
      self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
      data_access.add_assessment(self.subject, self.details, self.start, self.due)
      self.reset_form()
:class: notice
- **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
:linenos:
:lineno-start: 54
:emphasize-lines: 1 - 14
def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
  global __assessments

  print("Updating assessment details on the database")
  anvil.server.call('update_assessment',
                    assessment_id,
                    subject,
                    details,
                    start_date,
                    due_date,
                    completed
                   )
  __assessments = None
  my_assessment()
:class: notice
- **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
:linenos:
:lineno-start: 1
:emphasize-lines: 9
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
:class: notice
- **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
:linenos:
:lineno-start: 35
:emphasize-lines: 3-9
  def button_save_click(self, **event_args):
    # write to server
    data_access.update_assessment(self.item.get_id(),
                                  self.text_box_subject.text,
                                  self.text_box_details.text,
                                  self.date_picker_start.date,
                                  self.date_picker_due.date,
                                  self.check_box_completed.checked
                                 )

    # update display
    self.label_subject.text = self.text_box_subject.text
    self.label_details.text = self.text_box_details.text
    self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
    self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
    self.switch_components()
:class: notice
- **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

:linenos:
import anvil.server
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

# cached values
__user = None
__assessments = None

def the_user():
  global __user

  if __user:
    print("Using cached user")
    return __user

  print("Accessing user from database")
  __user = anvil.users.get_user()
  return __user

def logout():
  global __user
  __user = None
  anvil.users.logout()

def update_user(first_name, last_name):
  global __user
  
  print("Writing user details to database")
  anvil.server.call('update_user', first_name, last_name)
  __user = None
  __user = the_user()

def my_assessment():
  global __assessments
  
  if __assessments:
    print("Using cached assessments")
    return __assessments

  print("Accessing assessments from database")
  __assessments = anvil.server.call('get_assessment')
  return __assessments

def add_assessment(subject, details, start_date, due_date):
  global __assessments
  
  print("Writing assessment details to the database")
  anvil.server.call('add_assessment', subject, details, start_date, due_date)
  __assessments = None
  my_assessment()

def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
  global __assessments

  print("Updating assessment details on the database")
  anvil.server.call('update_assessment',
                    assessment_id,
                    subject,
                    details,
                    start_date,
                    due_date,
                    completed
                   )
  __assessments = None
  my_assessment()

Final HomeComponent

:linenos:
from ._anvil_designer import HomeComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access

class HomeComponent(HomeComponentTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run before the form opens.
    self.repeating_panel_1.items = data_access.my_assessment()

Final AddComponent

:linenos:
from ._anvil_designer import AddComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access


class AddComponent(AddComponentTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    self.subject = ""
    self.details = ""
    self.start = None
    self.due = None

    # Any code you write here will run before the form opens.
    self.label_message.visible = False
    if data_access.the_user():
      self.card_details.visible = True
      self.card_error.visible = False
      self.button_add.visible = True
    else:
      self.card_details.visible = False
      self.card_error.visible = True
      self.button_add.visible = False

  def button_add_click(self, **event_args):
    # validation
    if not self.text_box_subject.text:
      self.display_error("Subject name needed")
    elif not self.text_box_details.text:
      self.display_error("Assessment details needed")
    elif not self.date_picker_start.date:
      self.display_error("Start date needed")
    elif not self.date_picker_due.date:
      self.display_error("Due date needed")
    else:
      self.subject = self.text_box_subject.text
      self.details = self.text_box_details.text
      self.start = self.date_picker_start.date
      self.due = self.date_picker_due.date
      self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
      data_access.add_assessment(self.subject, self.details, self.start, self.due)
      self.reset_form()

  def display_error(self, message):
    self.label_message.visible = True
    self.label_message.foreground = "#ff0000"
    self.label_message.icon = "fa:exclamation-triangle"
    self.label_message.bold = True
    self.label_message.text = message

  def display_save(self, message):
    self.label_message.visible = True
    self.label_message.foreground = "#000000"
    self.label_message.icon = "fa:save"
    self.label_message.bold = False
    self.label_message.text = message

  def reset_form(self):
    self.subject = ""
    self.details = ""
    self.start = None
    self.due = None
    self.text_box_subject.text = ""
    self.text_box_details.text = ""
    self.date_picker_start.date = None
    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

:linenos:
class AssessmentPanel(AssessmentPanelTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run before the form opens.
    self.check_box_completed.checked = self.item['completed']
    self.label_subject.text = self.item['subject']
    self.label_details.text = self.item['details']
    self.label_start.text = self.item['start_date'].strftime('%d/%m/%Y')
    self.label_due.text = self.item['due_date'].strftime('%d/%m/%Y')

  def check_box_completed_change(self, **event_args):
    new_value = self.check_box_completed.checked
    anvil.server.call('update_assessment_completed', self.item.get_id(),new_value)

  def button_edit_click(self, **event_args):
    self.text_box_subject.text = self.item["subject"]
    self.text_box_details.text = self.item["details"]
    self.date_picker_start.date = self.item["start_date"]
    self.date_picker_due.date = self.item["due_date"]
    self.switch_components()

  def button_save_click(self, **event_args):
    # write to server
    data_access.update_assessment(self.item.get_id(),
                                  self.text_box_subject.text,
                                  self.text_box_details.text,
                                  self.date_picker_start.date,
                                  self.date_picker_due.date,
                                  self.check_box_completed.checked
                                 )

    # update display
    self.label_subject.text = self.text_box_subject.text
    self.label_details.text = self.text_box_details.text
    self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
    self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
    self.switch_components()
  
  def switch_components(self):
    # display elements
    self.label_subject.visible = not self.label_subject.visible
    self.label_details.visible = not self.label_details.visible
    self.label_start.visible = not self.label_start.visible
    self.label_due.visible = not self.label_due.visible
    self.button_edit.visible = not self.button_edit.visible
    
    # edit elements
    self.text_box_subject.visible = not self.text_box_subject.visible
    self.text_box_details.visible = not self.text_box_details.visible
    self.date_picker_start.visible = not self.date_picker_start.visible
    self.date_picker_due.visible = not self.date_picker_due.visible
    self.button_save.visible = not self.button_save.visible