From 4915e75ba574befb1a505ef83c81df41f27c1ff7 Mon Sep 17 00:00:00 2001 From: jaylin <chieh.lin@rwth-aachen.de> Date: Fri, 1 Mar 2024 11:54:19 +0100 Subject: [PATCH] multiple database --- my_flask_app/__pycache__/main.cpython-39.pyc | Bin 14632 -> 8551 bytes my_flask_app/main.py | 556 ++++++++---------- .../models/__pycache__/models.cpython-39.pyc | Bin 4551 -> 4622 bytes my_flask_app/models/models.py | 12 +- my_flask_app/templates/app.html | 6 + my_flask_app/templates/select_database.html | 19 - my_flask_app/templates/set_database.html | 13 + 7 files changed, 269 insertions(+), 337 deletions(-) delete mode 100644 my_flask_app/templates/select_database.html create mode 100644 my_flask_app/templates/set_database.html diff --git a/my_flask_app/__pycache__/main.cpython-39.pyc b/my_flask_app/__pycache__/main.cpython-39.pyc index 7f1701b3638f7875b2bf7d8f5cf3b86b29fd50f6..7cff0738314b51c610d7db7ec14f520833d49a47 100644 GIT binary patch delta 5550 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 O42&H792^|{T#NwdKrny+ literal 14632 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 diff --git a/my_flask_app/main.py b/my_flask_app/main.py index 144e885..88190b4 100644 --- a/my_flask_app/main.py +++ b/my_flask_app/main.py @@ -1,50 +1,68 @@ from my_flask_app import app -from .models.models import Table, Column, Theme +from .models.models import CustomTable, CustomColumn, Theme from flask_sqlalchemy import SQLAlchemy -from flask import jsonify, render_template, request, session, send_file -from sqlalchemy import text -import re, pydot, base64 -import pydot +from flask import jsonify, redirect, render_template, request, url_for, session +from sqlalchemy import Inspector, MetaData, create_engine, text, inspect +import pydot, base64, os +from sqlalchemy.orm import scoped_session, sessionmaker -# Set up database +# Set up database (call db.engine) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test" +# app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test" db = SQLAlchemy() -db.init_app(app) +# db.init_app(app) app.secret_key = 'my_secret_key' # Needed for session management dropped_items = [] @app.route('/', methods=['POST', 'GET']) def index(): - # Initialize variables - database = db.engine.url.database - tables_selected = [] - # tables_selected = ['machine_sensor', 'machine_tool', 'machine_trace'] - schemas = getSchema() - themes = getThemes() + if request.method == 'POST': + # Handle the form submission + database_uri = request.form['database_uri'] + session['db_uri'] = database_uri + engine = create_engine(database_uri) + session_factory = sessionmaker(bind=engine) + db.session = scoped_session(session_factory) + insp = inspect(engine) + metadata_obj = MetaData() + + # Determine the type and naem of database + database_type = engine.dialect.name # postgresql, sqlite + database = database_name_from_uri(engine, database_uri) + # Initialize variables + tables_selected = [] + schemas = getSchema(insp) + themes = getThemes() + + schema_Selected = request.form.get('schema', None) + show_all = request.form.get('show_all') == 'True' + + tables1 = importMetadata(engine, schema_Selected, tables_selected, show_all) + # graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) + # image1 = generate_erd(graph_DOT1) + + if dropped_items==[]: + image2 = "" + # else: + # tables2 = importMetadata(engine, None, dropped_items, False) + # graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) + # image2 = generate_erd(graph_DOT2) + + # return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items) + - schema_Selected = request.form.get('schema', None) - show_all = request.form.get('show_all') == 'True' - tables1 = importMetadata(database, schema_Selected, tables_selected, show_all) - graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) - image1 = generate_erd(graph_DOT1) + # print(insp.get_foreign_keys('machine_sensor')) + # print(insp.get_unique_constraints('segmentation_data', schema='segmentation')) - if dropped_items==[]: - image2 = "" + return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, dropped_items=dropped_items) else: - tables2 = importMetadata(database, None, dropped_items, False) - graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) - image2 = generate_erd(graph_DOT2) - - print(getTableSchema('event_data')) - - return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items) - - + # Display the form + return render_template('app.html') + @app.route('/handle-drop', methods=['POST']) def handle_drop(): @@ -67,8 +85,6 @@ def handle_drop(): return jsonify(image2=image2) - - @app.route('/get-table-data', methods=['POST']) def get_table_data(): data = request.json @@ -83,6 +99,15 @@ def get_table_data(): return jsonify({'html_table': html_table}) + +def database_name_from_uri(engine, database_uri: str): + if engine.dialect.name == 'postgresql': + return engine.url.database + elif engine.dialect.name == 'sqlite': + return os.path.splitext(os.path.basename(database_uri.split('///')[-1]))[0] + else: + return 'Unknown' + def generate_html_table(content): if not content: return "No data found." @@ -104,7 +129,6 @@ def generate_html_table(content): return table_html - def query_database_for_table_content(table_name, number=20): # Initialize content list content_list = [] @@ -136,6 +160,7 @@ def query_database_for_table_content(table_name, number=20): return content_list +# Only postgresql needs this function (database_type = 'postgresql') def getTableSchema(table_name): sql= text(f""" SELECT table_schema @@ -144,214 +169,256 @@ def getTableSchema(table_name): """) schema = db.session.execute(sql, {'table_name': table_name}).fetchone()[0] return schema + - - -def getSchema(): - sql = text("""SELECT schema_name FROM information_schema.schemata;""") - result = db.session.execute(sql) - schemas = [row[0] for row in result] +def getSchema(insp): + # sql = text("""SELECT schema_name FROM information_schema.schemata;""") + # result = db.session.execute(sql) + # schemas = [row[0] for row in result] + schemas = insp.get_schema_names() return schemas -def importMetadata(database, schema=None, tables_selected=None, show_all=False): +def importMetadata(engine, schema=None, tables_selected=None, show_all=False): tables = {} - if database == '': + if engine == None: return tables # Convert tables_selected to a list to ensure compatibility with SQL IN operation. tables_selected_list = list(tables_selected) if tables_selected else None # Fetch initial tables based on schema and table_names. - tables = fetch_initial_tables(schema, tables_selected_list) + tables = fetch_initial_tables(engine, schema, tables_selected_list) # If show_all is True, expand the list to include related tables. if show_all: - expand_to_include_related_tables(tables) + expand_to_include_related_tables(engine, tables) # Fetch columns for each table. - fetch_columns_for_tables(tables) + fetch_columns_for_tables(engine, tables) # Fetch constraints (PK, FK, Unique) for each table. - fetch_constraints_for_tables(tables) + fetch_constraints_for_tables(engine, tables) return tables -def fetch_initial_tables(schema, tables_selected_list): +def fetch_initial_tables(engine, schema=None, tables_selected_list=None): + if isinstance(engine, str): + engine = create_engine(engine) tables = {} - - # Construct WHERE clauses based on input parameters. - schema_condition = "AND table_schema = :schema" if schema else "" - tables_selected_condition = "AND table_name = ANY(:tables_selected)" if tables_selected_list else "" - - # Fetching tables with dynamic schema and table name filtering. - sql_tables = text(f""" - SELECT table_name, table_schema - FROM information_schema.tables - WHERE table_type = 'BASE TABLE' - {schema_condition} {tables_selected_condition}; - """) - - # Adjust parameters based on the conditions. - params = {} - if schema: - params['schema'] = schema + insp = inspect(engine) + database_type = engine.dialect.name + + # Get all table names in the database (or specific schema for PostgreSQL) + all_tables = [] + if schema!=None and database_type == 'postgresql': + all_tables = insp.get_table_names(schema=schema) + elif schema==None and database_type == 'postgresql': + for schema_of_schemas in insp.get_schema_names(): + for table_name in insp.get_table_names(schema=schema_of_schemas): + all_tables.append(table_name) + else: # For SQLite + all_tables = insp.get_table_names() + + # Filter tables if a specific list is provided if tables_selected_list: - params['tables_selected'] = tables_selected_list - - result = db.session.execute(sql_tables, params) + table_names = [table for table in all_tables if table in tables_selected_list] + else: + table_names = all_tables - for row in result: - tableName, tableSchema = row - table = Table(tableName, tableSchema) # adding schema to the table comment - tables[tableName] = table + for table_name in table_names: + # For PostgreSQL, use the provided schema, otherwise use the default schema + table_schema = getTableSchema(table_name) if database_type == 'postgresql' else insp.default_schema_name + table = CustomTable(table_name, table_schema) + tables[table_name] = table table.label = f"n{len(tables)}" return tables -def expand_to_include_related_tables(tables): +def expand_to_include_related_tables(engine, tables): + if isinstance(engine, str): + engine = create_engine(engine) + # Create an inspector object + insp = inspect(engine) + # This dictionary will temporarily store related tables to fetch later. related_tables_to_fetch = {} # Iterate over initially fetched tables to find foreign key relationships. for tableName, table in tables.items(): - # Fetch foreign key relationships for the current table. - sql_fk = text(""" - SELECT - ccu.table_name AS pk_table_name, - ccu.table_schema AS pk_table_schema - FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - WHERE - tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = :table_name - """) - fk_result = db.session.execute(sql_fk, {'table_name': tableName}) + # Fetch foreign key relationships for the current table using the inspector. + fks = insp.get_foreign_keys(tableName, schema=table.schema) + for fk in fks: + referenced_table_name = fk['referred_table'] + referenced_schema = fk['referred_schema'] - - for referenced_table_name, referenced_schema in fk_result: if referenced_table_name not in tables and referenced_table_name not in related_tables_to_fetch: related_tables_to_fetch[referenced_table_name] = referenced_schema # Fetch and add related tables. for tableName, tableSchema in related_tables_to_fetch.items(): - # Assuming a function fetch_table_details(tableName, tableSchema) that fetches and returns - # a Table object with columns and constraints populated. - table = Table(tableName, tableSchema) + # Create a CustomTable object for each related table. + table = CustomTable(tableName, tableSchema) tables[tableName] = table return tables -def fetch_columns_for_tables(tables): - for tableName, table in tables.items(): - sql_columns = text(""" - SELECT column_name, data_type, is_nullable, column_default - FROM information_schema.columns - WHERE table_name = :table_name; - """) - column_result = db.session.execute(sql_columns, {'table_name': tableName}) +def fetch_columns_for_tables(engine, tables): + if isinstance(engine, str): + engine = create_engine(engine) + insp = inspect(engine) - for col in column_result: - name, datatype, nullable, default = col - column = Column(table, name, '') + for tableName, table in tables.items(): + # Use the inspector to get column information for each table + columns = insp.get_columns(tableName, schema=table.schema) + for col in columns: + name = col['name'] + datatype = col['type'] + nullable = col['nullable'] + default = col['default'] + + # Create a CustomColumn object with the retrieved information + column = CustomColumn(table, name, '') column.setDataType({ - "type": datatype, - "nullable": nullable == 'YES', + "type": str(datatype), + "nullable": nullable, "default": default }) + + # Append the column to the table's columns list table.columns.append(column) - return tables + return tables + +def fetch_constraints_for_tables(engine, tables): + if isinstance(engine, str): + engine = create_engine(engine) + insp = inspect(engine) -def fetch_constraints_for_tables(tables): # Fetching Unique Constraints for tableName, table in tables.items(): - sql_unique = text(""" - SELECT kcu.column_name, tc.constraint_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name - WHERE tc.table_name = :table_name AND tc.constraint_type = 'UNIQUE'; - """) - unique_result = db.session.execute(sql_unique, {'table_name': tableName}) - for col in unique_result: - name, constraintName = col - column = table.getColumn(name) - if column: - column.isunique = True - if constraintName not in table.uniques: - table.uniques[constraintName] = [] - table.uniques[constraintName].append(column) - + unique_constraints = insp.get_unique_constraints(tableName, schema=table.schema) + for uc in unique_constraints: + for column_name in uc['column_names']: + column = table.getColumn(column_name) + if column: + column.isunique = True + if uc['name'] not in table.uniques: + table.uniques[uc['name']] = [] + table.uniques[uc['name']].append(column) # Primary Keys for tableName, table in tables.items(): - sql_pk = text(""" - SELECT kcu.column_name, tc.constraint_name, kcu.ordinal_position - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name - WHERE tc.table_name = :table_name AND tc.constraint_type = 'PRIMARY KEY'; - """) - pk_result = db.session.execute(sql_pk, {'table_name': tableName}) - for col in pk_result: - name, constraintName, ordinal_position = col - column = table.getColumn(name) + pk_constraint = insp.get_pk_constraint(tableName, schema=table.schema) + for column_name in pk_constraint['constrained_columns']: + column = table.getColumn(column_name) if column: column.ispk = True - column.pkconstraint = constraintName - # Assuming you want to order PKs, though not directly used in provided class + column.pkconstraint = pk_constraint['name'] - - # Fetching Foreign Keys for each table + # Foreign Keys for tableName, table in tables.items(): - sql_fk = text(""" - SELECT - tc.constraint_name, - tc.table_name AS fk_table_name, - kcu.column_name AS fk_column_name, - ccu.table_name AS pk_table_name, - ccu.column_name AS pk_column_name, - ccu.table_schema AS pk_table_schema - FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - WHERE - tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = :table_name - """) - - fk_result = db.session.execute(sql_fk, {'table_name': tableName}) - for row in fk_result: - constraintName, fkTableName, fkColumnName, pkTableName, pkColumnName, pkTableSchema = row + fks = insp.get_foreign_keys(tableName, schema=table.schema) + for fk in fks: + fk_columns = fk['constrained_columns'] + referred_table = fk['referred_table'] + referred_columns = fk['referred_columns'] + for fk_column, ref_column in zip(fk_columns, referred_columns): + column = table.getColumn(fk_column) + if column: + column.fkof = f"{referred_table}.{ref_column}" + if fk['name'] not in table.fks: + table.fks[fk['name']] = [] + table.fks[fk['name']].append(column) - # Ensure the foreign key table is the current table being processed - if fkTableName != tableName: - continue - - - fkTable = tables.get(fkTableName) - pkTable = tables.get(pkTableName) + return tables - if fkTable and pkTable: - fkColumn = fkTable.getColumn(fkColumnName) - pkColumn = pkTable.getColumn(pkColumnName) - if fkColumn and pkColumn: - # Here, instead of assigning pkColumn directly, store relevant info - fkColumn.fkof = pkColumn # Adjust based on your application's needs - if constraintName not in fkTable.fks: - fkTable.fks[constraintName] = [] - fkTable.fks[constraintName].append(fkColumn) - return tables +# def fetch_constraints_for_tables(engine, tables): +# # Fetching Unique Constraints +# for tableName, table in tables.items(): +# sql_unique = text(""" +# SELECT kcu.column_name, tc.constraint_name +# FROM information_schema.table_constraints AS tc +# JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name +# WHERE tc.table_name = :table_name AND tc.constraint_type = 'UNIQUE'; +# """) +# unique_result = db.session.execute(sql_unique, {'table_name': tableName}) +# for col in unique_result: +# name, constraintName = col +# column = table.getColumn(name) +# if column: +# column.isunique = True +# if constraintName not in table.uniques: +# table.uniques[constraintName] = [] +# table.uniques[constraintName].append(column) + + +# # Primary Keys +# for tableName, table in tables.items(): +# sql_pk = text(""" +# SELECT kcu.column_name, tc.constraint_name, kcu.ordinal_position +# FROM information_schema.table_constraints AS tc +# JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name +# WHERE tc.table_name = :table_name AND tc.constraint_type = 'PRIMARY KEY'; +# """) +# pk_result = db.session.execute(sql_pk, {'table_name': tableName}) +# for col in pk_result: +# name, constraintName, ordinal_position = col +# column = table.getColumn(name) +# if column: +# column.ispk = True +# column.pkconstraint = constraintName +# # Assuming you want to order PKs, though not directly used in provided class + + +# # Fetching Foreign Keys for each table +# for tableName, table in tables.items(): +# sql_fk = text(""" +# SELECT +# tc.constraint_name, +# tc.table_name AS fk_table_name, +# kcu.column_name AS fk_column_name, +# ccu.table_name AS pk_table_name, +# ccu.column_name AS pk_column_name, +# ccu.table_schema AS pk_table_schema +# FROM +# information_schema.table_constraints AS tc +# JOIN information_schema.key_column_usage AS kcu +# ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name +# JOIN information_schema.constraint_column_usage AS ccu +# ON ccu.constraint_name = tc.constraint_name +# WHERE +# tc.constraint_type = 'FOREIGN KEY' +# AND tc.table_name = :table_name +# """) + +# fk_result = db.session.execute(sql_fk, {'table_name': tableName}) +# for row in fk_result: +# constraintName, fkTableName, fkColumnName, pkTableName, pkColumnName, pkTableSchema = row + +# # Ensure the foreign key table is the current table being processed +# if fkTableName != tableName: +# continue + + +# fkTable = tables.get(fkTableName) +# pkTable = tables.get(pkTableName) + +# if fkTable and pkTable: +# fkColumn = fkTable.getColumn(fkColumnName) +# pkColumn = pkTable.getColumn(pkColumnName) + +# if fkColumn and pkColumn: +# # Here, instead of assigning pkColumn directly, store relevant info +# fkColumn.fkof = pkColumn # Adjust based on your application's needs +# if constraintName not in fkTable.fks: +# fkTable.fks[constraintName] = [] +# fkTable.fks[constraintName].append(fkColumn) +# return tables def createGraph(tables, theme, showColumns, showTypes, useUpperCase): @@ -393,139 +460,4 @@ def getThemes(): if __name__ == "__main__": - app.run(port=5001) - - -class Theme: - def __init__(self, color, fillcolor, fillcolorC, - bgcolor, icolor, tcolor, style, shape, pencolor, penwidth): - self.color = color - self.fillcolor = fillcolor - self.fillcolorC = fillcolorC - self.bgcolor = bgcolor - self.icolor = icolor - self.tcolor = tcolor - self.style = style - self.shape = shape - self.pencolor = pencolor - self.penwidth = penwidth - - -class Table: - def __init__(self, name, comment): - self.name = name - self.comment = comment if comment is not None and comment != 'None' else '' - self.label = None - - self.columns = [] # list of all columns - self.uniques = {} # dictionary with UNIQUE constraints, by name + list of columns - self.pks = [] # list of PK columns (if any) - self.fks = {} # dictionary with FK constraints, by name + list of FK columns - - - @classmethod - def getClassName(cls, name, useUpperCase, withQuotes=True): - if re.match("^[A-Z_0-9]*$", name) == None: - return f'"{name}"' if withQuotes else name - return name.upper() if useUpperCase else name.lower() - - def getName(self, useUpperCase, withQuotes=True): - return Table.getClassName(self.name, useUpperCase, withQuotes) - - def getColumn(self, name): - for column in self.columns: - if column.name == name: - return column - return None - - def getDotShape(self, theme, showColumns, showTypes, useUpperCase): - fillcolor = theme.fillcolorC if showColumns else theme.fillcolor - colspan = "2" if showTypes else "1" - tableName = self.getName(useUpperCase, False) - - s = (f' {self.label} [\n' - + f' fillcolor="{fillcolor}" color="{theme.color}" penwidth="1"\n' - + f' label=<<table style="{theme.style}" border="0" cellborder="0" cellspacing="0" cellpadding="1">\n' - + f' <tr><td bgcolor="{theme.bgcolor}" align="center"' - + f' colspan="{colspan}"><font color="{theme.tcolor}"><b>{tableName}</b></font></td></tr>\n') - - if showColumns: - for column in self.columns: - name = column.getName(useUpperCase, False) - if column.ispk: name = f"<u>{name}</u>" - if column.fkof != None: name = f"<i>{name}</i>" - if column.nullable: name = f"{name}*" - if column.identity: name = f"{name} I" - if column.isunique: name = f"{name} U" - datatype = column.datatype - if useUpperCase: datatype = datatype.upper() - - if showTypes: - s += (f' <tr><td align="left"><font color="{theme.icolor}">{name} </font></td>\n' - + f' <td align="left"><font color="{theme.icolor}">{datatype}</font></td></tr>\n') - else: - s += f' <tr><td align="left"><font color="{theme.icolor}">{name}</font></td></tr>\n' - - return s + ' </table>>\n ]\n' - - - def getDotLinks(self, theme): - s = "" - for constraint in self.fks: - fks = self.fks[constraint] - fk1 = fks[0] - dashed = "" if not fk1.nullable else ' style="dashed"' - arrow = "" if fk1.ispk and len(self.pks) == len(fk1.fkof.table.pks) else ' arrowtail="crow"' - s += (f' {self.label} -> {fk1.fkof.table.label}' - + f' [ penwidth="{theme.penwidth}" color="{theme.pencolor}"{dashed}{arrow} ]\n') - return s - - -class Column: - def __init__(self, table, name, comment): - self.table = table - self.name = name - self.comment = comment if comment is not None and comment != 'None' else '' - self.nullable = True - self.datatype = None # with (length, or precision/scale) - self.identity = False - - self.isunique = False - self.ispk = False - self.pkconstraint = None - self.fkof = None # points to the PK column on the other side - - - def getName(self, useUpperCase, withQuotes=True): - return Table.getClassName(self.name, useUpperCase, withQuotes) - - - def setDataType(self, datatype): - self.datatype = datatype["type"] - self.nullable = bool(datatype["nullable"]) - - if self.datatype == "FIXED": - self.datatype = "NUMBER" - elif "fixed" in datatype: - fixed = bool(datatype["fixed"]) - if self.datatype == "TEXT": - self.datatype = "CHAR" if fixed else "VARCHAR" - - if "length" in datatype: - self.datatype += f"({str(datatype['length'])})" - elif "scale" in datatype: - if int(datatype['precision']) == 0: - self.datatype += f"({str(datatype['scale'])})" - if self.datatype == "TIMESTAMP_NTZ(9)": - self.datatype = "TIMESTAMP" - elif "scale" in datatype and int(datatype['scale']) == 0: - self.datatype += f"({str(datatype['precision'])})" - if self.datatype == "NUMBER(38)": - self.datatype = "INT" - elif self.datatype.startswith("NUMBER("): - self.datatype = f"INT({str(datatype['precision'])})" - elif "scale" in datatype: - self.datatype += f"({str(datatype['precision'])},{str(datatype['scale'])})" - #if column.datatype.startswith("NUMBER("): - # column.datatype = f"FLOAT({str(datatype['precision'])},{str(datatype['scale'])})" - self.datatype = self.datatype.lower() + app.run(debug=True) \ No newline at end of file diff --git a/my_flask_app/models/__pycache__/models.cpython-39.pyc b/my_flask_app/models/__pycache__/models.cpython-39.pyc index 52c1845341f74497b1ac419ea9d8e8fb62401df8..922505e8e007a16202bcb47fb6b19cc560709207 100644 GIT binary patch delta 432 zcmX@E+^52u$ji&cz`(#zz3E}9?MB`ij4F=$p~b01#rnw^nW-82E~&-YCHV#VxrwPM z@u?LhMTyBJ`9+(LGx9UCb32z7m*nS8zRpz1&Q_e9k(!%0If6NXT?8t=lleKj1eCpy zrJG$C%GPC_$1VzCZ$86%mzgnY@+!_|c5$eX9#;~hrVRrFLljqXeqM1&QDSCZNs$~# zfH^JOFp4cDu{a|&C5kn%s3^aD@_A12$rrelVcI8u;r3>;Wnf?^a+vJOQ_s!=F?I4$ zo-QGKkgx;;1H&!$<ow*+)Vz|(vAibSaN#27$#Zy3VQ!mzi8mMKm(31*dl;3YSlv7$ zTwQLl`GxvAxdz>0P0OrEO^IR&ag7LxVsZ9x42qg8FCfMkHQ7u+gE4Ayyg)jm`{d&S zk?>$p6g1Kc0=a@8l@I`NofsGxA{ZDLirE+#7&sU?__$b#oF+F4iZVJ)o-Swr0DujC A!2kdN delta 352 zcmeBEIj+o`$ji&cz`(#z(7!HKawG2yMkRaw(BjmhV*TWd%+w5hm(=3ylKcYw+{*a0 zoW$bn_{4&O%_kZ885vn8-)E}iU{B7^%}vcKnH<lYz{ofG81r*Rp~+iVx*2&VyRpt= z<lp?7^)55xt;r`jn;8W+2XiGcD%&zJFx=uw&d)0@DN4-DD=Cr#2{5N+8{T3|Ni5Dt zO}RDs2DbyF;AD9oZ#Fvy28JTn$pt+1jBJy?@pSPyfCMB!gd_t4!{kZ4CfrCulP~g` zGV)L6<;!K{+uX>vhf(<!tD9$ptII7mzffN%*PvUhX_*zNDYsZcTq8nmu{e7;2Hl#R zARxwgYjU}O2IH;C3k1>`eI|1VMluRb&JZ+WjG0`(Bcbiiz`zi}z`#(<#=yY9!N|eK e#Zm+k(_|@90J((;M2Lb2_sIo3;*-A$N&*0#JYpmO diff --git a/my_flask_app/models/models.py b/my_flask_app/models/models.py index 5092e14..118e0c5 100644 --- a/my_flask_app/models/models.py +++ b/my_flask_app/models/models.py @@ -15,10 +15,10 @@ class Theme: self.penwidth = penwidth -class Table(): - def __init__(self, name, comment): +class CustomTable(): + def __init__(self, name, schema): self.name = name - self.comment = comment if comment is not None and comment != 'None' else '' + self.schema = schema if schema is not None and schema != 'None' else '' # table schema self.label = None self.columns = [] # list of all columns @@ -34,7 +34,7 @@ class Table(): return name.upper() if useUpperCase else name.lower() def getName(self, useUpperCase, withQuotes=True): - return Table.getClassName(self.name, useUpperCase, withQuotes) + return CustomTable.getClassName(self.name, useUpperCase, withQuotes) def getColumn(self, name): for column in self.columns: @@ -85,7 +85,7 @@ class Table(): return s -class Column: +class CustomColumn: def __init__(self, table, name, comment): self.table = table self.name = name @@ -101,7 +101,7 @@ class Column: def getName(self, useUpperCase, withQuotes=True): - return Table.getClassName(self.name, useUpperCase, withQuotes) + return CustomTable.getClassName(self.name, useUpperCase, withQuotes) def setDataType(self, datatype): diff --git a/my_flask_app/templates/app.html b/my_flask_app/templates/app.html index b7f13c2..99e8ff9 100644 --- a/my_flask_app/templates/app.html +++ b/my_flask_app/templates/app.html @@ -383,6 +383,12 @@ </li> <!-- Add more items as needed --> </ul> + + <!-- set_database.html --> + <form method="post" action="/"> + <input type="text" name="database_uri" placeholder="Enter Database URI"> + <input type="submit" value="Set Database"> + </form> <div id="resize-handle-left" class="resize-handle-left"></div> </div> diff --git a/my_flask_app/templates/select_database.html b/my_flask_app/templates/select_database.html deleted file mode 100644 index 98135dc..0000000 --- a/my_flask_app/templates/select_database.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <title>Select Database</title> - <!-- Add some CSS for styling --> -</head> -<body> - <div> - <!-- <h3>Current Database: {{ current_db }}</h3> --> - <h3>Select a Database</h3> - <ul> - {% for db in databases %} - <li>{{ db }}</li> - {% endfor %} - </ul> - </div> -</body> -</html> diff --git a/my_flask_app/templates/set_database.html b/my_flask_app/templates/set_database.html new file mode 100644 index 0000000..b3337a2 --- /dev/null +++ b/my_flask_app/templates/set_database.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> + <title>Set Database</title> +</head> +<body> + <form method="POST" action="/set-database"> + <label for="database_uri">Database URI:</label> + <input type="text" name="database_uri" id="database_uri"> + <input type="submit" value="Set Database"> + </form> +</body> +</html> \ No newline at end of file -- GitLab