An Expense Approval System Built with Python
Tags: python, programming, showdev, webdev
Published: 2023-03-01 15:10:00

# Build an expense approval system with Anvil

Anvil is a powerful and versatile tool for building full-stack web applications. With Anvil, you can [quickly and easily create your UI](https://anvil.works/blog/build-a-web-ui-with-python?utm_source=crosspost:medium:/learn/examples/expense-approval-app) and write Python code to control the front-end and back-end of your app. Creating apps with Anvil is fast and flexible, making it well-suited for building [internal tools](https://anvil.works/for/internal-tools?utm_source=crosspost:medium:/learn/examples/expense-approval-app). This expense approval system app showcases some of the features that Anvil has on offer.

![The Expense Approval app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjkc73m0aijv3zbu85k6.png)

**Open in Anvil: https://anvil.works/build#clone:ODKPA5XG5ZBEYSLZ%3dT6QRGCPIIRYAZ5FKKXALBEQI**

I built [the app’s UI](https://anvil.works/docs/client/ui?utm_source=crosspost:medium:/learn/examples/expense-approval-app) using Anvil’s drag-and-drop editor and writing front-end Python code. I also wrote the backend in Python, on a [secure Server Module](https://anvil.works/docs/server?utm_source=crosspost:medium:/learn/examples/expense-approval-app). This app also uses Anvil’s built-in database, user authentication and email sending. Take a look below at how I built this expense approval system, with only Python and Anvil.

# The Expense Approval app

This app is a simple expense approval system, where users can submit expenses for an admin user to approve or reject. The main page is a view of the expenses, and users can add expenses by clicking on the + New Expense button, and filling out the pop-up form that it triggers. Clicking on the expense description brings out a detailed view of the expense.

![Adding a new expense and viewing it.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjlthjyqjvxpsjlmpq36.png)<figcaption>Adding a new expense and viewing it.</figcaption>

An admin will be able to see all expenses and reject, approve and reimburse them, whereas a regular user will only have access to their own submissions


![The admin view.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/04n5h4f4g4h8irg7rw30.png)<figcaption>The admin view.</figcaption>

Whenever an admin takes action on an expense, the user that submitted it receives an email notifying them of the change. Finally, admins also have access to a dashboard summary of the expenses, with visualisations, which they can [download as a PDF](https://anvil.works/docs/working-with-files/creating-pdf-files/quickstart?utm_source=crosspost:medium:/learn/examples/expense-approval-app).

![A summary page with data visualisations.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/et895e2iji1ozxlaamba.png)<figcaption>A summary page with data visualisations.</figcaption>

# How it’s built

## Creating the UI

Anvil makes it simple to [create a UI](https://anvil.works/docs/client/ui?utm_source=crosspost:medium:/learn/examples/expense-approval-app). You can use drag-and-drop components and then write Python to make them interact with each other. For example, I used a [Data Grid](https://anvil.works/docs/client/components/data-grids?utm_source=crosspost:medium:/learn/examples/expense-approval-app) to display the expenses in my app.

![Dragging-and-dropping a Data Grid component from the Toolbox onto the ExpenseDashboard Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/398x6y6ki7nl6szb8ssn.png)<figcaption>Dragging-and-dropping a Data Grid component from the Toolbox onto the ExpenseDashboard Form</figcaption>

Once the layout is set, you can make your components interact with each other by writing your browser code, all in Python. The function below, which runs whenever we change to the Pending Approval view, is running entirely in the browser without making any round trips to the server.

```python
def pendingappr_btn_click(self, **event_args):
# When pendingappr_btn is clicked, the page contents are changed
    self.content_panel.clear()
    self.content_panel.add_component(
        ExpenseDashboard(status='pending'))
```

## Data Tables and Server Side

Anvil has a [built-in database system](https://anvil.works/docs/data-tables?utm_source=crosspost:medium:/learn/examples/expense-approval-app) built on top of PostgreSQL, with a GUI for creating, editing and deleting tables. This makes it simple to define your schema. By default, Data Tables are only accessible from the server side.

[Anvil’s Server Modules](https://anvil.works/docs/server?utm_source=crosspost:medium:/learn/examples/expense-approval-app) are full server-side Python environments, which cannot be edited or seen by the user. Using server functions to interact with our database ensures our data stays [secure](https://anvil.works/docs/security#client-vs-server-code?utm_source=crosspost:medium:/learn/examples/expense-approval-app). These functions can then be [called from the client-side](https://anvil.works/docs/server#calling-server-functions-from-client-code?utm_source=crosspost:medium:/learn/examples/expense-approval-app) with just one line of code. For example, the function below adds a row to my expenses data table.

```python
@anvil.server.callable
def add_expense(expense_dict):
  app_tables.expenses.add_row(created=datetime.now(), 
                              status="pending", 
                           submitted_by=anvil.users.get_user(), 
                              **expense_dict)
```

The `@anvil.server.callable` decorator means I can call this function from client-side code.

```python
anvil.server.call('add_expense', expense)
```

## User Management

Anvil has a built-in [Users Service](https://anvil.works/docs/users?utm_source=crosspost:medium:/learn/examples/expense-approval-app), which handles signup, login and user permissions, as well as supporting corporate single-sign-on, two-factor authentication, and more. I simply added the Users Service to my app and chose the authentication methods I wanted to use - Email + Password and Google sign-in. I then added `anvil.users.login_with_form()` to my client side to display a login form on startup.


![Log-in modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k99m3idr74clo1250h3w.png)

With Anvil, it’s also very simple to restrict functionalities based on [user roles](https://anvil.works/docs/users/permissions#user-roles?utm_source=crosspost:medium:/learn/examples/expense-approval-app). For example, I wanted certain components to be visible and enabled only if the logged in user is an admin, and I was able to implement this with only a couple lines of code:

```python
if State.user['role'] == 'admin':
    self.summary_btn.visible = True
    self.summary_btn.enabled = True
```

## Email service and background tasks

Anvil makes it easy to send and receive emails from your app with a few lines of Python using the [built-in email service](https://anvil.works/docs/email?utm_source=crosspost:medium:/learn/examples/expense-approval-app). I wanted to notify users via email of a status change in their expense request, so first I wrote a function to do just that:

```python
@anvil.server.background_task
def send_email(user, message):
  anvil.email.send(to=user, from_name="Expenses App", subject="Your expense has been updated", html=message)
```

The `@anvil.server.background_task` decorator indicates that this task can be run in the background. [Background Tasks](https://anvil.works/docs/background-tasks?utm_source=crosspost:medium:/learn/examples/expense-approval-app) allow you to fire off a function to run in the background, leaving your main program to continue executing while the function is running.

With this, I could launch my background task every time an expense’s status is changed, without blocking the control flow of my app, by adding `anvil.server.launch_background_task` to my `change_status` server function.

```python
@anvil.server.callable(require_user=is_admin)
def change_status(row, status):
  old_status = row['status']
  user = row['submitted_by']['email']
  message = f"<p>Hi, {user},</p><p>The status of your expense ('{row['description']}') changed from <b>{old_status}</b> to <b>{status}</b>.</p><p>Visit the <a href={anvil.server.get_app_origin()}>app</a> to learn more details.</p>"
  row.update(status=status)
  anvil.server.launch_background_task('send_email', user=user, message=html)
```

## Data Visualisations and PDF rendering

Anvil comes with a built in [Plotly integration](https://anvil.works/docs/client/components/plots?utm_source=crosspost:medium:/learn/examples/expense-approval-app), so you can import, process and visualize data, all within Anvil, using Python. Because they’re Plotly plots under the hood, Anvil’s plots are interactive — you can move the mouse over a plot to see the available tools.

I decided to plot some data from the app’s database, so I first created some functions on the server to get the data into shape, like in the example below:

```python
@anvil.server.callable(require_user=is_admin)
def get_status_data():
  status_data = [x['status'] for x in app_tables.expenses.search()]
  labels = list(set(status_data))
  values = []
  for l in labels:
    values.append(status_data.count(l))
  return labels, values
```

Then, I just fed it into one of my plots from my Summary Form:

```python
labels, values = anvil.server.call('get_status_data')
self.plot_1.data = go.Pie(labels=labels, values=values, hole=.4)
self.plot_1.layout.title = "Expense requests by status"
```

I also decided to make the Form [downloadable as a PDF](https://anvil.works/docs/working-with-files/media/quickstart-pdfs?utm_source=crosspost:medium:/learn/examples/expense-approval-app). First, I rendered it into a PDF in a server function:

```python
@anvil.server.callable(require_user=is_admin)
def create_summary_pdf():
  return anvil.pdf.render_form('Plots')
```

Then, I call this function from the client to download the file whenever the download button is clicked:

```python
  def download_summ_btn_click(self, **event_args):
    """This method is called when the button is clicked"""
    media_object = anvil.server.call('create_summary_pdf')
    anvil.media.download(media_object)
```

# Build your internal tools with Anvil

Anvil makes building powerful internal tools simple, straightforward and fast. You can use all your favourite [Python libraries](https://anvil.works/docs/server/packages?utm_source=crosspost:medium:/learn/examples/expense-approval-app) to build your code, and [the drag-and-drop designer](https://anvil.works/docs/client/ui?utm_source=crosspost:medium:/learn/examples/expense-approval-app) makes building UIs a breeze.

This expense approval system makes use of Anvil’s [built-in database system](https://anvil.works/docs/data-tables?utm_source=crosspost:medium:/learn/examples/expense-approval-app), the easy-to-use [user management service](https://anvil.works/docs/users?utm_source=crosspost:medium:/learn/examples/expense-approval-app), the [email service](https://anvil.works/docs/email?utm_source=crosspost:medium:/learn/examples/expense-approval-app), and more. It’s powered by [Python running in the browser](https://anvil.works/docs/client/python?utm_source=crosspost:medium:/learn/examples/expense-approval-app), as well as on the [server](https://anvil.works/docs/server?utm_source=crosspost:medium:/learn/examples/expense-approval-app), meaning that it’s extremely customisable. Clone the app to check out the source code and play with it yourself!

Open in Anvil: https://anvil.works/build#clone:ODKPA5XG5ZBEYSLZ%3dT6QRGCPIIRYAZ5FKKXALBEQI