Web Development with Dash

sqlite3
dash
Author

Jade Liang

Published

November 8, 2024

Welcome! In this post, I will go over how to create a webapp using Dash by Plotly. By the end of this blog, we’ll be able to create a website that accepts user input!

Function to Create the Database of Messages

We’ll first make a function to set up a database to receive messages from users. This is what the function looks like:

import sqlite3

message_db = None

def get_message_db():
    """creates database of messages, creates a 
        `messages` table if not already in database,
         and return the connection of message_db

    Returns:
        message_db: connection to the message_db database
    """
    global message_db

    if message_db is not None: # if database is not empty
        return message_db
    else:
        # Connect to the database messages_db.sqlite
        message_db = sqlite3.connect("messages_db.sqlite", 
                                     check_same_thread=False)

        # SQL command to create a `messages` table in 
        #   the database if it does not exist
        cmd = '''
        CREATE TABLE IF NOT EXISTS messages (
            handle TEXT NOT NULL,
            message TEXT NOT NULL
        )
        '''
        cursor = message_db.cursor()
        cursor.execute(cmd)
        message_db.commit()     # saves changes
        cursor.close()      # closes cursor

        return message_db

# setting up `message` table
get_message_db()
<sqlite3.Connection at 0x1061506d0>

Function to Insert a User’s Message into Database

Next, we’ll create a function that inserts a user’s inputted message into the database we just created.

def insert_message(handle, message):
    """
    Inserts a new message into the database.
    Args:
        handle (str): The user handle.
        message (str): The content of the message.
    """

    # creating a cursor to our database
    cur = message_db.cursor()
    cur.execute("""INSERT INTO messages (handle, message)
                    VALUES (?, ?)
                   """,
                   (handle, message))
    
    # committing changes to enure row insertion is saved
    message_db.commit()

    cursor.close() # closing cursor
    message_db.close()  # closing connection to database

Callback Function to Update Components

Now, we’ll create a callback function to update the components. Before doing so, we need to define our Dash app. Then we’ll implement the callback function submit() to allow user to submit their message along with their name or handle.

import dash
from dash import html, dcc, Input, Output, State

# creating Dash app
app = dash.Dash(__name__)

app.layout = html.Div([
    # page title
    html.H1("A SIMPLE MESSAGE BANK",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0F807A'}),

    # form for submission
    html.H2("Submit",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0B5551'}),
    html.Div("Your Message:",
             style={'font-family': 'Arial, sans-serif', 
                    'color': '#678D8B'}),
    dcc.Input(id='user-message', type='text',
              style={'width': '50%', 'margin-bottom': '5px'}),
    html.Div("Your Name or Handle:",
             style={'font-family': 'Arial, sans-serif', 
                    'color': '#678D8B'}),
    dcc.Input(id='user-handle', type='text',
              style={'width': '50%', 'margin-bottom': '5px'}),
    html.Button('Submit', id='submit-button',
                style={'margin-top': '10px', 
                       'background-color': '#0B5551', 
                       'color': 'white', 'border': 'none'}),
    html.Div(id='message-output'),

    # to view submitted messages
    html.H2("View",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0B5551'}),
    html.Button('Update', id='refresh-button', 
                style={'background-color': '#4A708B', 
                       'color': 'white'}),
    html.Div(id='messages-display')
])

# define callback function to handle form submission
@app.callback(
    Output('message-output', 'children'),
    Input('submit-button', 'n_clicks'),
    [State('user-handle', 'value'), 
     State('user-message', 'value')],
    prevent_initial_call=True
)
def submit(n_clicks, handle, message):
    if not handle or not message: # if user didn't input both
        return 'Please enter both a handle and a message.'
    try: 
        insert_message(handle, message) 
        return 'Thank you for submitting a message!'
    except Exception as e: # return error message if failed to insert message
        return f'An error occurred: {str(e)}'

df = sqlite3.connect("messages_db.sqlite", check_same_thread=False)

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

Function to Fetch Random Messages

Then, we’ll create a function to fetch random messages in our database.

def random_messages(n):
    """
    Fetches a random selection of messages from the database.
    Args:
        n (int): Number of random messages to retrieve.
    Returns:
        list of tuples: A list of messages with their handles.
    """

    # connect to database
    db = get_message_db()

    # create a cursor
    cursor = db.cursor()

    # extracting n random messages
    query = "SELECT name_or_handle, message FROM messages ORDER BY RANDOM() LIMIT ?"
    cursor.execute(query, (n,))
    messages = cursor.fetchall()

    # close cursor and connection to database
    cursor.close()
    db.close()
    return messages

Callback Function to Display Random Messages

@app.callback(
    Output('messages-display', 'children'),
    Input('refresh-button', 'n_clicks'),
    prevent_initial_call=True
)
def view(n_clicks):
    try:
        messages = random_messages(5) # extracting 5 random messages
        return [
            # displays each message in one line
            html.Div([
                html.P(message, style={'font-size': '16px'}),
                html.P(f"- {handle}", style={'font-size': '16px', 'text-align': 'right'})
            ], style={'margin-bottom': '20px', 'border-bottom': '1px solid #ccc', 'padding-bottom': '10px'})
            for handle, message in messages
        ]
    except Exception as e:
        # return error message if failed to fetch messages
        return html.Div(f"Failed to fetch messages: {str(e)}")

To View Final Results

We’ll compile all our functions, update our Dash app to display messages that were submitted, and try using our message bank!

import sqlite3
import dash
from dash import html, dcc, Input, Output, State

message_db = None

def get_message_db():
    """creates database of messages, creates a `messages` table if not already in database,
         and return the connection of message_db

    Returns:
        message_db: connection to the message_db database
    """
    global message_db

    if message_db is not None: # if database is not empty
        return message_db
    else:
        # Connect to the database messages_db.sqlite
        message_db = sqlite3.connect("messages_db.sqlite", check_same_thread=False)

        # SQL command to create a `messages` table in the database if it does not exist
        cmd = '''
        CREATE TABLE IF NOT EXISTS messages (
            handle TEXT NOT NULL,
            message TEXT NOT NULL
        )
        '''
        cursor = message_db.cursor()
        cursor.execute(cmd)
        message_db.commit()     # saves changes
        cursor.close()      # closes cursor

        return message_db

# setting up `message` table
get_message_db()

app = dash.Dash(__name__)

app.layout = html.Div([
    # page title
    html.H1("A SIMPLE MESSAGE BANK",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0F807A'}),

    # form for submission
    html.H2("Submit",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0B5551'}),
    html.Div("Your Message:",
             style={'font-family': 'Arial, sans-serif', 
                    'color': '#678D8B'}),
    dcc.Input(id='user-message', type='text',
              style={'width': '50%', 'margin-bottom': '5px'}),
    html.Div("Your Name or Handle:",
             style={'font-family': 'Arial, sans-serif', 
                    'color': '#678D8B'}),
    dcc.Input(id='user-handle', type='text',
              style={'width': '50%', 'margin-bottom': '5px'}),
    html.Button('Submit', id='submit-button',
                style={'margin-top': '10px', 
                       'background-color': '#0B5551', 
                       'color': 'white', 'border': 'none'}),
    html.Div(id='message-output'),

    # to view submitted messages
    html.H2("View",
            style = {'font-family': 'Arial, sans-serif', 
                     'color': '#0B5551'}),
    html.Button('Update', id='refresh-button', 
                style={'background-color': '#4A708B', 
                       'color': 'white'}),
    html.Div(id='messages-display')
])

def insert_message(handle, message):
    with sqlite3.connect("messages_db.sqlite") as conn:
        cur = conn.cursor()
        cur.execute("""INSERT INTO messages 
                    (handle, message) 
                    VALUES (?, ?)""", 
                    (handle, message))
        conn.commit()

def random_messages(n):
    with sqlite3.connect("messages_db.sqlite") as conn:
        cur = conn.cursor()
        cur.execute("""SELECT handle, message FROM 
                    messages ORDER BY RANDOM() 
                    LIMIT ?""", (n,))
        messages = cur.fetchall()
    return messages

@app.callback(
    Output('message-output', 'children'),
    Input('submit-button', 'n_clicks'),
    [State('user-handle', 'value'), 
     State('user-message', 'value')],
    prevent_initial_call=True
)
def submit(n_clicks, handle, message):
    if not handle or not message:
        return 'Please enter both a handle and a message.'
    try:
        insert_message(handle, message)
        return 'Thank you for submitting a message!'
    except Exception as e:
        return f'An error occurred: {str(e)}'

@app.callback(
    Output('messages-display', 'children'),
    Input('refresh-button', 'n_clicks'),
    prevent_initial_call=True
)
def view(n_clicks):
    try:
        messages = random_messages(5) # extracting 5 random messages
        return [
            # displays each message in one line
            html.Div([
                html.P(message, style={'font-size': '16px'}),
                html.P(f"- {handle}", 
                       style={'font-size': '16px', 
                              'text-align': 'right'})
            ], style={'margin-bottom': '20px', 
                      'border-bottom': '1px solid #ccc', 
                      'padding-bottom': '10px'})
            for handle, message in messages
        ]
    except Exception as e:
        # return error message if failed to fetch messages
        return html.Div(f"Failed to fetch messages: {str(e)}")

if __name__ == '__main__':
    app.run_server(debug=True)