Storing News in a Database

Anvil Essentials Part 2 of 4

In this tutorial, we’ll take a tour of Anvil and build a simple news aggregator. We’ll start from a blank page, and build up to a web app complete with database storage and deployed on the web - all in nothing but Python.

This is also known as a “CRUD” app, named after the Create, Read, Update and Delete operations.

The final app will look something like this:

screenshot of the completed app

The techniques you’ll learn in this tutorial are fundamental to building any Anvil app, and mastering them will give you a good understanding of how Anvil works.

In this tutorial, you’ll learn how to:

  1. Create database tables
  2. Build your user interface
  3. Write client-side Python
  4. Add news articles to the database (Create)
  5. Display a list of news articles from the database (Read)
  6. Update existing news articles (Update)
  7. Delete an article from the database (Delete)

Click the following link to clone the finished app and explore it yourself, or read on as we take a step-by-step guide to building it yourself.


You don't need any prior experience with Anvil or web development to complete this tutorial.

All you'll need is a little bit of Python. If you’re new to Python, here are some resources to help you get started.



Chapter 1

Create your database tables

Let’s start by creating the data tables to store your news articles.

Create your App

Log in to Anvil and click ‘New Blank App’. Choose the Material Design theme.

Location of the Create App button

First, name the app. Click on the name at the top of the screen and give it a name.

Add the Data Tables service

We’ll use Data Tables to store our news articles. Data Tables are an out-of-the-box option for data storage in Anvil, and they’re backed by PostgreSQL.

In the panel on the left (the App Browser), click the ‘+’ next to ‘Services’, then click on Data Tables.

Adding new service

Articles

Now set up your “articles” table, with the following columns:

  • title (Text column)
  • content (Text column)
  • image (Media column)
  • created (Date and Time column)
  • updated (Date and Time column)
Creating the 'articles' data table
  1. Click ‘Add a table’ in the light blue box at the top, then ‘Create new table…’. You’ll be prompted to give your table a name - let’s call it ‘articles’.

  2. Next, add a column for the article title, by clicking ‘+ New Column’ and choosing ‘Add text column…’. Call this column ‘title’.

  3. Keep adding columns until your Data Table has the structure we described above. Column titles are case sensitive, so let’s stick to lower case.

Your Data Table should look something like this:

Database schema

Categories

We’re also going to put each article into a ‘category’. These categories are fixed, so we’ll create a second table called ‘categories’ to store the categories, and link the ‘articles’ table to the categories table.

Create your “categories” table, with a single column:

  • title (Text column)

Add these categories to the table:

  • entertainment
  • business
  • travel
  • sport
Creating the categories table

Finally, create a column in your ‘Articles’ table to store a category for each news article. Click the ‘+’ to add a new column, then follow ‘Link to table…’, ‘categories’, and select ‘Single Row’. Call this ‘category’. This creates a link to the ‘categories’ table you just created.

Creating the categories table

Your Data Tables are set up and ready to use. Nice work! Time for Chapter 2.


Chapter 2

Build your user interface

Let’s build your UI using Anvil’s drag-and-drop editor.

Rename your Form

Click on ‘Form1’ in the panel on the left. You will see your app in the centre of the screen.

‘Forms’ are the pages that make up your Anvil app. Form1 was automatically created when you created your app, and will act as your homepage. Rename it ‘Homepage’ by clicking on the dropdown next to Form1 in the panel on the left.

Location of the Rename Form button

Add components

We construct the UI by dragging-and-dropping components from the Toolbox into the page.

Drop a Button Button icon into the page. Our users will click this button when they want to add a new news article:

Creating the categories table

Change component properties

Below the Toolbox, you’ll see the Properties Panel, where you edit the styling and behaviour of your components by changing their properties.

Select the Button you just added to the page, and change the text to ‘Add an article’.

Next, change the Button’s name. Click on the red text that says self.button_1 at the top of the Properties Panel, and change it to add_article_button. We’re changing the name of our Button to make it easier to identify from code.

Change the role to ‘primary-color’. Then, in the ‘Icon’ section of the Properties Panel, add an icon to the Button by clicking the Icon image (I’m using plus-circle in the example above).


Add a new Form

Next, we’ll build the UI for Creating and Updating news articles. We can save work by using the same UI and code for both! (We talk about this more in our CRUD best-practice guide.)

Add a new Form by clicking the ‘plus’ icon to the right of ‘Forms’ in the App Browser. Choose the ‘Blank Panel’ template, and rename the Form ‘ArticleEdit’ (in accordance with our recommended naming convention).

Location of the Add Form button

Our ArticleEdit Form will ask our users for the ‘name’, ‘content’, ‘category’ and ‘image’ for each article they want to add to the Data Table (we’ll add the ‘created’ and ‘updated’ columns programmatically).

We’ll use Labels for our input prompts. Drop four labels into the page, and change their text properties to:

  • Title:
  • Content:
  • Category:
  • Image:
Creating the name and content inputs for the ArticleEdit Form

We’ll then need user input components for each of these. Drop these components into the page, renaming them as you go:

  • Title: TextBox, rename this title_box
  • Content: TextArea, rename this content_box
  • Category: DropDown, rename this category_box
  • Image: FileLoader, rename this image_uploader
Creating the name and content inputs for the ArticleEdit Form

We’re changing the name of our user input components to make it easier to identify them from code.

We’ll also add a placeholder to our DropDown. Select the DropDown you just added, check the ‘include_placeholder’ box, and set the placeholder to ‘choose category’.

setting a placeholder for the `category_box` dropdown

Our ArcticleEdit UI is now complete. Nice job! Time for Chapter 3.


Chapter 3

Write client-side Python

Go to ‘Code’ view in the Form Editor:

The location of code view in the Anvil Editor

This is where you write your client-side Python code that runs in the browser.

Populate your DropDown

Let’s populate your DropDown with a list of category names from your ‘categories’ Data Table. We’ll use Anvil’s search query operator to do this.

Add this line of code underneath the import statements on your ‘ArticleEdit’ Form:

# Return a list of rows from the 'categories' Data Table
categories = [(cat['name'], cat) for cat in app_tables.categories.search()]

This creates a list of 2-tuples [("First option", <first_row_from_table>)...("Final Option", <final_row_from_table>)]. The first element of each tuple is what will be displayed in the DropDown box. The second element of the tuple becomes the selected_value property of the DropDown if that option is selected.

By default, client-side code in Anvil doesn’t have permission to access Data Tables. Go to your ‘categories’ Data Table and change the permissions to allow Forms ‘search’ access:

allow Forms to search the 'categories' Data Table

It’s safe to do so in this case, because the ‘categories’ Data Table contains no sensitive information - it’s just a simple list of strings.

To display our list of categories in the DropDown, we set the items property of our DropDown to the ‘categories’ list we just defined. Go back to your ‘ArticleEdit’ Form and add this line to the __init__ method:

# Any code you write here will run when the form opens.
self.category_box.items = categories

Your ‘ArticleEdit’ Form should now look like this:

from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

categories = [(cat['name'], cat) for cat in app_tables.categories.search()]

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

    # Any code you write here will run when the form opens.
    self.category_box.items = categories

We now have an ‘Add an Article’ button on the Homepage and an ‘ArticleEdit’ Form which users will use to Create and Update news articles. Nice job! Time for Chapter 4.


Chapter 4

Add news articles to the database

The Create process will look like this:

  1. The user clicks the ‘Add an article’ button
  2. A popup appears, displaying the content of the ‘ArticleEdit’ Form
  3. The user fills in the inputs
  4. The user clicks either ‘Save’ or ‘Cancel’
  5. If the user clicks save, store the article. Otherwise, discard the article.

We’ll walk through these steps below.

1. Display a popup when the user clicks ‘Add an Article’

We want to display our ‘ArticleEdit’ Form when a user clicks the ‘Add an article’ button.

We’ll use Events for this.

Click on ‘Homepage’ in the App Browser to go back to your UI. Click on your ‘Add an article’ Button, and scroll to the bottom of the Properties Panel. You’ll see a list of events for the Button.

Click the blue arrows next to ‘click’.

You will be taken to the Form Editor’s ‘Code’ view, where you’ll see an add_article_button_click method on your Form that looks like this:

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

This is the Python method that runs when the ‘Add an article’ Button is clicked.

When someone clicks this button, we want to display a popup Form that users can fill out to save a new article.

We do this using Alerts.

For example, to display a simple popup with the text ‘You clicked the button’, we would edit our add_article_button_click function as follows:

def add_article_button_click(self, **event_args):
  # Display a popup that says 'You clicked the button'
  alert("You clicked the button")

To see it in action, click the ‘Run’ button at the top of the screen:

Location of the Run button

You’ll see your app running. Click the ‘Add an article’ button and your popup should appear!

2. Set the content of the popup to the ‘ArticleEdit’ Form

We want our alert to display the ‘ArticleEdit’ Form. To do this, we first need to import the ArticleEdit Form inside our Homepage Form.

Add this line to the top of your ‘Homepage’ Form:

from ArticleEdit import ArticleEdit

We can now display the ‘ArticleEdit’ Form in our popup by customising the alert using Anvil’s custom popup styles. Set the content property of the alert to an instance of the ‘ArticleEdit’ Form, set the title property to “Add an article”, and set the large property to True:

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

3. The user fills in the form

When someone chooses to save a news article, we want to store a title, content, image and category for that article in our Data Table.

We want to keep a record of the user inputs as they fill in the form. We also want a simple way to discard user inputs if they decide not to save the news article. We’ll do this by storing user inputs in a Python dictionary, so we can discard it if the user cancels the operation.

We’ll add user inputs to the dictionary. If the user chooses to save the news article, we’ll write this dictionary to our ‘articles’ Data Table. If the user decides to cancel, we can just discard the dictionary.

All Forms in Anvil are initialised with an item property that can be accessed in the form’s code. When we open our alert, we’ll set the item property of our instance of the ‘ArticlesEdit’ Form to the empty dictionary. The self.item property is a good place for forms (like ‘ArticleEdit’) to hold the data they’re displaying or editing.

Edit your add_article_button_click function to look like this:

def add_article_button_click(self, **event_args):
  # initialise an empty dictionary which we'll write back to with the user inputs
  new_article = {}
  # Open an alert displaying the 'ArticleEdit' Form
  # set the `self.item` property of the ArticleEdit Form to {}
  alert(content=ArticleEdit(item=new_article), title="Add Article",
           large=True)

We want to store user inputs in the empty dictionary that we initialised as the self.item property of the ArticleEdit Form. We can do this in Anvil using Data Bindings.

For each of the ‘name’, ‘content’, and ‘category’ inputs, add a data binding to self.item:

  • title_box: Bind the text property to self.item['title']
  • content_box: Bind the text property to self.item['content']
  • category_box: Bind the selected_value property to self.item['category']
adding data bindings to the ArticleEdit Form

For the FileLoader component, when the user uploads an image, we we want to add it to self.item. To do this, we set an event handler on the FileLoader component which will run when the file property changes (i.e. when a user uploads an image):

Double click the FileLoader component to create a method for the change event - the change event is raised when a user uploads a file. Its file argument is a Media object containing the chosen file:

adding a 'file_loader_change' event handler to the FileLoader component

Edit the image_uploader_change method to look like this:

def image_uploader_change(self, file, **event_args):
  """This method is called when a new file is loaded into this FileLoader"""
  # Add the image to self.item
  self.item['image'] = file

4. Add ‘Save’ and ‘Cancel’ buttons to the popup

We want to display two buttons on our popup: ‘Save’ and ‘Cancel’. We’ll Create new article if the user clicks ‘Save’, and discard the user inputs if they click ‘Cancel’.

We do this by setting the alert’s buttons property, which expects a list of buttons. Each button should be set to a 2-tuple. The first element of each tuple is is the text to display on the button, and the second element is the value returned if the user clicks the button.

Add a buttons property to the alert in your add_article_button_click function:

def add_article_button_click(self, **event_args):
  # initialise an empty dictionary which we'll write back to with the user inputs
  new_article = {}
  # Open an alert displaying the 'ArticleEdit' Form
  # Return True if the 'Save' button on the alert is clicked 
  # and False if the 'Cancel' button on the alert is clicked
  alert(content=ArticleEdit(item=new_article), title="Add Article",
           large=True, buttons=[("Save", True), ("Cancel", False)])

Run your app, and click your ‘Add an Article button’. You’ll see the ArticleEdit Form in a popup, and notice that you have ‘Save’ and ‘Cancel’ buttons.

5. Store the article in your Data Table, or discard it

We want to Create a new article if the user clicks ‘Save’. We’ll write to the Data Table from inside a Server Module.

Write server-side code

Anvil’s Server Modules are a full server-side Python environment. Server modules cannot be edited or seen by the user, so we can trust them to do what we tell them. This is why we’ll use a Server Module to write to the Data Table. (Read more about our security recommendations for CRUD apps.)

Create a Server Module by clicking the ‘+’ next to Server Modules in the App Browser. You’ll see some code with a yellow background:

Adding a Server Module

We’ll write a server function to add a new row to the ‘articles’ Data Table. We’ll use the add_row method to do this.

Add this function to your server Module:

@anvil.server.callable
def add_article(article_dict):
  app_tables.articles.add_row(
    created=datetime.now(),
    **article_dict
  )

The @anvil.server.callable decorator allows us to call this function from client code.

We’re using the datetime library to set the ‘created’ column to the current date and time, so you’ll also need to import the datetime class at the top of your Server Module:

from datetime import datetime

The final step is to call this method from the client when the user chooses to save a news article.

We want to call our add_article server function when the button is clicked, and pass in the user inputs. We made our add_article server function available to our client-side code by decorating it as @anvil.server.callable.

This means we can call it from our client-side code using anvil.server.call('add_article', <arguments>)

The ‘Save’ and ‘Cancel’ buttons on our alert 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’.

Let’s call our add_article server function from our add_article_button_click function:

def add_article_button_click(self, **event_args):
  # initialise an empty dictionary which we'll write back to with the user inputs
  new_article = {}
  # Open an alert displaying the 'ArticleEdit' Form
  # Return True if the 'Save' button on the alert is clicked 
  # and False if the 'Cancel' button on the alert is clicked
  if alert(content=ArticleEdit(item=new_article), title="Add Article",
           large=True, buttons=[("Save", True), ("Cancel", False)]):
    # Add the article to the Data Table is the user clicks 'Save'
    anvil.server.call('add_article', new_article)

If the user clicks ‘Cancel’, the alert will be dismissed, and the user inputs in our new_article dictionary will be discarded.

Run your app again, click the ‘Add an article’ button, fill in the inputs, and click Save. Now, if you go to the ‘articles’ table in your Data Table, you’ll see the article in the Data Table.

Great! We can now add news articles to our Data Table. On to Chapter 5!


Chapter 5

Display news articles

The Read process will look like this:

  1. When the Homepage is opened, a list of articles from the Data Table is displayed
  2. Each item in the list is a detailed view of each article, including ‘title’, ‘content’, ‘category’ and ‘image’.

We’ll walk through these steps below, in reverse order:

2. Create a detailed view for each Article

First, let’s create the UI. Create a new Form, select the BlankPanel template, and rename it ‘ArticleView’.

We want to display an instance of ‘ArticleView’ on our Homepage for each article in our Data Table. We’ll need space for the article’s title, content, category, and image.

Drop a Card Card icon component into the page, then add these components to the Card:

  • Title: Label, rename this title_label
  • Content: Label, rename this content_label
  • Category: Label, rename this category_label
  • Image: Image
Dragging and dropping components to create the 'ArticleView' Form

Next, change your title_label’s role to subheading using the ‘Appearance’ section of the Properties Panel.

Applying the 'subheading' role to the 'title_label' in the 'appearance' section of the Properties Panel

Make both your title_label and content_label bold - click on ‘MORE’ to the right of the ‘text’ section of the Properties Panel to change the bold property:

Location of the 'bold' property in the 'appearance' section of the Properties Panel

In order to display an instance of ‘ArticleView’ for each article in your Data Table, your ‘ArticleView’ Form needs to be placed inside a RepeatingPanel.

Go to your ‘Homepage’ Form, and drop a RepeatingPanel Repeating Panel Icon onto the page. Change its name to articles_panel and set its item_template to your ArticleView Form:

Dragging and dropping a RepeatingPanel into the Homepage Form and changing its ItemTemplate to the 'ArticleView' Form

Next, we want to display data on each of our ArticleView Forms. Once again, we’ll use Data Bindings, and the Form’s self.item property to do this.

Open the ‘ArticleView’ Form and add the following data bindings:

  • title_label: Bind the text property to self.item['title']
  • category_label: Bind the text property to self.item['category']['name'] (self.item['category'] is a row from the categories table, so we want to get the ‘name’ from the row)
  • content_label: Bind the text property to self.item['content']
  • image_1: Bind the source property to self.item['image']
Adding data bindings to the ArticleView Form

1. Display a list of articles on the Homepage

We’ll use a Server Module to retrieve a list of articles from the Data Table. (Again, this is because code in Server Modules can be trusted, and client-side code cannot - and, and we might want to add authentication or other logic to this retrieval code. (We recommend doing it this way for all database-backed apps.)

Add this function to your Server Module:

# In your Server Module
@anvil.server.callable
def get_articles():
  # Get a list of articles from the Data Table, sorted by 'created' column, in descending order
  return app_tables.articles.search(tables.order_by("created", ascending=False))

Next, we want to display an instance of ‘ArticleView’ on the Homepage for each article returned by the Server Function above.

We’ll define a function called refresh_articles() to display a list of articles on the Homepage. This function can be used in a number of places - we’ll also call it after we update or delete an article, to refresh the content on the Homepage, so we call it refresh_articles(). (You can read more about Anvil’s naming conventions here.)

Go back to your Homepage Form and add this function:

def refresh_articles(self):
  # Load existing articles from the Data Table, and display them in the RepeatingPanel
  self.articles_panel.items = anvil.server.call('get_articles')

How it works:

Our articles_panel is a RepeatingPanel, with the template set to our ‘ArticleView’ Form.

When you set the RepeatingPanel’s items property to an iterable, the RepeatingPanel creates one instance of its item_template for each item in the iterable. It also sets the self.item property of that instance to that item from the iterable.

In this case, get_articles() returns an iterator over the ‘articles’ table, so the RepeatingPanel will create an instance of ‘ArticleView’ for each row in the data table. Each instance will have its self.item property set to its row in the ‘articles’ table. The Data Bindings on our ‘ArticleView’ form read data from self.item, so they will display data from that article.

We want to load our news articles from the Data Table when the Homepage is opened, so let’s call our refresh_articles function when the Homepage loads.

Add this line to the bottom of the __init__ method of your Homepage Form:

# Any code you write here will run when the form opens.
self.refresh_articles()

We also want to call refresh_articles() after we Create a new article, so the new article shows up on our Homepage. Modify your add_article_button_click to look like this:

def add_article_button_click(self, **event_args):
  # initialise an empty dictionary which we'll write back to with the user inputs
  new_article = {}
  # Open an alert displaying the 'ArticleEdit' Form
  # Return True if the 'Save' button on the alert is clicked 
  # and False if the 'Cancel' button on the alert is clicked
  if alert(content=ArticleEdit(item=new_article), title="Add Article",
           large=True, buttons=[("Save", True), ("Cancel", False)]):
    # Add the article to the Data Table is the user clicks 'Save'
    anvil.server.call('add_article', new_article)
    self.refresh_articles()

Run your app, and try adding some articles to your Data Table!

We’re now displaying articles on our Homepage. Nice job! Time for Chapter 6.


Chapter 6

Update existing articles

The Update process 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.

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. Change its name to edit_article_button, and its role to ‘primary-color’. (You can also set an icon - I like to use the pencil-square icon instead of text for my edit buttons):

Adding an edit button to the ArticleView Form

We want to display a popup when the user clicks the edit_article_button.

Create an event handler for your edit button by double clicking the button in the designer:

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

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)

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(list(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))

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

We’ll write our function to update an existing article in the Data Table inside a Server Module. (Again, this is because code in Server Modules can be trusted, and client-side code cannot - and we have a couple of important checks to run before we update the database. See our security recommendations for CRUD 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 this 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(list(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
  if alert(content=ArticleEdit(item=article_copy), title="Update Article",
           large=True, buttons=[("Save", True), ("Cancel", False)]):
    # Update the article if the user clicks save
    anvil.server.call('update_article', self.item, article_copy)

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(list(self.item))
  # Only update the article in the Data Table if the 'Save' button is clicked
  # if the 'Cancel' button is clicked, discard the copy
  if alert(content=ArticleEdit(item=article_copy), 
           title="Update Article", 
           large=True,
           buttons=[("Save", True), ("Cancel", False)]):
    # Update the article if the user clicks save
    anvil.server.call('update_article', self.item, article_copy)
    self.refresh_data_bindings()

Run your app and you’ll see that you can now update your articles! Great job! Time for the final Chapter.


Chapter 7

Delete news articles

The Delete process is our final step, and it will look like this:

  1. The user clicks the ‘Delete’ button
  2. A popup appears, asking the user to confirm if they wish to delete the selected article
  3. If the user clicks ‘Yes’, we delete the article. Otherwise, we do nothing
  4. If the article is deleted, we refresh the Homepage to remove the deleted article

We’ll walk through these steps below.

1. The Delete button

First, let’s create our ‘Delete’ button:

Adding a delete button to the ArticleView Form

Go to Design View of your ‘ArticleView’ Form, and drop a FlowPanel Flow Panel icon into the page. Move your edit_article_button into the FlowPanel. Then, drop a second button into the FlowPanel, and set the align property of the FlowPanel to ‘right’.

Select the new button, and rename it delete_article_button. Change the role to primary-color, the background property to ‘red’, and the foreground property to ‘white’. I also like to use the trash icon in place of text for my delete buttons. Finally, adjust the column width if necessary to make sure both buttons are displayed properly (use Ctrl+drag for finer adjustments).

Create an event handler for your delete button by double clicking the button in the designer.

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

2. Confirm that the user wants to delete the article

When the delete button is clicked, we want to display a popup asking the user to confirm if they want to delete the article. We do this with a confirmation, which is just an alert with pre-built “Yes” and “No” buttons.

Update your delete_article_button_click function to look like this:

def delete_article_button_click(self, **event_args):
  # Check that the user does want to delete the selected article
  confirm("Are you sure you want to delete {}?".format(self.item['title']))

This will display a confirmation popup with the article’s title, asking the user if they want to delete the article.

3. Delete the article, or do nothing

We’ll write the function for deleting articles from the Data Table inside a Server Module. (Again, this is because code in Server Modules can be trusted, and client-side code cannot. See our security recommendations for CRUD apps.)

Add this function to your Server Module:

@anvil.server.callable
def delete_article(article):
  # check that the article being deleted exists in the Data Table
  if app_tables.articles.has_row(article):
    article.delete()
  else:
    raise Exception("Article does not exist")

As with the Update function, we’ve added a security check to ensure that the article actually exists in the Data Table. If not, the server function will raise an exception. Without this check, a malicious user could delete any row from any table by passing it to this function.

We’ll call this server function from our Homepage rather than the ‘ArticleView’ Form. It’s a good idea to keep the CRUD operations on the main Form (our Homepage in this case) where possible.

The delete button is on the ‘ArticleView’ Form, but when it’s clicked, we want to call a function on our Homepage Form. We do this by raising an event on the ArticleView’s parent container.

You can look up any component’s parent container with the .parent attribute. In this case, all our ‘ArticleView’ instances are inside the articles_panel on our HomePage. So we can access articles_panel from within any ‘ArticleView’ on this page, and raise an event on it.

self.parent when called on the ArticleView Form refers to the articles_panel on the Homepage.

self.parent when called on the ArticleView Form refers to the articles_panel on the Homepage.

Next, we’ll set a custom event handler on the articles_panel on our Homepage. Then, we can raise this custom event when the delete button on our ‘ArticleView’ Form is clicked, and catch it on our Homepage.

Go back to your ‘Homepage’ Form, and add the following line to the __init__ method of your Form:

# Set an event handler on the RepeatingPanel (our 'articles_panel')
self.articles_panel.set_event_handler('x-delete-article', self.delete_article)

This will call the self.delete_article function on the Homepage when the x-delete-event is raised, so let’s create the self.delete_article function. Add this to your Homepage Form:

def delete_article(self, article, **event_args):
  # Delete the article
  anvil.server.call('delete_article', article)

We’ll call this function when the delete button on our ‘ArticlesView’ Form is clicked, so go back to your ‘ArticlesView’ Form, and edit your delete_article_button_click to look like this:

def delete_article_button_click(self, **event_args):
  # Get the user to confirm if they wish to delete the article
  # If they confirm, raise the 'x-delete-article' event on the parent (which is the articles_panel on our Homepage)
  if confirm("Are you sure you want to delete {}?".format(self.item['title'])):
    self.parent.raise_event('x-delete-article', article=self.item)

The delete button on our ‘ArticlesView’ Form will now raise the ‘x-delete-event’ on our articles_panel, which we just configured to call the self.delete_article function on the Homepage. This will call our server function and delete the article.

4. Refresh the list of articles if an article is deleted

The final step is to call the self.refresh_articles() function on the Homepage if an article is deleted.

Update the self.delete_article() on your Homepage to look like this:

def delete_article(self, article, **event_args):
  # Delete the article
  anvil.server.call('delete_article', article)
  # Refresh articles to remove the deleted article from the Homepage
  self.refresh_articles()

Run your app and you’ll see that you can now delete articles from the Data Table!


That’s it!

And that’s it. You’ve just built a working database-backed “CRUD” app in Anvil.

Your news articles aggregator is already live on the internet. Go to Publish App in the Gear Menu gear icon for details.

You can also use the following link to clone the finished app and explore it yourself:


Congratulations!

Congratulations! You’ve built and shipped a database-backed web application!

You also now have a solid foundation in Anvil.


Optional: Customise your app

This part is optional! If you’re comfortable with HTML and CSS, we’ll show you how to use these technologies to make fine-grained changes to the appearance of your app. But you don’t need any HTML or CSS knowledge to use Anvil.

Anvil’s themes are defined with CSS stylesheets and HTML. You can customise the look and feel of your app in the Assets Editor (under “Assets” in the App Browser).

To illustrate, let’s change the font of your news app. Scroll down to ‘Theme’ in the App Browser, and click on ‘Assets’:

Location of 'Assets' in the App Browser

In the dropdown at the top, select theme.css, and add this line to the top of your css:

/**
 This CSS implements the Material Design look and feel for Anvil apps.
 **/
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');

Scroll down to the ‘Typography’ section of theme.css (around line 83), and modify the css that styles the body of your app to look like this:

/* Typography */
body {
  font-family: 'Open Sans', sans-serif; /* edit this line */
  font-size: 14px;
  line-height: 1.4286;
  background-color: #fafafa;
}

Run your app, and you’ll see the font has now changed!


What next?

The Anvil Essentials course continues with Storing and Displaying Data.

By the end of the four Anvil Essentials tutorials, you will be able to build and publish multi-user data management apps with Anvil.

Head to the Anvil Learning Centre for more tutorials, or head to our examples page to learn how to build more complex apps in Anvil.