Dash Callbacks with Modal Components

Hello guys,

I’m about to finish my scheduler alarm app, and I’m a bit stuck at the moment.
About the app:

It will be a simple value grab from the api with CRUD elements

image

This where I add a user and the email to the database( the user name value I use for checks in the app and the email for sending the email to the user)

image

Here is how the problem looks: I want here to get the name values from the api and when i selected the user to delete him via the button, but I’m stuck with it. I think the modal code and CRUD code does not align good.
The problem is that it gives me all the time Duplicate Outputs errors:
How do I code the def update_delete_user_dropdown: and def delete_user correctly?

Also if you know a better to make my modal_layout to looks like margins pls tell me :smiley:

In the callback for output(s):
output-container-button.children
Output 0 (output-container-button.children) is already in use.
To resolve this, set allow_duplicate=True on
duplicate outputs, or combine the outputs into
one callback function, distinguishing the trigger
by using dash.callback_context if necessary.

The
def update_delete_user_dropdown: makes me problems when i comment it out I can get the modal layout.

Code:

the api function:

def get_emails():
    response = requests.get('http://10.3.41.24:5001/api/emails')
    if response.status_code == 200:
        emails = response.json()
        return emails
    else:
        print("Failed to fetch emails from the API")
        return []
employee_positions = {}
emails = get_emails()
for email_data in emails:
    name = email_data['name']
    email = email_data['email']
    employee_positions[name] = [email]

This is my modal

modal_layout = html.Div([
    html.Div(id='modal-container', style={'display': 'none', 'position': 'fixed', 'width': '100%', 'height': '100%', 'top': '0', 'left': '0', 'background-color': 'rgba(0,0,0,0.5)', 'z-index': '1000'}),
    html.Div(id='modal', style={'display': 'none', 'position': 'fixed', 'top': '50%', 'left': '80%', 'transform': 'translate(-50%, -50%)', 'z-index': '1001', 'background-color': 'white', 'margin': '20px'}, children=[
        html.Div([
            html.Button("X", id="close-modal", n_clicks=0, style={'position': 'absolute', 'top': '10px', 'right': '10px'}),
            dcc.Tabs(id='modal-tabs', value='add-user', children=[
                dcc.Tab(label='Add User', value='add-user', children=[
                    html.Div([
                        html.H3("Add New User"),
                        html.Label("Name:"),
                        dcc.Input(id='new-user-name', type='text', placeholder='Enter name...'),
                        html.Label("Email:"),
                        dcc.Input(id='new-user-email', type='email', placeholder='Enter email...'),
                        html.Button("Add User", id="add-user-button", n_clicks=0, className="btn btn-primary")
                    ])
                ]),
                dcc.Tab(label='Delete User', value='delete-user', children=[
                    html.Div([
                        html.H3("Delete User"),
                        html.Label("Select User:"),
                        dcc.Dropdown(id='delete-user-dropdown', options=[], placeholder='Select user...'),
                        html.Button("Delete User", id="delete-user-button", n_clicks=0, className="btn btn-danger")
                    ])
                ])
            ])
        ], className='modal-content')
    ])
])

toggle modal:

@app.callback(
    [Output('modal', 'style'),
     Output('modal-container', 'style')],
    [Input('add-new-user-button1', 'n_clicks'),
     Input('close-modal', 'n_clicks')],
    [State('modal', 'style')]
)
def toggle_modal(add_new_user_clicks, close_modal_clicks, modal_style):
    ctx = dash.callback_context
    if not ctx.triggered:
        return modal_style, {'display': 'none'}
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]
        if button_id == 'add-new-user-button1':
            modal_style['display'] = 'block'
            return modal_style, {'display': 'block'}
        elif button_id == 'close-modal' or button_id == 'add-user-button' or button_id == 'delete-user-button':
            modal_style['display'] = 'none'
            return modal_style, {'display': 'none'}

CRUD:

@app.callback(
    Output('br206-employee-category-dropdown-alert1', 'options'),
    [Input('add-user-button', 'n_clicks')],
    [State('new-user-name', 'value'),
     State('new-user-email', 'value')]
)
def add_new_user(n_clicks, name, email):
    ctx = dash.callback_context
    if not ctx.triggered or not name or not email:
        return dash.no_update
    else:
        # Prepare data to be sent to the Flask API
        data = {'name': name, 'email': email}
        # Make a POST request to the Flask API endpoint
        response = requests.post('http://10.3.41.24:5001/api/emails', json=data)
        if response.status_code == 200:
            print("User added successfully")
            # Update the dropdown options with the new user
            new_option = {'label': name, 'value': email}
            employee_options = [{'label': key, 'value': key} for key in employee_positions.keys()]
            employee_options.append(new_option)
            return employee_options
        else:
            print("Failed to add user")
            return dash.no_update


@app.callback(
    Output('delete-user-dropdown', 'options'),
    [Input('delete-user-button', 'n_clicks')],
    [State('delete-user-dropdown', 'value')]
)
def update_delete_user_dropdown(n_clicks, selected_user):
    ctx = dash.callback_context
    if not ctx.triggered:
        return dash.no_update
    else:
        #Fetch the list of emails from the API
        if ctx.triggered[0]['prop_id'].split('.')[0] == 'delete-user-button':
            response = requests.get('http://10.3.41.24:5001/api/emails')
            if response.status_code == 200:
                emails = response.json()
                #Populate the dropdown with email options
                options = [{'label': email['name'], 'value': email['email']} for email in emails]
                return options
            else:
                print("Failed to fetch emails from the API")
                return dash.no_update
        else:
            return dash.no_update



@app.callback(
    [Output('delete-user-dropdown', 'options'),
     Output('delete-user-dropdown', 'value')],
    [Input('delete-user-button', 'n_clicks')],
    [State('delete-user-dropdown', 'value')]
)
def delete_user(n_clicks, selected_user):
    ctx = dash.callback_context
    if not ctx.triggered:
        return dash.no_update, None
    else:
        # Perform API call to delete the selected user from the database
        if selected_user:
            response = requests.delete(f'http://10.3.41.24:5001/api/emails/{selected_user}')
            if response.status_code == 200:
                print("User deleted successfully")
            else:
                print("Failed to delete user")

        # Retrieve updated user list from the API
        response = requests.get('http://10.3.41.24:5001/api/emails')
        if response.status_code == 200:
            user_data = response.json()
            # Update the dropdown options after deletion
            employee_options = [{'label': user['name'], 'value': user['email']} for user in user_data]
            return employee_options, None
        else:
            return dash.no_update, None

On the second delete callback, alter the options to have allow_duplicate=True and it should be good to go. Make sure you have prevent_initial_call=True on that same one.

Hopefully then it will work for you. :grin:

I did it like this, as I found it better, but also your reply worked. Thank you:

@app.callback(
    [Output('delete-user-dropdown', 'options'),
     Output('delete-user-dropdown', 'value')],
    [Input('delete-user-button', 'n_clicks')],
    [State('delete-user-dropdown', 'value')]
)
def update_delete_user_dropdown_and_delete_user(n_clicks, selected_user):
    if n_clicks and selected_user:
        try:

            emails = get_emails()
            user_id = next((email['id'] for email in emails if email['email'] == selected_user), None)

            if user_id is None:
                print(f"User with email '{selected_user}' not found")
                return dash.no_update, selected_user

            response = requests.delete(f'http://10.3.41.24:5001/api/emails/{user_id}')
            response.raise_for_status()  
            
            print(f"User '{selected_user}' deleted successfully via API")

            emails = get_emails()
            if emails:
                options = [{'label': email['name'], 'value': email['email']} for email in emails]
                return options, None  

        except requests.exceptions.RequestException as e:
            print(f"Error deleting user: {str(e)}")
            return dash.no_update, selected_user 


    emails = get_emails()
    if emails:
        options = [{'label': email['name'], 'value': email['email']} for email in emails]
        return options, None  # Clear the selected value after deletion

    return [], None  
1 Like