domingo, 15 de septiembre de 2019

Módulo de Login y Registro en Python - Conexión con SQLite

En la primera parte construimos el proyecto para trabajar con listas y guardar allí los datos de los usuarios. El principal problema con esto es que los datos no se almacenan de forma permanente, es decir, una vez se cerraba el programa, los usuario registrados se borraban. Por esta razón, en esta segunda parte se añaden funcionalidades que nos permitirán insertar y consultar información en una base de datos. 

En este post el manejador de base de datos que utilizaremos es SQLite; iniciamos con él debido que viene integrado con python y no requiere instalación extra para su uso. Más adelante haremos una integración con MySQL. El código de esté capitulo lo puedes descargar en mi cuenta github.
ACTUALIZACIÓN DE LA ESTRUCTURA DE LA APP

create_sqlite_db.py # Archivo para la creación de la db y tablas
app  # paquete app
  |-> main.py  # archivo principal del app
  |-> menu.py  # funciones para los menús del app
  |-> validators.py # funciones para validar el email y password
  |-> db_conection.py # funciones para gestionar la db

Para esta ocasión se agregaron dos archivos uno en el directorio principal (create_sqlite_db.py) y otro dentro del directorio app (db_conection.py).

CREACIÓN DE LA BASE DE DATOS

El archivo create_sqlite_db.py contiene el script para crear la base de datos y la tabla. El código es el siguiente:

# -*- coding: utf-8 -*-
import sqlite3

#Crea el archivo de Base de Datos si no ha sido creada
conn = sqlite3.connect('LoginDB.db') 
c = conn.cursor()

# Creación de la Tabla - USER
c.execute('''
 CREATE TABLE users (
     username text PRIMARY KEY,
     email text NOT NULL,
     password text NOT NULL
  )'''
)              
conn.commit()

  • Lo primero que se hace es importar el módulo de sqlite.
  • Posteriormente se crea una conexión con el archivo LoginDB.db, sino existe lo crea (conn = sqlite3.connect('LoginDB.db')); es justo con esta instrucción la que crea un archivo con el nombre LoginDB.db en el mismo directorio donde se encuentra el script.
  • Se crea un cursor la cual abre una interfaz para acceder a la base de datos (c = conn.cursor()).
  • Luego se crea la tabla haciendo uso del método execute y dentro de él la sentencia SQL para la creación de la tabla. La tabla se llama users, cuyos campos son: username de tipo text y la asignamos como llave primaria, email tipo text y no nulo y password también tipo text y no nulo.
  • Con el método commit se fija la ejecución del SQL.
Una vez creado este script es necesario ejecutarlo, esto lo hacemos:

# python create_sqlite_db.py

DESARROLLANDO EL MÓDULO DE CONEXIÓN

El archivo db_conection.py contiene las clases y métodos que permiten insertar y obtener datos de la base de datos.

import sqlite3

class SQLiteConection():
# Conexión con la Base de Datos
    def connect(self):
        self.con = sqlite3.connect('../LoginDB.db')
        self.cur = self.con.cursor()

    def create_user(self, username, email, password):
        self.connect()
        self.cur.execute(
         'INSERT INTO users (username, email, password) VALUES (?, ?, ?);',
            (username, email, password)
        )
        result = self.cur.lastrowid
        self.con.commit()
        self.con.close()
        return result

    def find_user_by_username(self, username):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE username = "{}"'.format(username))
        result = self.cur.fetchone()
        self.con.close()
        return result

    def find_user_by_email(self, email):
        self.connect()
        self.cur.execute('SELECT * FROM users WHERE email = "{}"'.format(email))
        result = self.cur.fetchone()
        self.con.close()
        return result

Se crea una clase llamada SQLiteConection, cuyos métodos son:  
  • connect: La que abre la conexión con la DB y fija el cursor. Acá se crea dos atributos con y cur que se usarán en los otros métodos de la clase.
  • create_user: A este método se le pasa los parámetros username, email y password, las cuales se concatenan con la instrucción SQL INSERT para insertar dentro de la tabla users este nuevo usuario.
  • find_user_by_username: Es un método para buscar al usuario por username, para lo cual se le pasa como parámetro el nombre de usuario y con un SELECT se busca el usuario, retornando un tupla con el primer valor encontrado gracias al fetchone.
  • find_user_by_email: Hace lo mismo que el método anterior, pero en esa ocasión la búsqueda se hace mediante el email.
MODIFICACIÓN DEL MAIN

Para poder adaptar la aplicación con la gestión de los datos mediante base de datos es necesario adaptar el main.

# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate
from db_conection import SQLiteConection

def main():
    menu = MenuTemplate()
    opcion = menu.main_menu()
    sqlite_db = SQLiteConection()

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if sqlite_db.find_user_by_username(data["username"]):
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif sqlite_db.find_user_by_email(data["email"]):
                print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
            else:
                password_hashed = bcrypt.hashpw(
                    data["password1"].encode('utf8'), 
                    bcrypt.gensalt()
                )
                
                new_user = sqlite_db.create_user(
                    data["username"],
                    data["email"],
                    password_hashed
                )
                
                if new_user:
                    print ("Se ha registrado en el sistema.")
                else:
                    print ("Ha ocurrido un error en la base de datos.")
        else:
            print(f"\n{data}")

        input("\nPresione una tecla para continuar...")
        main()

    elif opcion == "2":
        ''' Sección para el login de usuario '''
        data = menu.login()

        result = sqlite_db.find_user_by_username(data["username"])
        if result:
            if not bcrypt.checkpw(data["password"].encode('utf8'), result[2]):
                print("Username o password inválido.")
            else:
                print("Usted ha ingresado al sistema.")
        else:
            print("Username o password inválido.")
        
        input("\nPresione una tecla para continuar...")
        main()

    elif opcion == "3":
        ''' Sección para salir del sistema'''
        menu.exit_text()
        time.sleep(2)
        menu.clean_screen()

    else:
        ''' En caso de no coincidir la opciones anteriores '''
        print("\n Opción incorrecta. Intente de nuevo")
        time.sleep(2)
        main()

main()

Estudiemos los nuevos cambios ingresados al código por parte:

# -*- coding: utf-8 -*-
import time
import bcrypt
from menu import MenuTemplate
from db_conection import SQLiteConection
El primer segmento modificado se encuentra al inicio. En ella se agregó:
  • La importación de la clase SQLiteConection desde el archivo db_conection (from db_conection import SQLiteConection).
  • Se eliminan las listas username, email y password ya que no se usarán para almacenar los datos.
También se agrega un línea después de la función main() para crear una instancia de la clase SQLiteConection (sqlite_db = SQLiteConection()).

Otro segmento fuertemente modificado se encuentra en la exploración de la opción 1.

    if opcion == "1":
        ''' Sección para el registro de usuario '''
        data = menu.register()
        
        if "username" in data:
            if sqlite_db.find_user_by_username(data["username"]):
                print(f'Este username: <{data["username"]}> ya se encuentra registrado.')
            elif sqlite_db.find_user_by_email(data["email"]):
                print(f'Este Email: <{data["email"]}> ya se encuentra registrado.')
            else:
                password_hashed = bcrypt.hashpw(
                    data["password1"].encode('utf8'), 
                    bcrypt.gensalt()
                )
                
                new_user = sqlite_db.create_user(
                    data["username"],
                    data["email"],
                    password_hashed
                )
                
                if new_user:
                    print ("Se ha registrado en el sistema.")
                else:
                    print ("Ha ocurrido un error en la base de datos.")
        else:
            print(f"\n{data}")

        input("\nPresione una tecla para continuar...")
        main()

Nótese que ahora pasa verificar si el username y el email ya se encuentran registrado usamos los métodos find_user_by_username y find_user_by_email, respectivamente. Si no se consigue un usuario por su username o por su email, entonces se puede crear ese nuevo usuario mediante el método create_user al que se le pasa como argumento el username, email y password_hashed.

La sección donde se explora la opción 2 también sufre modificación.

    elif opcion == "2":
        ''' Sección para el login de usuario '''
        data = menu.login()

        result = sqlite_db.find_user_by_username(data["username"])
        if result:
            if not bcrypt.checkpw(data["password"].encode('utf8'), result[2]):
                print("Username o password inválido.")
            else:
                print("Usted ha ingresado al sistema.")
        else:
            print("Username o password inválido.")
        
        input("\nPresione una tecla para continuar...")
        main()

Acá, después de recibir los datos ingresados por el usuario, se busca al usuario en la base de datos por su username si encuentra un resultado comparamos el dato del password (result[2]) almacenado en la db. Se accede al password de esa forma debido que el resultado obtenido es una tupla y la información de la contraseña se ubica en la posición 2 de la misma.

Ahora ejecutando nuestra aplicación podemos registrarnos y logearnos como lo haciamos anteriormente, con la diferencia que si salimos del sistema y lo volvemos a ejecutar el usuario registrado anteriormente no se habrá perdido.

Con esto concluimos este capítulo. Con SQLite podemos gestionar bases de datos para aplicaciones pequeñas, pero si requerimos un mejor manejador de base de datos deberemos utilizar alguno como MySQL, y es justo lo que haremos en el próximo capítulo. ¡Hasta pronto!


0 comentarios:

Publicar un comentario