DataTable displaying blank rows

I’m actually working on an app that retrieves and display some data when a date time picker gets updated.

In the first call of the app, the data loads and get displayed correctly, but when update the table, the rows are aparently retrieved, but they are displayed in blank, as shown below:

Here is my code:


# Importa las librerías necesarias para la encripción de datos
from hmac import new
from hashlib import md5

# Importa la librerías necesarias para realizar consultas web
from requests import get, post

# Importa la librería necesaria para manipular objetos de tipo fecha-tiempo
from datetime import datetime, timedelta

# Importa la librería necesaría para manipular datos
from pandas import read_json

# Importa las librerías necesarias para crear una web app de tipo dashboard para visualización de datos
from dash import Dash
from dash.dependencies import Input, Output
from dash_html_components import Div, H2, Img
from dash_core_components import Dropdown, DatePickerRange
from dash_table import DataTable

# crea las variables necesarias para conectarse al servicio web de future time
url = 'some_url'
usr = 'some_user'
pwd = 'some_pwd'
pwdh = pwd.encode()
ts = str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))  # ; del datetime
msg = (usr + pwd + ts).encode()

# crea la firma hash HMAC MD5 utilizando la contraseña como llave y la guarda en la variable digest
digest = new(pwdh, msg, md5).hexdigest()

# Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
arg = {'LoginName': usr,
       'Signature': digest,
       'TimeStamp': ts}

# Establece la conexión al web service, utiliza el recurso /System/LoginUser y autentica al usuario,
# guarda en la variable r el resultado de la conexión.
r = post(url, arg)

# Obtiene el UserID
UserId = str(r.json()['Result'][11:13])

# Crea las variable necesarias para consumir el servicio de Working Day
url = 'some_url'
Keywords = ''
StartDate = str((datetime.today() - timedelta(1)).strftime('%Y-%m-%d')) + ' 00:00:00'
EndDate = str((datetime.today() - timedelta(1)).strftime('%Y-%m-%d')) + ' 23:59:59'
Filter = 'todos'
OrderBy = ''

# Construye el nuevo mensaje a ser firmado
msg = (Keywords + StartDate + EndDate + Filter + OrderBy + UserId).encode()

# Crea la firma hash HMAC MD5
digest = new(pwdh, msg, md5).hexdigest()  # ; del new, pwdh, msg, md5

# Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
arg = {'Keywords': '',
       'StartDate': StartDate,
       'EndDate': EndDate,
       'Filter': Filter,
       'OrderBy': '',
       'UserID': UserId,
       'Signature': digest}  # ; del Keywords, StartDate, EndDate, Filter, OrderBy, UserId, digest

# Invoca mediante get el recurso /WorkingDay/Get
r = get(url, arg)  # ; del url, arg

# Convierte la cadena json del webservice a dataframe de pandas
df = read_json(r.json()['Result'])  # ; del r; read_json

# Filtra las columnas necesarias para el reporte de faltas injustificadas
df = df.filter(items=['ExportId', 'Name', 'OU5', 'Date', 'TimeGroup', 'Exceptions'])

# Renombra las columnas del reporte
df.columns = ['Código de empleado', 'Nombre del empleado',
              'Departamento', 'Fecha', 'Horario', 'Excepción']

# Inicializa la aplicación web
app = Dash('BI-UYEDA')

# Define las hojas de estilos externas a utilizar   
external_css = [
    "https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css",  # Normalize the CSS
    "https://fonts.googleapis.com/css?family=Open+Sans|Roboto",  # Fonts
    "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css",
    "https://cdn.rawgit.com/TahiriNadia/styles/faf8c1c3/stylesheet.css",
    "https://cdn.rawgit.com/TahiriNadia/styles/b1026938/custum-styles_phyloapp.css"
]

# Añade las hojas de estilos a la aplicación
for css in external_css:
    app.css.append_css({"external_url": css})

server = app.server

# Define la estructura de la aplicación
app.layout = Div([
    Div([
        H2("Inteligencia de negocios UYEDA"),
        Img(src="some_url")],
        className="banner"),
    Div([
        Div([
        ], style={
            'margin': '5px',
            'text-align': 'center',
            'vertical-align': 'middle',
            'width': '48%',
            'display': 'inline-block'
        }),
        Div([
            DatePickerRange(
                id='fechas',
                start_date=str((datetime.today() - timedelta(1)).strftime('%Y-%m-%d')),
                end_date=str((datetime.today() - timedelta(1)).strftime('%Y-%m-%d')),
                display_format='DD/MM/YYYY',
                max_date_allowed=str((datetime.today() - timedelta(1)).strftime('%Y-%m-%d')),
                min_date_allowed='2018/01/01'
            ),
        ], style={
            'margin': '5px',
            'text-align': 'center',
            'vertical-align': 'middle',
            'width': '48%',
            'display': 'inline-block'
        })

    ],
        className="container"
    ),
    Div([
        DataTable(id='table',
                  columns=[{'name': i, 'id': i} for i in df.columns],
                  data=[{}], #df.to_dict('rows'),
                  sorting=True,
                  style_table={'overflowX': 'scroll'},
                  style_cell={'minWidth': '0px',
                              'maxWidth': '180px',
                              'whiteSpace': 'normal'},
                  css=[{
                      'selector': '.dash-cell div.dash-cell-value',
                      'rule': 'display: inline; white-space: inherit; overflow: inherit; text-overflow: inherit;'
                  }],
                  virtualization=True,
                  pagination_mode=True,
                  n_fixed_rows=1,
                  merge_duplicate_headers=True
                  )
    ],
        className="container")
])


# Crea las llamadas a la aplicación para actualizar las filas
@app.callback(
    Output('table', 'data'),
    [Input('fechas', 'start_date'),
     Input('fechas', 'end_date')]
)
# Crea la función que actualiza las filas de la tabla
def act_filas(start_date, end_date):
    """
    Esta función actualiza las filas de un datatable
    :param start_date: Cadena de caracteres que indica la fecha inicial del reporte
    :param end_date: Cadena de caracteres que indica la fehca final del reporte
    :return: Retorna las filas que actualizaran las filas del datatable
    """
    # crea las variables necesarias para conectarse al servicio web de future time
    url = 'some_url'
    usr = 'some_usr'
    pwd = 'some_pwd'
    pwdh = pwd.encode()
    ts = str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))  # ; del datetime
    msg = (usr + pwd + ts).encode()

    # crea la firma hash HMAC MD5 utilizando la contraseña como llave y la guarda en la variable digest
    digest = new(pwdh, msg, md5).hexdigest()

    # Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
    arg = {'LoginName': usr,
           'Signature': digest,
           'TimeStamp': ts}

    # Establece la conexión al web service, utiliza el recurso /System/LoginUser y autentica al usuario,
    # guarda en la variable r el resultado de la conexión.
    r = post(url, arg)

    # Obtiene el UserID
    UserId = str(r.json()['Result'][11:13])

    # Crea las variable necesarias para consumir el servicio de Working Day
    url = 'some_url'
    Keywords = ''
    StartDate = start_date + ' 00:00:00'
    EndDate = end_date + ' 23:59:59'
    Filter = 'todos'
    OrderBy = ''

    # Construye el nuevo mensaje a ser firmado
    msg = (Keywords + StartDate + EndDate + Filter + OrderBy + UserId).encode()

    # Crea la firma hash HMAC MD5
    digest = new(pwdh, msg, md5).hexdigest()  # ; del new, pwdh, msg, md5

    # Crea los argumentos necesarios para acceder al webservice y los asigna a la variable argumentos
    arg = {'Keywords': '',
           'StartDate': StartDate,
           'EndDate': EndDate,
           'Filter': Filter,
           'OrderBy': '',
           'UserID': UserId,
           'Signature': digest}  # ; del Keywords, StartDate, EndDate, Filter, OrderBy, UserId, digest

    # Invoca mediante get el recurso /WorkingDay/Get
    r = get(url, arg)  # ; del url, arg

    # Convierte la cadena json del webservice a dataframe de pandas
    df = read_json(r.json()['Result'])  # ; del r; read_json

    # Reemplaza los valores nulos por caracter vacio
    df.fillna('', None, None, True)

    # Descarta los registros correspondientes a practicantes, del cómputo de faltas
    df.drop(df[df['TimeGroup'] == 'Practicantes'].index, inplace=True)

    # Descarta los registros correspondientes a horarios libres, del cómputo de faltas
    df.drop(df[df['TimeGroup'] == 'Libre'].index, inplace=True)

    # Descarta los registros correspondientes a descansos, del cómputo de faltas
    df.drop(df[df['ProjectedTimeToWork'].str.contains('00:00:00')].index, inplace=True)

    # Descarta los registros correspondientes a trabajadores borrados, del cómputo de faltas
    df.drop(df[df['Name'].str.contains('Borrado')].index, inplace=True)

    # Descarta los registros correspondientes a visitantes, del cómputo de faltas
    df.drop(df[df['Name'].str.contains('Visitante')].index, inplace=True)

    # Descarta los registros correspondientes a personal directivo, del cómputo de faltas
    df.drop(df[df['Name'].str.contains('Director')].index, inplace=True)

    # Descarta los registros correspondientes a trabajadores borrados, del cómputo de faltas
    df.drop(df[df['Name'].str.contains('New node')].index, inplace=True)

    # Descarta los registros correspondientes a personal de soporte y pruebas, del cómputo de faltas
    df.drop(df[df['Name'].str.contains('Soporte y Pruebas')].index, inplace=True)

    # Descarta los registros correspondientes a Incapacidad IMSS Riesgo de Trabajo, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('[057]')].index, inplace=True)

    # Descarta los registros correspondientes a permiso sin goce de sueldo 069, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('Permiso Sin Goce de Sueldo')].index, inplace=True)

    # Descarta los registros correspondientes a Día de castigo 069, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('Día de castigo')].index, inplace=True)

    # Descarta los registros correspondientes a vacaciones, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('[801]')].index, inplace=True)

    # Descarta los registros correspondientes a falta con goce de sueldo 800, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('falta con goce de sueldo')].index, inplace=True)

    # Descarta los registros correspondientes a dias feriados, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('Dia No Laborable General de Ley día feriado')].index, inplace=True)

    # Descarta los registros correspondientes a Incapacidad IMSS Enfermedad General 060, del cómputo de faltas
    df.drop(df[df['Exceptions'].str.contains('Incapacidad IMSS Enfermedad General')].index, inplace=True)

    # Descarta los registros correspondientes a omisión de registro de entrada
    df.drop(df[df['InHour'].str[11:16] > '00:00'].index, inplace=True)

    # Filtra las columnas necesarias para el reporte de faltas injustificadas
    df = df.filter(items=['ExportId', 'Name', 'OU5', 'Date', 'TimeGroup', 'Exceptions'])

    # Renombra las columnas del reporte
    df.columns = ['Código de empleado', 'Nombre del empleado',
                  'Departamento', 'Fecha', 'Horario', 'Excepción']

    # Envía los resultados del DataFrame a las filas del data table
    data = [{'name': i, 'id': i} for i in df.to_dict('rows')]

    # Retorna los registros para actualizar la tabla
    return data


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