DEV Community

Cover image for Simple pero poderoso: CRUD dinámico con Flask, Javascript y Fetch API.

Simple pero poderoso: CRUD dinámico con Flask, Javascript y Fetch API.

Con tantas herramientas para el desarrollo web, nos volvemos locos. Sin embargo en las de toda la vida se puede lograr efectos bastante interesantes. Por ejemplo, un crud que use el poder Javascript para generar efectos muy similares al actualizar una parte de la pantalla, sin tener que recurrir a un refresco completo de esta.

El ejemplo que presentaré es una pantalla CRUD con un formulario básico, que gestionará la información a través de la técnica AJAX usando fetch API. el estándar actual de JS para obtener la información desd eel backend y que lleguen a la presentación. Como he estado trabajando con Flask, el backend será ese.

La vista

<section class="section">
    <div class="row">
        <div class="col-md-12">
            <div class="card">
                <div class="card-body">
                    <div class="card-title">Datos Bancos</div>
                    <form id="bancoForm">
                        <div class="row g-3 mb-4">
                            <div class="col-md-4">
                                {% if session.usuario[4] == 1 and session.usuario[5] == 1%}
                                    <select name="idempresa" id="idempresa" class="form-select form-select-lg">
                                        <option value="">Seleccione empresa...</option>
                                        {% for empresa in empresas %}
                                            <option value="{{ empresa.id }}">{{ empresa.nombre }}</option>
                                        {% endfor %}
                                    </select>
                                {% else %}
                                    <input type="hidden" name="idempresa" id="idempresa" value="{{ session.usuario[5] }}" readonly>
                                {% endif %}
                                <input type="hidden" name="idbanco" class="form-control form-control sm" id="idbanco" >
                            </div>
                        </div>
                        <div class="row g-3 mb-4">
                            <div class="col-md-4">
                                <input type="text" name="nombre" placeholder="Nombre del banco" id="nombre" class="form-control bank" required>
                            </div>
                            <div class="col-md-4">
                                <input type="text" name="ncuenta" placeholder="Numero de cuenta" id="ncuenta" class="form-control bank" required>
                            </div>
                            <div class="col-md-4">
                                <select name="tipo_cuenta_bancaria" placeholder="Tipo de cuenta" id="tipo_cuenta_bancaria" class="form-select bank" required>
                                    <option value="">Seleccione tipo de cuenta</option>
                                    {% for cuenta in tipo_cuenta %}
                                        <option value="{{ cuenta.id }}">{{ cuenta.tipo_cuenta }}</option>
                                    {% endfor %}                                    
                                </select>
                            </div>
                            <div class="col-md-4">
                                <select name="tipo_moneda" placeholder="Tipo de moneda" id="tipo_moneda" class="form-select bank" required>
                                    <option value="">Seleccione tipo de moneda</option>
                                    <option value="CLP">Pesos</option>
                                    <option value="USD">Dolares</option>
                                    <option value="EUR">Euros</option>
                                </select>
                            </div>
                            <div class="col-md-4">
                                <input type="checkbox" name="estado" id="estado" class="form-check-input bank">
                                <label class="form-check-label" for="estado">Activo</label>
                            </div>
                        </div>
                        <div class="row g-3 mb-4">
                            <div class="col-md-8">
                                <button type="submit" class="btn btn-primary mb-2" id="grabar"><i class="fa fa-save"></i> Grabar</button>
                                <button type="reset" class="btn btn-warning mb-2 limpiar"><i class="fa fa-eraser"></i> Limpiar</button>
                                <button type="button" class="btn btn-danger mb-2" id="cancelar"><i class="fa fa-times"></i> Cerrar</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
            <br>
            <div class="card">
                <div class="card-body">
                    <div class="card-title">Datos Bancos</div>
                    <div class="table-responsive">
                        <table class="table table-bordered table-striped" id="lista_bancos">
                            <thead>
                                <th>#</th>
                                <th>N&deg; Cuenta</th>
                                <th>Banco</th>
                                <th>Tipo de Cuenta</th>
                                <th>Tipo de Moneda</th>
                                <th>Estado</th>
                                <th>Acciones</th>
                            </thead>
                            <tbody>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
</section>
Enter fullscreen mode Exit fullscreen mode

En este html, tenemos en dos card distintas, primero el formulario en la superior y la tabla vacía en el inferior. Esto es el esqueleto aun sin datos, salvo una validación con Jinja2 para traer un select con una lista.

Javascript

Por otra parte, tenemos el JS de turno. Aquí es donde ocurrirá la magia.

function traeDatosBanco(idempresa) {
    listaBancos.innerHTML = ""
    fetch('/traebancos', {
        method : 'POST',
        headers : {
            'Content-type' : 'application/json'
        },
        body : JSON.stringify({idempresa: idempresa})
        })
    .then(response => response.json())
    .then(data => {
        console.log(data.msg)
        if (data.resp == 1) {
            let linea = ''
            let i = 1
            let colorTextEstado = ''
            data.msg.forEach(dato => {
                colorTextEstado = dato.estado == 'Activo' ? 'text-success' : 'text-danger'
                linea = `<tr>
                <td>${i++}</td>
                <td>${dato.numero_cuenta}</td>
                <td>${dato.nombre_banco}</td>
                <td>${dato.tipo_cuenta_bancaria}</td>
                <td>${dato.tipo_moneda}</td>
                <td class="${colorTextEstado}"><b>${dato.estado}</b></td>
                <td>
                    <button type="button" class="btn btn-primary btn-sm dropdown-toggle" id="opciones" data-bs-toggle="dropdown" aria-expanded="false">
                        <i class="fa fa-gear"></i>
                    </button>
                    <ul class="dropdown-menu" aria-labelledby="opciones">
                        <li>
                            <button type="button" class="btn btn-link btn-block dropdown-item editar" data-id="${dato.id}"><i class="fa fa-pencil text-primary"></i>  Editar</button>
                        </li>
                        <li>
                            <button type="button" class="btn btn-link btn-block dropdown-item eliminar" data-id="${dato.id}" data-estado=${dato.estado == 'Activo' ? 'I' : 'A'}>
                            <i class="fa ${dato.estado == 'Activo' ? 'fa-eye-slash text-danger' : 'fa-eye text.success'}"></i> ${dato.estado == 'Activo' ? 'Desactivar' : 'Activar'}</button>
                        </li>
                    </ul>
                </td>
                </tr>
                `
                listaBancos.insertAdjacentHTML('beforeend', linea)
            })
            cargaTabla('#lista_bancos')
        } else {
            console.log(data.msg)
        }
    })
    .catch(error => {
        console.log(error)
    })
}
Enter fullscreen mode Exit fullscreen mode

Esta función es la que que tendrá comunicación con el backed para traer la información. En este caso, una lista de cuentas y las cargará en la tabla de la vista. ¿Cómo lo hará? de dos maneras: usando el select visto en el HTML y la otra a la hora de rellenar y enviar el formulario.

idempresa.addEventListener('change', function() {
    let idempresa = this.value
    console.log(idempresa)
    grabar.disabled = false
    habilitar('.bank')
    // Se invoca a la función. refrescará la tabla en cada elección
    traeDatosBanco(idempresa)
})


Enter fullscreen mode Exit fullscreen mode

En cuanto a lo que hace, la función hace el llamado asíncrono al backend y generalas filas de la tabla con los datos y acciones a usar dentro de ella. Con esto le damos una dinámica en que, cada evento de creacion y/o actualización. Lo mismo pasa en botón de envío del formulario.

formBancos.addEventListener('submit', function(e) {
    e.preventDefault()
    // Sentencias y validaciones
    fetch('/enviadatos', {
        method : 'POST',
        headers : {
            'Content-type' : 'application/json'
        },
        body : datos
    })
    .then(response => response.json())
    .then(data => {
        if (data.resp == 1) {
            // si la respuesta es positiva, vuelve a cargar la tabla
            traeDatosBanco(idempresa.value)
        }
    })
    .catch(error => {console.log(error)})
})
Enter fullscreen mode Exit fullscreen mode

La ventaja al ser una función, es que se puede invocar en cualquier evento. La tabla se refrescará con los datos que el backend responda.

Backend.

El backend fue escrito dividiendo rutas, controladores y modelos, que es uno de los estándares de Flask para trabajar de mejor manera, al separar las responsabilidades.

route

La ruta es básicamente la "pasarela" entre la vista y el controlador.

@contab.route('/traebancos', methods=['POST'])
@login_required
def get_lista_bancos():
    data = request.get_json()
    empresa = data.get('idempresa', '') if data.get('idempresa', '') else session['usuario'][5]
    res = contableController.get_lista_bancos(empresa)
    return jsonify(res)
Enter fullscreen mode Exit fullscreen mode

controller

En el controlador, tenemos la lógica de negocio que permite procesar la información.

@staticmethod
    def get_lista_bancos(empresa):
        res = contableModel.get_lista_bancos(empresa)
        if res:
            return {'resp': 1, 'msg': res}
        return {'resp': 0, 'msg': 'No se encontraron resultados'}
Enter fullscreen mode Exit fullscreen mode

model

En cuanto al modelo, hace la conexion con la base de datos y trae el resultado, mediante una consulta SQL cruda. (no uso ORM en este ejemplo).

def get_lista_bancos(empresa):
    conexion = DBConn.conexion()
    string = '''select cb.numero_cuenta, cb.nombre_banco, tcb.tipo_cuenta_bancaria, cb.tipo_moneda, e.nombre,
    case
        when cb.estado = 'A' then 'Activo'
        else 'Inactivo'
    end as estado, cb.id
    from public.cuentas_bancarias cb 
    join public.tipo_cuenta_bancaria tcb on (cb.tipo_cuenta_bancaria = tcb.id)
    join public.empresa e on (cb.empresa = e.id) where e.id = %s;
    '''
    try:
        with conexion:
            with conexion.cursor() as sql:
                sql.execute(string, (empresa,))
                return [{'numero_cuenta': row[0], 
                         'nombre_banco': row[1], 
                         'tipo_cuenta_bancaria': row[2], 
                         'tipo_moneda': row[3],
                         'empresa': row[4], 
                         'estado': row[5], 
                         'id': row[6]} for row in sql.fetchall()]
    except psycopg2.DatabaseError as e:
        Utils.errorLog(f'Error get_lista_bancos => {str(e)}')
Enter fullscreen mode Exit fullscreen mode

El resultado se puede ver en el siguiente video.

En resumen, lo que se hizo es lo siguiente:

  • Creamos una función que reciba los datos desde el backend y escriba las filas de la tabla.
  • Al ser una función, se puede utilizar en dos eventos distintos: la lista seleccionble y los eventos DML para crear y/o actualizar los datos.
  • Evitamos el tener que recargar constantemente la página.

Si bien, existen frameworks que hacen exactamente lo mismo, aprender lo básico de javascript, permite entender el funcionamiento y hasta evitar recargar con tantas librerías que hagan lo mismo, cuando se pueden programar con el lenguaje mismo.

Top comments (0)