35 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | #--------------------------------------------------------------------
2 | # Instalar con pip install Flask
3 | from flask import Flask, request, jsonify
4 |
5 | # Instalar con pip install flask-cors
6 | from flask_cors import CORS
7 |
8 | # Instalar con pip install mysql-connector-python
9 | import mysql.connector
10 |
11 | # Si es necesario, pip install Werkzeug
12 | from werkzeug.utils import secure_filename
13 |
14 | # No es necesario instalar, es parte del sistema standard de Python
15 | import os
16 | import time
17 | import datosconexion as bd
18 | #--------------------------------------------------------------------
19 |
20 | app = Flask(__name__)
21 | CORS(app) # Esto habilitará CORS para todas las rutas
22 |
23 | class Catalogo:
24 | #----------------------------------------------------------------
25 | # Constructor de la clase
26 | def __init__(self, host, user, password, database):
27 | self.conn = mysql.connector.connect(
28 | host=host,
29 | user=user,
30 | password=password
31 | )
32 | self.cursor = self.conn.cursor()
33 | # Intentamos seleccionar la base de datos
34 | try:
35 | self.cursor.execute(f"USE {database}")
36 | except mysql.connector.Error as err:
37 | # Si la base de datos no existe, la creamos
38 | if err.errno == mysql.connector.errorcode.ER_BAD_DB_ERROR:
39 | self.cursor.execute(f"CREATE DATABASE {database}")
40 | self.conn.database = database
41 | else:
42 | raise err
43 |
44 | self.cursor.execute('''CREATE TABLE IF NOT EXISTS productos (
45 | codigo INT AUTO_INCREMENT PRIMARY KEY,
46 | descripcion VARCHAR(255) NOT NULL,
47 | cantidad INT NOT NULL,
48 | precio DECIMAL(10, 2) NOT NULL,
49 | imagen_url VARCHAR(255),
50 | proveedor INT(4))''')
51 | self.conn.commit()
52 |
53 | # Cerrar el cursor inicial y abrir uno nuevo con el parámetro dictionary=True
54 | self.cursor.close()
55 | self.cursor = self.conn.cursor(dictionary=True)
56 |
57 | def listar_productos(self):
58 | self.cursor.execute("SELECT * FROM productos")
59 | productos = self.cursor.fetchall()
60 | return productos
61 |
62 | def consultar_producto(self, codigo):
63 | # Consultamos un producto a partir de su código
64 | self.cursor.execute(f"SELECT * FROM productos WHERE codigo = {codigo}")
65 | return self.cursor.fetchone()
66 |
67 | def agregar_producto(self, descripcion, cantidad, precio, imagen, proveedor):
68 | sql = "INSERT INTO productos (descripcion, cantidad, precio, imagen_url, proveedor) VALUES (%s, %s, %s, %s, %s)"
69 | valores = (descripcion, cantidad, precio, imagen, proveedor)
70 |
71 | self.cursor.execute(sql,valores)
72 | self.conn.commit()
73 | return self.cursor.lastrowid
74 |
75 | def modificar_producto(self, codigo, nueva_descripcion, nueva_cantidad, nuevo_precio, nueva_imagen, nuevo_proveedor):
76 | sql = "UPDATE productos SET descripcion = %s, cantidad = %s, precio = %s, imagen_url = %s, proveedor = %s WHERE codigo = %s"
77 | valores = (nueva_descripcion, nueva_cantidad, nuevo_precio, nueva_imagen, nuevo_proveedor, codigo)
78 |
79 | self.cursor.execute(sql, valores)
80 | self.conn.commit()
81 | return self.cursor.rowcount > 0
82 |
83 | def eliminar_producto(self, codigo):
84 | # Eliminamos un producto de la tabla a partir de su código
85 | self.cursor.execute(f"DELETE FROM productos WHERE codigo = {codigo}")
86 | self.conn.commit()
87 | return self.cursor.rowcount > 0
88 |
89 | #--------------------------------------------------------------------
90 | # Cuerpo del programa
91 | #--------------------------------------------------------------------
92 | # Crear una instancia de la clase Catalogo
93 |
94 | catalogo = Catalogo(host=bd.host, user=bd.user, password=bd.password, database=bd.database)
95 | # las variables con los datos de la conexion estan guardadas en el archivo datosconexion.py
96 |
97 |
98 | # Carpeta para guardar las imagenes
99 | ruta_destino = './static/imagenes/' # Reemplazar por los datos de Pythonanywhere
100 |
101 | #ruta_destino = '/home/maxisimonazzi/mysite/static/imagenes/'
102 |
103 | @app.route("/productos", methods=["GET"])
104 | def listar_productos():
105 | productos = catalogo.listar_productos()
106 | return jsonify(productos)
107 |
108 | @app.route("/productos/", methods=["GET"])
109 | def mostrar_producto(codigo):
110 | producto = catalogo.consultar_producto(codigo)
111 | if producto:
112 | return jsonify(producto)
113 | else:
114 | return "Producto no encontrado", 404
115 |
116 | @app.route("/productos", methods=["POST"])
117 | def agregar_producto():
118 | #Recojo los datos del form
119 | descripcion = request.form['descripcion']
120 | cantidad = request.form['cantidad']
121 | precio = request.form['precio']
122 | imagen = request.files['imagen']
123 | proveedor = request.form['proveedor']
124 | nombre_imagen = ""
125 |
126 | # Genero el nombre de la imagen
127 | nombre_imagen = secure_filename(imagen.filename)
128 | nombre_base, extension = os.path.splitext(nombre_imagen)
129 | nombre_imagen = f"{nombre_base}_{int(time.time())}{extension}"
130 |
131 | nuevo_codigo = catalogo.agregar_producto(descripcion, cantidad, precio, nombre_imagen, proveedor)
132 | if nuevo_codigo:
133 | imagen.save(os.path.join(ruta_destino, nombre_imagen))
134 | return jsonify({"mensaje": "Producto agregado correctamente.", "codigo": nuevo_codigo, "imagen": nombre_imagen}), 201
135 | else:
136 | return jsonify({"mensaje": "Error al agregar el producto."}), 500
137 |
138 | @app.route("/productos/", methods=["PUT"])
139 | def modificar_producto(codigo):
140 | #Se recuperan los nuevos datos del formulario
141 | nueva_descripcion = request.form.get("descripcion")
142 | nueva_cantidad = request.form.get("cantidad")
143 | nuevo_precio = request.form.get("precio")
144 | nuevo_proveedor = request.form.get("proveedor")
145 |
146 | # Verifica si se proporcionó una nueva imagen
147 | if 'imagen' in request.files:
148 | imagen = request.files['imagen']
149 | # Procesamiento de la imagen
150 | nombre_imagen = secure_filename(imagen.filename)
151 | nombre_base, extension = os.path.splitext(nombre_imagen)
152 | nombre_imagen = f"{nombre_base}_{int(time.time())}{extension}"
153 |
154 | # Guardar la imagen en el servidor
155 | imagen.save(os.path.join(ruta_destino, nombre_imagen))
156 |
157 | # Busco el producto guardado
158 | producto = catalogo.consultar_producto(codigo)
159 | if producto: # Si existe el producto...
160 | imagen_vieja = producto["imagen_url"]
161 | # Armo la ruta a la imagen
162 | ruta_imagen = os.path.join(ruta_destino, imagen_vieja)
163 |
164 | # Y si existe la borro.
165 | if os.path.exists(ruta_imagen):
166 | os.remove(ruta_imagen)
167 | else:
168 | producto = catalogo.consultar_producto(codigo)
169 | if producto:
170 | nombre_imagen = producto["imagen_url"]
171 |
172 | # Se llama al método modificar_producto pasando el codigo del producto y los nuevos datos.
173 | if catalogo.modificar_producto(codigo, nueva_descripcion, nueva_cantidad, nuevo_precio, nombre_imagen, nuevo_proveedor):
174 | return jsonify({"mensaje": "Producto modificado"}), 200
175 | else:
176 | return jsonify({"mensaje": "Producto no encontrado"}), 404
177 |
178 | @app.route("/productos/", methods=["DELETE"])
179 | def eliminar_producto(codigo):
180 | # Primero, obtiene la información del producto para encontrar la imagen
181 | producto = catalogo.consultar_producto(codigo)
182 | if producto:
183 | # Eliminar la imagen asociada si existe
184 | ruta_imagen = os.path.join(ruta_destino, producto['imagen_url'])
185 | if os.path.exists(ruta_imagen):
186 | os.remove(ruta_imagen)
187 |
188 | # Luego, elimina el producto del catálogo
189 | if catalogo.eliminar_producto(codigo):
190 | return jsonify({"mensaje": "Producto eliminado"}), 200
191 | else:
192 | return jsonify({"mensaje": "Error al eliminar el producto"}), 500
193 | else:
194 | return jsonify({"mensaje": "Producto no encontrado"}), 404
195 |
196 |
197 | if __name__ == "__main__":
198 | app.run(debug=True)
--------------------------------------------------------------------------------
/app_closingconnection.py:
--------------------------------------------------------------------------------
1 | #--------------------------------------------------------------------
2 | # Instalar con pip install Flask
3 | from flask import Flask, request, jsonify
4 |
5 | # Instalar con pip install flask-cors
6 | from flask_cors import CORS
7 |
8 | # Instalar con pip install mysql-connector-python
9 | import mysql.connector
10 |
11 | # Si es necesario, pip install Werkzeug
12 | from werkzeug.utils import secure_filename
13 |
14 | # No es necesario instalar, es parte del sistema standard de Python
15 | import os
16 | import time
17 | import datosconexion as bd
18 | #--------------------------------------------------------------------
19 |
20 | app = Flask(__name__)
21 | CORS(app) # Esto habilitará CORS para todas las rutas
22 |
23 | class Catalogo:
24 | #----------------------------------------------------------------
25 | # Constructor de la clase
26 |
27 | def __init__(self):
28 | self.conn = mysql.connector.connect(
29 | host=bd.host,
30 | user=bd.user,
31 | password=bd.password
32 | )
33 | self.cursor = self.conn.cursor()
34 | # Intentamos seleccionar la base de datos
35 | try:
36 | self.cursor.execute(f"USE {bd.database}")
37 | except mysql.connector.Error as err:
38 | # Si la base de datos no existe, la creamos
39 | if err.errno == mysql.connector.errorcode.ER_BAD_DB_ERROR:
40 | self.cursor.execute(f"CREATE DATABASE {bd.database}")
41 | self.conn.database = bd.database
42 | else:
43 | raise err
44 |
45 | self.cursor.execute('''CREATE TABLE IF NOT EXISTS productos (
46 | codigo INT AUTO_INCREMENT PRIMARY KEY,
47 | descripcion VARCHAR(255) NOT NULL,
48 | cantidad INT NOT NULL,
49 | precio DECIMAL(10, 2) NOT NULL,
50 | imagen_url VARCHAR(255),
51 | proveedor INT(4))''')
52 | self.conn.commit()
53 |
54 | # Cerrar el cursor inicial y abrir uno nuevo con el parámetro dictionary=True
55 | self.cursor.close()
56 | self.conn.close()
57 |
58 | def conectar(self):
59 | try:
60 | self.conn = mysql.connector.connect(
61 | host=bd.host,
62 | user=bd.user,
63 | password=bd.password,
64 | database=bd.database
65 | )
66 | self.cursor = self.conn.cursor(dictionary=True)
67 | return "OK"
68 | except:
69 | return "Error al conectar con la base de datos."
70 |
71 | def desconectar(self):
72 | try:
73 | self.cursor.close()
74 | self.conn.close()
75 | return "OK"
76 | except:
77 | return "Error al cerrar la conexión."
78 |
79 | def listar_productos(self):
80 | self.conectar()
81 | self.cursor.execute("SELECT * FROM productos")
82 | productos = self.cursor.fetchall()
83 | self.desconectar()
84 | return productos
85 |
86 | def consultar_producto(self, codigo):
87 | # Consultamos un producto a partir de su código
88 | self.conectar()
89 | self.cursor.execute(f"SELECT * FROM productos WHERE codigo = {codigo}")
90 | self.desconectar()
91 | return self.cursor.fetchone()
92 |
93 | def mostrar_producto(self, codigo):
94 | # Mostramos los datos de un producto a partir de su código
95 | producto = self.consultar_producto(codigo)
96 | if producto:
97 | print("-" * 40)
98 | print(f"Código.....: {producto['codigo']}")
99 | print(f"Descripción: {producto['descripcion']}")
100 | print(f"Cantidad...: {producto['cantidad']}")
101 | print(f"Precio.....: {producto['precio']}")
102 | print(f"Imagen.....: {producto['imagen_url']}")
103 | print(f"Proveedor..: {producto['proveedor']}")
104 | print("-" * 40)
105 | else:
106 | print("Producto no encontrado.")
107 |
108 | def agregar_producto(self, descripcion, cantidad, precio, imagen, proveedor):
109 | self.conectar()
110 | sql = "INSERT INTO productos (descripcion, cantidad, precio, imagen_url, proveedor) VALUES (%s, %s, %s, %s, %s)"
111 | valores = (descripcion, cantidad, precio, imagen, proveedor)
112 |
113 | self.cursor.execute(sql,valores)
114 | self.conn.commit()
115 | self.desconectar()
116 | return self.cursor.lastrowid
117 |
118 | def modificar_producto(self, codigo, nueva_descripcion, nueva_cantidad, nuevo_precio, nueva_imagen, nuevo_proveedor):
119 | self.conectar()
120 | sql = "UPDATE productos SET descripcion = %s, cantidad = %s, precio = %s, imagen_url = %s, proveedor = %s WHERE codigo = %s"
121 | valores = (nueva_descripcion, nueva_cantidad, nuevo_precio, nueva_imagen, nuevo_proveedor, codigo)
122 |
123 | self.cursor.execute(sql, valores)
124 | self.conn.commit()
125 | self.desconectar()
126 | return self.cursor.rowcount > 0
127 |
128 | def eliminar_producto(self, codigo):
129 | self.conectar()
130 | # Eliminamos un producto de la tabla a partir de su código
131 | self.cursor.execute(f"DELETE FROM productos WHERE codigo = {codigo}")
132 | self.conn.commit()
133 | self.desconectar()
134 | return self.cursor.rowcount > 0
135 |
136 | #--------------------------------------------------------------------
137 | # Cuerpo del programa
138 | #--------------------------------------------------------------------
139 | # Crear una instancia de la clase Catalogo
140 |
141 | catalogo = Catalogo()
142 | # las variables con los datos de la conexion estan guardadas en el archivo datosconexion.py
143 |
144 |
145 | # Carpeta para guardar las imagenes
146 | ruta_destino = './static/imagenes/' # Reemplazar por los datos de Pythonanywhere
147 |
148 | #ruta_destino = '/home/maxisimonazzi/mysite/static/imagenes/'
149 |
150 | @app.route("/productos", methods=["GET"])
151 | def listar_productos():
152 | productos = catalogo.listar_productos()
153 | return jsonify(productos)
154 |
155 | @app.route("/productos/", methods=["GET"])
156 | def mostrar_producto(codigo):
157 | producto = catalogo.consultar_producto(codigo)
158 | if producto:
159 | return jsonify(producto)
160 | else:
161 | return "Producto no encontrado", 404
162 |
163 | @app.route("/productos", methods=["POST"])
164 | def agregar_producto():
165 | #Recojo los datos del form
166 | descripcion = request.form['descripcion']
167 | cantidad = request.form['cantidad']
168 | precio = request.form['precio']
169 | imagen = request.files['imagen']
170 | proveedor = request.form['proveedor']
171 | nombre_imagen = ""
172 |
173 | # Genero el nombre de la imagen
174 | nombre_imagen = secure_filename(imagen.filename)
175 | nombre_base, extension = os.path.splitext(nombre_imagen)
176 | nombre_imagen = f"{nombre_base}_{int(time.time())}{extension}"
177 |
178 | nuevo_codigo = catalogo.agregar_producto(descripcion, cantidad, precio, nombre_imagen, proveedor)
179 | if nuevo_codigo:
180 | imagen.save(os.path.join(ruta_destino, nombre_imagen))
181 | return jsonify({"mensaje": "Producto agregado correctamente.", "codigo": nuevo_codigo, "imagen": nombre_imagen}), 201
182 | else:
183 | return jsonify({"mensaje": "Error al agregar el producto."}), 500
184 |
185 | @app.route("/productos/", methods=["PUT"])
186 | def modificar_producto(codigo):
187 | #Se recuperan los nuevos datos del formulario
188 | nueva_descripcion = request.form.get("descripcion")
189 | nueva_cantidad = request.form.get("cantidad")
190 | nuevo_precio = request.form.get("precio")
191 | nuevo_proveedor = request.form.get("proveedor")
192 |
193 | # Verifica si se proporcionó una nueva imagen
194 | if 'imagen' in request.files:
195 | imagen = request.files['imagen']
196 | # Procesamiento de la imagen
197 | nombre_imagen = secure_filename(imagen.filename)
198 | nombre_base, extension = os.path.splitext(nombre_imagen)
199 | nombre_imagen = f"{nombre_base}_{int(time.time())}{extension}"
200 |
201 | # Guardar la imagen en el servidor
202 | imagen.save(os.path.join(ruta_destino, nombre_imagen))
203 |
204 | # Busco el producto guardado
205 | producto = catalogo.consultar_producto(codigo)
206 | if producto: # Si existe el producto...
207 | imagen_vieja = producto["imagen_url"]
208 | # Armo la ruta a la imagen
209 | ruta_imagen = os.path.join(ruta_destino, imagen_vieja)
210 |
211 | # Y si existe la borro.
212 | if os.path.exists(ruta_imagen):
213 | os.remove(ruta_imagen)
214 | else:
215 | producto = catalogo.consultar_producto(codigo)
216 | if producto:
217 | nombre_imagen = producto["imagen_url"]
218 |
219 | # Se llama al método modificar_producto pasando el codigo del producto y los nuevos datos.
220 | if catalogo.modificar_producto(codigo, nueva_descripcion, nueva_cantidad, nuevo_precio, nombre_imagen, nuevo_proveedor):
221 | return jsonify({"mensaje": "Producto modificado"}), 200
222 | else:
223 | return jsonify({"mensaje": "Producto no encontrado"}), 403
224 |
225 | @app.route("/productos/", methods=["DELETE"])
226 | def eliminar_producto(codigo):
227 | # Primero, obtiene la información del producto para encontrar la imagen
228 | producto = catalogo.consultar_producto(codigo)
229 | if producto:
230 | # Eliminar la imagen asociada si existe
231 | ruta_imagen = os.path.join(ruta_destino, producto['imagen_url'])
232 | if os.path.exists(ruta_imagen):
233 | os.remove(ruta_imagen)
234 |
235 | # Luego, elimina el producto del catálogo
236 | if catalogo.eliminar_producto(codigo):
237 | return jsonify({"mensaje": "Producto eliminado"}), 200
238 | else:
239 | return jsonify({"mensaje": "Error al eliminar el producto"}), 500
240 | else:
241 | return jsonify({"mensaje": "Producto no encontrado"}), 404
242 |
243 | if __name__ == "__main__":
244 | app.run(debug=True)
--------------------------------------------------------------------------------
/modificaciones.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Modificar Producto
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Modificar Productos del Inventario
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
Datos del Producto
30 |
56 |
57 |
58 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Proyecto de BackEnd para el programa Codo a Codo
2 |
3 | 
4 |
5 | ---
6 |
7 | **Tabla de contenido**
8 |
9 | - [Proyecto de BackEnd para el programa Codo a Codo](#proyecto-de-backend-para-el-programa-codo-a-codo)
10 | - [Descripción general](#descripción-general)
11 | - [Clonando el repositorio](#clonando-el-repositorio)
12 | - [Configuración](#configuración)
13 | - [Funcionamiento](#funcionamiento)
14 | - [BackEnd](#backend)
15 | - [API](#api)
16 | - [Base URL](#base-url)
17 | - [Endpoints](#endpoints)
18 | - [`GET` `/productos` `(Lista todos los productos cargados y sus propiedades)`](#get-productos-lista-todos-los-productos-cargados-y-sus-propiedades)
19 | - [`GET` `/productos/` `(Muestra las propiedades del producto seleccionado)`](#get-productosintcodigo-muestra-las-propiedades-del-producto-seleccionado)
20 | - [`POST` `/productos` `(Agrega un producto a la lista)`](#post-productos-agrega-un-producto-a-la-lista)
21 | - [`PUT` `/productos/` `(Modifica el producto seleccionado)`](#put-productosintcodigo-modifica-el-producto-seleccionado)
22 | - [`DELETE` `/productos/` `(Elimina el producto seleccionado)`](#delete-productosintcodigo-elimina-el-producto-seleccionado)
23 | - [Cerrando conexiones a la Base de datos](#cerrando-conexiones-a-la-base-de-datos)
24 | - [Usando API Key de PythonAnywhere para recargar el sitio](#usando-api-key-de-pythonanywhere-para-recargar-el-sitio)
25 | - [Endpoint](#endpoint)
26 | - [`POST` `/api/v0/user/{tu_usuario}/webapps/{tu_dominio}/reload/` `(Recarga la pagina web.)`](#post-apiv0usertu_usuariowebappstu_dominioreload-recarga-la-pagina-web)
27 | - [FrontEnd](#frontend)
28 | - [index.html](#indexhtml)
29 | - [listado.html](#listadohtml)
30 | - [altas.html](#altashtml)
31 | - [modificaciones.html](#modificacioneshtml)
32 | - [listadoEliminar.html](#listadoeliminarhtml)
33 |
34 |
35 | ## Descripción general
36 |
37 | El proyecto se trata de un sistema que permite administrar una base de datos de productos, implementado una API en Python utilizando el framework Flask. Las operaciones que se realizarán en este proyecto es dar de alta, modificar, eliminar y listar los productos, operaciones que podrán hacer los usuarios a través de una página Web.
38 |
39 | Como ejemplo trabajaremos con una empresa de venta de artículos de computación que ofrece sus productos a través de una Web.
40 |
41 | Aquí hay un resumen de las principales características y funcionalidades del proyecto:
42 |
43 | **Gestión de productos:**
44 | - Agregar un nuevo producto al catálogo.
45 | - Mostrar un listado de los productos en el catálogo.
46 | - Modificar la información de un producto existente en el catálogo.
47 | - Eliminar un producto del catálogo.
48 |
49 | **Persistencia de datos:**
50 | - Los datos de los productos se almacenan en una base de datos SQL.
51 | - Se utiliza una conexión a la base de datos para realizar operaciones CRUD en los productos.
52 | - El código proporcionado incluye las clases Catálogo, que representa la estructura y funcionalidad relacionada con el catálogo de productos. Además, se define una serie de rutas en Flask para manejar las solicitudes HTTP relacionadas con la gestión de productos.
53 |
54 | Se implementan desde cero el backend y el frontend. En el caso del backend, el proyecto va "evolucionando", comenzando en el desarrollo de las funciones que se necesitan para manipular los productos utilizando arreglos en memoria, luego se modifica para utilizar objetos, más tarde se gestiona la persistencia de los datos utilizando una base de datos SQL, se aloja el script Python en un servidor, y por último se crea un frontend básico para interactuar con los datos desde el navegador, a través de la API creada. Para este primer proyecto de BackEnd no vamos a utilizar entornos virtuales de ningun tipo ya que el proposito es solo entender los conceptos.
55 |
56 |
57 | El proyecto se divide en seis etapas:
58 |
59 | 1) Desarrollo de arreglos y funciones: Implementar un CRUD de productos utilizando arreglos y funciones.
60 | 2) Conversión a clases y objetos: Convertir las funciones vistas en objetos y clases.
61 | 3) Creación de la base de datos SQL: Utilizar como almacenamiento una base de datos SQL.
62 | 4) Implementación de la API en Flask: A través de este framework implementar una API que permita ser consumida desde el front.
63 | 5) Codificación del Front-End: Vistas que permitan realizar las operaciones del CRUD.
64 | 6) Despliegue en servidor PythonAnywhere: Hosting para aplicaciones web escritas en Python.
65 |
66 | ## Clonando el repositorio
67 |
68 | Para clonar este repo localmente, ejecuta en la terminal el siguiente comando.
69 |
70 | ```properties
71 | git clone https://github.com/maxisimonazzi/proyecto-fullstack-codo2024-1ercuatrimestre.git
72 | ```
73 |
74 | Si queres replicar este repo en tu cuenta de github podes hacer un fork al proyecto o sino crear un nuevo repo en tu perfil y luego agregar los archivos de tu repo local.
75 |
76 | ```properties
77 | #En tu repo local
78 | git init
79 | git add .
80 | git commit -m "primer commit"
81 | git branch -M main
82 | git remote add origin https://github.com/{tu_usuario}/tu_repo.git
83 | git push -u origin main
84 | ```
85 |
86 | ## Configuración
87 |
88 | La app hace uso del archivo **`datosconexion.py`** para obtener las variables de conexion a la base de datos y la API de PythonAnywhere. En el repo vas a encontrar el archivo **`datosconexion-template.py`** que tiene la siguiente estructura
89 |
90 | ```Python
91 | # Datos de acceso a la Base de Datos
92 | host='host'
93 | user='user'
94 | password='pass'
95 | database='database'
96 |
97 | # Credenciales API PythonAnywhere
98 | username = "xxxxxxx"
99 | api_token = "api_token"
100 | domain_name = "xxxxxxx.pythonanywhere.com"
101 | ```
102 |
103 | Reemplazar los datos para conectar con la base de datos segun corresponda, de igual manera los datos de la API Key (si se utiliza) y renombrar el archivo a **`datosconexion.py`** para que pueda ser importado correctamente por la app.
104 |
105 | Para obtener los datos de acceso a la Base de datos en PythonAnywhere nos dirigimos a la seccion Database
106 |
107 | 
108 |
109 | Para obtener la API Key en PythonAnywhere
110 |
111 | 
112 |
113 | ## Funcionamiento
114 |
115 | Explicaremos el funcionamiento dividido en dos partes, el FrontEnd y el BackEnd.
116 |
117 | ### BackEnd
118 |
119 | El archivo **`app.py`** contiene todo el backend sin modularizar. Lo primero que hay que hacer es especificar la ruta correcta donde seran guardadas las imagenes
120 |
121 | ```properties
122 | ruta_destino = './static/imagenes/'
123 | ```
124 |
125 | Esta debe ser cambiada por la ruta a utilizar en PythonAnywhere, que sera del tipo:
126 |
127 | ```properties
128 | ruta_destino = '/home/{tu_usuario}/mysite/static/imagenes/'
129 | ```
130 |
131 | Una vez realizado este cambio, podemos ver los metodos de la clase Catalogo como asi tambien los endpoints definidos en Flask.
132 |
133 | #### API
134 |
135 | ##### Base URL
136 |
137 | La URL base para tu API sera la que te entregue PythonAnywhere y dependera de tu nombre de usuario:
138 |
139 | ```properties
140 | https://{tu_usuario}.pythonanywhere.com/
141 | ```
142 |
143 | ##### Endpoints
144 |
145 |
146 |
147 |
148 | ###### `GET` `/productos` `(Lista todos los productos cargados y sus propiedades)`
149 |
150 |
151 | ###### Parametros
152 |
153 | > Ninguno
154 |
155 | ###### Respuesta
156 |
157 | > | Código HTTP | content-type | Respuesta |
158 | > | :---------- | :----------: | :-------: |
159 | > | 200 | application/json | JSON |
160 |
161 | ###### Ejemplo con cURL
162 | ```bash
163 | curl -X GET -H "Content-Type: application/json" https://{tu_usuario}.pythonanywhere.com/productos
164 | ```
165 |
166 |
167 |
168 |
169 |
170 | ###### `GET` `/productos/` `(Muestra las propiedades del producto seleccionado)`
171 |
172 |
173 | ###### Parametros
174 |
175 | > Ninguno
176 |
177 | ###### Respuesta
178 |
179 | > | Código HTTP | content-type | Respuesta |
180 | > | :---------- | :----------: | :-------: |
181 | > | 200 | application/json | JSON |
182 | > | 404 | application/json | JSON |
183 |
184 | ###### Ejemplo con cURL
185 | ```bash
186 | curl -X GET -H "Content-Type: application/json" https://{tu_usuario}.pythonanywhere.com/productos/1
187 | ```
188 |
189 |
190 |
191 |
192 |
193 | ###### `POST` `/productos` `(Agrega un producto a la lista)`
194 |
195 |
196 | ###### Parametros
197 |
198 | > | Nombre | Tipo | Tipo de dato | Descripción |
199 | > | :---------- | :----------: | :-------: | :------- |
200 | > | descripcion | requerido | string | Descripcion del producto |
201 | > | cantidad | requerido | int | Cantidad del producto a agregar |
202 | > | precio | requerido | float | Precio del producto a agregar |
203 | > | proveedor | requerido | int | Codigo del proveedor del producto a agregar |
204 | > | imagen | requerido | file | Imagen del producto a agregar |
205 |
206 | ###### Respuesta
207 |
208 | > | Código HTTP | content-type | Respuesta |
209 | > | :----------: | :----------: | :-------: |
210 | > | 201 | application/json | JSON |
211 | > | 400 | text/html; charset=utf-8 | None |
212 | > | 500 | application/json | JSON |
213 |
214 | ###### Ejemplo con cURL
215 | ```bash
216 | curl -X POST -H 'Content-Type: multipart/form-data' -F 'descripcion=Descripción del producto' -F 'cantidad=5' -F 'precio=10000' -F 'proveedor=123' -F imagen=@./static/imagenes/img.jpg https://{tu_usuario}.pythonanywhere.com/productos
217 | ```
218 |
219 |
220 |
221 |
222 |
223 | ###### `PUT` `/productos/` `(Modifica el producto seleccionado)`
224 |
225 |
226 | ###### Parametros
227 |
228 | > | Nombre | Tipo | Tipo de dato | Descripción |
229 | > | :---------- | :----------: | :-------: | :------- |
230 | > | descripcion | requerido | string | Descripcion del producto |
231 | > | cantidad | requerido | int | Cantidad del producto a agregar |
232 | > | precio | requerido | float | Precio del producto a agregar |
233 | > | proveedor | requerido | int | Codigo del proveedor del producto a agregar |
234 | > | imagen | requerido | file | Imagen del producto a agregar |
235 |
236 | ###### Respuesta
237 |
238 | > | Código HTTP | content-type | Respuesta |
239 | > | :----------: | :----------: | :-------: |
240 | > | 200 | application/json | JSON |
241 | > | 404 | application/json | JSON |
242 |
243 | ###### Ejemplo con cURL
244 | ```bash
245 | curl -X PUT -H 'Content-Type: multipart/form-data' -F 'descripcion=Descripción del producto' -F 'cantidad=5' -F 'precio=10000' -F 'proveedor=123' -F imagen=@./static/imagenes/img.jpg https://{tu_usuario}.pythonanywhere.com/productos
246 | ```
247 |
248 |
249 |
250 |
251 |
252 | ###### `DELETE` `/productos/` `(Elimina el producto seleccionado)`
253 |
254 |
255 | ###### Parametros
256 |
257 | > Ninguno
258 |
259 | ###### Respuesta
260 |
261 | > | Código HTTP | content-type | Respuesta |
262 | > | :----------: | :----------: | :-------: |
263 | > | 200 | application/json | JSON |
264 | > | 404 | application/json | JSON |
265 |
266 | ###### Ejemplo con cURL
267 | ```bash
268 | curl -X DELETE -H "Content-Type: application/json" https://{tu_usuario}.pythonanywhere.com/productos/1
269 | ```
270 |
271 |
272 | #### Cerrando conexiones a la Base de datos
273 |
274 | Por cuestionas meramente pedagogicas, en el archivo **`app.py`** se abre una conexion a la Base de datos para luego ejecutar todas las consultas necesarias. Esta conexion no se cierra nunca, y como es de esperarse, el motor de Base de datos cierra automaticamente la conexion luego de un tiempo de inactividad, por lo que la app devuelve un error cuando intenta consultar la base de datos.
275 |
276 | Para sobrellevar esto, implementamos la alternativa de abrir una conexion antes de ejecutar cualquier consulta y cerrarla luego de terminarla, de esta manera, las conexiones son efimeras y solo se conectara cuando se necesite hacer una consulta. Esto lo tenemos resuelto dentro del archivo **`app_closingconnection.py`**.
277 |
278 | Alli veremos que la clase Catalogo tiene dos metodos nuevos, uno llamado **`conectar`** y otro llamado **`desconectar`**. El primero abre una conexion a la Base de datos y el segundo cierra la conexion a la Base de datos. Al momento de ejecutar una consulta, primero llamamos al metodo de conectar y luego al metodo desconectar.
279 |
280 | #### Usando API Key de PythonAnywhere para recargar el sitio
281 |
282 | Muchas veces, la app alojada en PythonAnywhere deja de responder, ya sea por una limitacion de los recursos gratuitos o por que no se usa la implementacion de cerrar la conexion a la base de datos. En esas situaciones la pagina debe recargarse en PythonAnywhere haciendo click en el boton **`Reload`**. Esto implica tener que dirigirnos a la pagina, loguearnos y luego buscar el boton reload de forma manual, lo cual puede ser tedioso.
283 |
284 | Para solucionar esto, podemos realizar este procedimiento haciendo uso de la API de PythonAnywhere y hacer la recarga directamente desde una peticion.
285 |
286 | ##### Endpoint
287 |
288 |
289 |
290 |
291 | ###### `POST` `/api/v0/user/{tu_usuario}/webapps/{tu_dominio}/reload/` `(Recarga la pagina web.)`
292 |
293 |
294 | ###### Parametros
295 |
296 | > Ninguno
297 |
298 | ###### Cabcera
299 |
300 | > La cabecera a mandar debe ser 'Authorization: Token {tu_token}'
301 |
302 | ###### Respuesta
303 |
304 | > | Código HTTP | content-type | Respuesta |
305 | > | :----------: | :----------: | :-------: |
306 | > | 200 | application/json | JSON |
307 |
308 | ###### Ejemplo con cURL
309 | ```bash
310 | curl -X POST -H 'Authorization: Token {tu_token}' https://www.pythonanywhere.com/api/v0/user/{tu_usuario}/webapps/{nomre_de_dominio}/reload/
311 | ```
312 |
313 |
314 | La documentacion de la API la encuentran en el siguiente link https://help.pythonanywhere.com/pages/API/
315 |
316 | ### FrontEnd
317 |
318 | Los archivos que componen el mini FrontEnd son 5.
319 |
320 | ```properties
321 | - index.html
322 | - listado.html
323 | - altas.html
324 | - modificaciones.html
325 | - listadoEliminar.html
326 | ```
327 |
328 | #### index.html
329 |
330 | Esta página actúa como un menú o punto de entrada para distintas funciones de la aplicación web, cada una relacionada con el manejo de productos a través de una API. El uso de una tabla para organizar los enlaces proporciona una estructura clara y sencilla para la navegación.
331 |
332 | 
333 |
334 | #### listado.html
335 |
336 | Esta página es la encargada de mostrar un listado de productos. Crea una tabla de productos con sus detalles, utilizando JavaScript para realizar una solicitud al servidor y recuperar los datos de los productos, que luego agrega dinámicamente en la tabla. El script maneja tanto la recuperación exitosa de los datos como los posibles errores que puedan surgir durante el proceso.
337 |
338 | 
339 |
340 | #### altas.html
341 |
342 | Aqio podremos agregar productos al Catalogo. Incluye un formulario para introducir los detalles del producto y un script de JavaScript para manejar el envío de estos datos al servidor de manera asíncrona, sin recargar la página.
343 |
344 | 
345 |
346 | #### modificaciones.html
347 |
348 | Esta página web le permite al usuario obtener los detalles de un producto específico, modificar esos detalles y enviar los cambios al servidor.
349 |
350 | 
351 |
352 | #### listadoEliminar.html
353 |
354 | Esta página le permite al usuario obtener un listado de los productos, con la posibilidad de eliminarlos.
355 |
356 | 
357 |
358 | Estos archivos html pueden ser subidos a cualquier hosting/servidor gratuito. En clases vimos el ejemplo de subirlo a Netlify.
359 |
360 | Es importante que antes de subir los archivos al servidor, modificar las rutas en los html para que las peticiones las haga a la URL de nuestro app en PythonAnywhere, para ello modificamos la constante **`URL`** de la siguiente manera
361 |
362 | ```properties
363 | const URL = "https://{tu_usuario}.pythonanywhere.com/"
364 | ```
365 |
366 | De igual manera hay que cambiar las secciones donde tiene que buscar las imagenes de los productos, que al ser enviadas mediante un formulario a la app, las mismas son guardadas en PythonAnywhere, por ello debemos colocar su ruta. Para ello, debemos buscar en los html la siguiente parte
367 |
368 | ```properties
369 | src=./static/imagenes/
370 | ```
371 |
372 | Y modificarla por la correspondiente
373 |
374 | ```properties
375 | src=https://{tu_usuario}.pythonanywhere.com/static/imagenes/
376 | ```
377 |
378 | **Observaciones:** Dentro de los html veran dos maneras distintas de armar las tablas con los productos. Para ello se utilizan dos formas distintas de manipulacion del DOM. En el listado.html vamos a ver como se modifica el cuerpo de la tabla directamente.
379 |
380 | ```js
381 | document.getElementById('tablaProductos')
382 | ```
383 |
384 | En cambio en el listadoeliminar.html veremos que modificamos la tabla apuntando al TagName.
385 |
386 | ```js
387 | document.getElementById('productos-table').getElementsByTagName('tbody')[0];
388 | ```
389 |
390 | En ambos caso, lo que hacemos es modificar el cuerpo de la tabla y el resultado es similar.
--------------------------------------------------------------------------------