Chapter 6:
Update existing articles

We can add and read articles in our app. Let’s now build the Update process, which will look like this:

  1. A popup appears when the user clicks the ’edit’ button
  2. The popup displays the content of the ‘ArticleEdit’ Form
  3. The user makes changes
  4. If the user clicks save, we update the article. Otherwise, we discard the article.
  5. We refresh the Homepage to reflect any updates

We’ll walk through these steps below.

Step 1: Display a popup when the user clicks the edit button

Go to Design View of your ‘ArticleView’ Form, and drop a Button into the page next to the category_label. Change its name to edit_article_button, and its role to ‘filled-button’. Add the pencil-square icon instead of text:

Adding an edit button to the ArticleView Form

We might want to resize the edit_article_button since its now pushing the category_label over. Select the ColumnPanel to display blue lines in between its components. You can drag these blue lines to resize the column widths.

Dragging the blue lines in between components to resize them

We want to display a popup when the user clicks the edit_article_button. Create an event handler for the edit button by scrolling to the bottom of the Properties Panel and clicking the blue arrows next to ‘click’. The following function will be added to the Form’s code:

  def edit_article_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    pass

Step 2: Set the content of the popup to the ‘ArticleEdit’ Form

We want to open a popup that allows the user to update an article. We already have the ‘ArticleEdit’ Form which has all the inputs we’ll need, so we’ll also use this Form for updating articles. (Re-using our hard work is great!)

To do this, we first need to import the ‘ArticleEdit’ inside our ‘ArticleView’ Form:

from ..ArticleEdit import ArticleEdit

Next, customise the alert to display the ‘ArticleEdit’ Form:

  def edit_article_button_click(self, **event_args):
    # Open an alert displaying the 'ArticleEdit' Form
    alert(
      content=ArticleEdit(),
      title="Update Article",
      large=True
    )

Step 3: The user makes their updates

This time, we’re updating an existing article, so instead of passing an empty dictionary to the ‘ArticleEdit’ form, we pass a copy of the article row from the Data Table that we want to edit.

We copy this row because we want to be able to discard the user’s changes if they click Cancel. If we were to use the original article from the Data Table, we would be editing this row directly, and we wouldn’t be able to discard any changes the user made in error.

You can read more about making it easy to cancel operations in database-backed apps.

Modify the edit_article_button_click function to pass in a copy of the article to be updated. Let’s also add ‘Save’ and ‘Cancel’ buttons:

  def edit_article_button_click(self, **event_args):
    # Create a copy of the existing article from the Data Table 
    article_copy = dict(self.item)
    # Open an alert displaying the 'ArticleEdit' Form
    # set the `self.item` property of the ArticleEdit Form to a copy of the article to be updated
    alert(
      content=ArticleEdit(item=article_copy),
      title="Update Article",
      large=True,
      buttons=[("Save", True), ("Cancel", False)]
    )

By passing in a copy of the existing article, the ArticleEdit’s self.item property becomes that copy of the article.

Our ‘ArticleEdit’ form already contains data bindings to self.item, so any updates the user makes will automatically write back to self.item, which is the copy of the article that we passed into alert(content=ArticleEdit(item=article_copy))

Step 4: Update the article in your Data Table, or discard it

We’ll use a Server Module to update an existing article in the Data Table.

We’re using a Server Module because code in Server Modules can be trusted, and we might want to add authentication or other logic to this update code. We recommend doing it this way for all database-backed apps.

Add this function to your Server Module:

@anvil.server.callable
def update_article(article, article_dict):
  # check that the article given is really a row in the ‘articles’ table
  if app_tables.articles.has_row(article):
    article_dict['updated'] = datetime.now()
    article.update(**article_dict)
  else:
    raise Exception("Article does not exist")

This function takes two arguments: the existing article, and the updated article.

We’ve added a security check, to first check that the article we were given is really a row in the ‘articles’ table. If we didn’t make this check, a malicious user could edit any row in any data table by passing it to this function!

This check must occur in a Server Module because server code can be trusted.

Need to validate your inputs in more detail – for example, to ensure that every article has a name? Check out our recommendations for validating inputs in CRUD apps.

Finally, we’ll call update_article from the client when we want to update a news article. Our ‘Save’ and ‘Cancel’ buttons return the values ‘True’ and ‘False’, respectively, when clicked. If we wrap our alert in an if statement, the code block will be executed if the user clicks ‘Save’.

Change your edit_article_button_click function to the following:

  def edit_article_button_click(self, **event_args):
    # Create a copy of the existing article from the Data Table 
    article_copy = dict(self.item)
    # Open an alert displaying the 'ArticleEdit' Form
    # set the `self.item` property of the ArticleEdit Form to a copy of the article to be updated
    save_clicked = alert(
      content=ArticleEdit(item=article_copy),
      title="Update Article",
      large=True,
      buttons=[("Save", True), ("Cancel", False)]
    )
    # Update the article if the user clicks save
    if save_clicked:
      anvil.server.call('update_article', self.item, article_copy)

Step 5: Refresh the page to reflect any updates

After updating the article, we call self.refresh_data_bindings to reflect any changes in the articles as displayed on the Homepage.

Your edit_article_button_click should look like this:

  def edit_article_button_click(self, **event_args):
    # Create a copy of the existing article from the Data Table 
    article_copy = dict(self.item)
    # Open an alert displaying the 'ArticleEdit' Form
    # set the `self.item` property of the ArticleEdit Form to a copy of the article to be updated
    save_clicked = alert(
      content=ArticleEdit(item=article_copy),
      title="Update Article",
      large=True,
      buttons=[("Save", True), ("Cancel", False)]
    )
    # Update the article if the user clicks save
    if save_clicked:
      anvil.server.call('update_article', self.item, article_copy)

      # Now refresh the page
      self.refresh_data_bindings()

Run your app and you’ll see that you can now update your articles!

Running the app and editing the title of an article

We’ve finished the U in our CRUD app - we can now update articles that already exist!

Only one more letter to go: we’ll make it possible to delete articles. That’s the D in CRUD app.

Chapter complete

Congratulations, you've completed this chapter!