Skip to content
Snippets Groups Projects
Commit ad660234 authored by Chieh Lin's avatar Chieh Lin
Browse files

multiple database fix ERD

parent 4915e75b
No related branches found
No related tags found
No related merge requests found
Pipeline #367085 failed
No preview for this file type
from my_flask_app import app
from .models.models import CustomTable, CustomColumn, Theme
from .models.models import CustomTable, CustomColumn, Theme, CompressedDataType, RegType, RegRole
from flask_sqlalchemy import SQLAlchemy
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 flask import jsonify, redirect, render_template, request, session, url_for
from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect
import pydot, base64, os, logging
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.dialects.postgresql.base import ischema_names
from sqlalchemy.dialects.postgresql import JSONB, TSTZRANGE, INTERVAL, BYTEA
# Set up database (call db.engine)
......@@ -14,55 +16,79 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy()
# db.init_app(app)
app.secret_key = 'my_secret_key' # Needed for session management
dropped_items = []
# Register the custom type with SQLAlchemy's PostgreSQL dialect
ischema_names['_timescaledb_internal.compressed_data'] = CompressedDataType
ischema_names['regtype'] = RegType
ischema_names['regrole'] = RegRole
@app.errorhandler(400)
def log_bad_request(error):
logging.error(f"Bad request: {request} {request.data} {request.args}")
# You can add more request details to log here
return "Bad request", 400
@app.route('/', methods=['POST', 'GET'])
def index():
try:
print("1")
if request.method == 'POST':
# Handle the form submission
database_uri = request.form['database_uri']
print("2")
database_uri = request.form.get('database_uri')
print(database_uri)
if database_uri != '' and database_uri != None:
print("3")
if database_uri != session.get('db_uri', ''):
session['db_uri'] = database_uri
print("4")
# Initialize inspector here, outside the inner if-else
print("4.5")
engine = create_engine(database_uri)
insp = inspect(engine)
session_factory = sessionmaker(bind=engine)
db.session = scoped_session(session_factory)
insp = inspect(engine)
metadata_obj = MetaData()
print("5")
# 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()
else:
database_uri = session.get('db_uri', '')
engine = create_engine(database_uri)
insp = inspect(engine)
session_factory = sessionmaker(bind=engine)
db.session = scoped_session(session_factory)
print("6")
print("6.5")
database = database_name_from_uri(engine, database_uri) if database_uri != '' else ''
schemas = getSchema(insp) if session['db_uri'] != '' else []
themes = getThemes()
tables_selected = []
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)
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)
else:
tables2 = importMetadata(engine, None, dropped_items, False)
graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True)
image2 = generate_erd(graph_DOT2)
# print(insp.get_foreign_keys('machine_sensor'))
# print(insp.get_unique_constraints('segmentation_data', schema='segmentation'))
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)
return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, dropped_items=dropped_items)
else:
# Display the form
return render_template('app.html')
except Exception as e:
return f"An error occurred: {str(e)}", 400
@app.route('/handle-drop', methods=['POST'])
def handle_drop():
......@@ -108,6 +134,7 @@ def database_name_from_uri(engine, database_uri: str):
else:
return 'Unknown'
def generate_html_table(content):
if not content:
return "No data found."
......@@ -192,13 +219,13 @@ def importMetadata(engine, schema=None, tables_selected=None, show_all=False):
# If show_all is True, expand the list to include related tables.
if show_all:
expand_to_include_related_tables(engine, tables)
tables = expand_to_include_related_tables(engine, tables)
# Fetch columns for each table.
fetch_columns_for_tables(engine, tables)
tables = fetch_columns_for_tables(engine, tables)
# Fetch constraints (PK, FK, Unique) for each table.
fetch_constraints_for_tables(engine, tables)
tables = fetch_constraints_for_tables(engine, tables)
return tables
......@@ -222,6 +249,7 @@ def fetch_initial_tables(engine, schema=None, tables_selected_list=None):
all_tables = insp.get_table_names()
# Filter tables if a specific list is provided
table_names = []
if tables_selected_list:
table_names = [table for table in all_tables if table in tables_selected_list]
else:
......@@ -242,6 +270,7 @@ def expand_to_include_related_tables(engine, tables):
engine = create_engine(engine)
# Create an inspector object
insp = inspect(engine)
database_type = engine.dialect.name
# This dictionary will temporarily store related tables to fetch later.
related_tables_to_fetch = {}
......@@ -252,7 +281,8 @@ def expand_to_include_related_tables(engine, tables):
fks = insp.get_foreign_keys(tableName, schema=table.schema)
for fk in fks:
referenced_table_name = fk['referred_table']
referenced_schema = fk['referred_schema']
# default schema is 'public' for postgresql
referenced_schema = insp.default_schema_name if fk['referred_schema']==None else fk['referred_schema']
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
......@@ -262,6 +292,7 @@ def expand_to_include_related_tables(engine, tables):
# Create a CustomTable object for each related table.
table = CustomTable(tableName, tableSchema)
tables[tableName] = table
table.label = f"n{len(tables)}"
return tables
......@@ -277,19 +308,61 @@ def fetch_columns_for_tables(engine, tables):
for col in columns:
name = col['name']
datatype = col['type']
if isinstance(datatype, UUID):
datatype = "UUID"
elif isinstance(datatype, INTEGER):
datatype = "INTEGER"
elif isinstance(datatype, JSONB):
datatype = "JSONB"
elif isinstance(datatype, JSON):
datatype = "JSON"
elif isinstance(datatype, TIMESTAMP):
datatype = "TIMESTAMP"
elif isinstance(datatype, TSTZRANGE):
datatype = "TSTZRANGE"
elif isinstance(datatype, VARCHAR):
datatype = "VARCHAR"
elif isinstance(datatype, BOOLEAN):
datatype = "BOOLEAN"
elif isinstance(datatype, NUMERIC):
datatype = "NUMERIC"
elif isinstance(datatype, FLOAT):
datatype = "FLOAT"
elif isinstance(datatype, String):
datatype = "String"
elif isinstance(datatype, SMALLINT):
datatype = "SMALLINT"
elif isinstance(datatype, BIGINT):
datatype = "BIGINT"
elif isinstance(datatype, RegType):
datatype = "RegType"
elif isinstance(datatype, RegRole):
datatype = "RegRole"
elif isinstance(datatype, CompressedDataType):
datatype = "CompressedDataType"
elif isinstance(datatype, ARRAY):
datatype = "ARRAY"
elif isinstance(datatype, INTERVAL):
datatype = "INTERVAL"
elif isinstance(datatype, DOUBLE_PRECISION):
datatype = "DOUBLE_PRECISION"
elif isinstance(datatype, BYTEA):
datatype = "BYTEA"
nullable = col['nullable']
default = col['default']
# Create a CustomColumn object with the retrieved information
column = CustomColumn(table, name, '')
column.setDataType({
"type": str(datatype),
"type": datatype,
"nullable": nullable,
"default": default
})
# Append the column to the table's columns list
table.columns.append(column)
return tables
......@@ -326,18 +399,343 @@ def fetch_constraints_for_tables(engine, tables):
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 referred_table in tables:
for (fk_column, ref_column) in zip(fk_columns, referred_columns):
fkColumn = table.getColumn(fk_column)
pkColumn = tables.get(referred_table).getColumn(ref_column)
if fkColumn and pkColumn:
fkColumn.fkof = pkColumn
if fk['name'] not in table.fks:
table.fks[fk['name']] = []
table.fks[fk['name']].append(column)
table.fks[fk['name']].append(fkColumn)
return tables
# def fetch_constraints_for_tables(engine, tables):
def createGraph(tables, theme, showColumns, showTypes, useUpperCase):
s = ('digraph {\n'
+ ' graph [ rankdir="LR" bgcolor="#ffffff" ]\n'
+ f' node [ style="filled" shape="{theme.shape}" gradientangle="180" ]\n'
+ ' edge [ arrowhead="none" arrowtail="none" dir="both" ]\n\n')
for name in tables:
s += tables[name].getDotShape(theme, showColumns, showTypes, useUpperCase)
s += "\n"
for name in tables:
s += tables[name].getDotLinks(theme)
s += "}\n"
return s
def generate_erd(graph_DOT):
graph_module = pydot.graph_from_dot_data(graph_DOT)
graph = graph_module[0]
png_image_data = graph.create_png()
encoded_image = base64.b64encode(png_image_data).decode('utf-8')
return encoded_image
def getThemes():
return {
"Common Gray": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
"#e0e0e0", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
"Blue Navy": Theme("#1a5282", "#1a5282", "#ffffff",
"#1a5282", "#000000", "#ffffff", "rounded", "Mrecord", "#0078d7", "2"),
#"Gradient Green": Theme("#716f64", "#008080:#ffffff", "#008080:#ffffff",
# "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
#"Blue Sky": Theme("#716f64", "#d3dcef:#ffffff", "#d3dcef:#ffffff",
# "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
"Common Gray Box": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
"#e0e0e0", "#000000", "#000000", "rounded", "record", "#696969", "1")
}
if __name__ == "__main__":
app.run(debug=True)
# from my_flask_app import app
# 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
# # Set up database
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test"
# db = SQLAlchemy()
# 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()
# 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)
# if dropped_items==[]:
# image2 = ""
# 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)
# @app.route('/handle-drop', methods=['POST'])
# def handle_drop():
# data = request.json
# item_name = data.get('item')
# action = data.get('action')
# if action == 'added':
# dropped_items.append(item_name)
# elif action == 'removed' and item_name in dropped_items:
# dropped_items.remove(item_name)
# # Regenerate ERD based on the updated dropped_items
# database = db.engine.url.database
# themes = getThemes()
# tables2 = importMetadata(database, None, dropped_items, False)
# graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True)
# image2 = generate_erd(graph_DOT2)
# return jsonify(image2=image2)
# @app.route('/get-table-data', methods=['POST'])
# def get_table_data():
# data = request.json
# table_name = data.get('table_name')
# print(table_name)
# # Query your database to get the data for the table_name
# content = query_database_for_table_content(table_name)
# # Convert content to HTML table format
# html_table = generate_html_table(content)
# return jsonify({'html_table': html_table})
# def generate_html_table(content):
# if not content:
# return "No data found."
# # Generate column headers
# columns = content[0]
# table_html = "<table class='uk-table uk-table-small uk-table-hover uk-table-divider'><thead><tr>"
# for col in columns:
# table_html += f"<th>{col}</th>"
# table_html += "</tr></thead><tbody>"
# # Generate table rows
# for i in range(1, len(content)):
# table_html += "<tr>"
# for item in content[i]:
# table_html += f"<td>{item}</td>"
# table_html += "</tr>"
# table_html += "</tbody></table>"
# return table_html
# def query_database_for_table_content(table_name, number=20):
# # Initialize content list
# content_list = []
# # Get the schema of the table
# schema = getTableSchema(table_name)
# # Query the database to get the content of the table
# sql_content = text(f"""SELECT * FROM {schema}.{table_name} LIMIT {number};""")
# result = db.session.execute(sql_content, {'table_name': table_name, 'number': number}).fetchall()
# if not result:
# return []
# # Get the column names
# sql_columns = text("""
# SELECT column_name
# FROM information_schema.columns
# WHERE table_name = :table_name;
# """)
# column_names = db.session.execute(sql_columns, {'table_name': table_name}).fetchall()
# # Prepare column names
# columns = [column_name[0] for column_name in column_names]
# content_list.append(columns)
# # Append rows to content list
# for row in result:
# content_list.append(list(row))
# return content_list
# def getTableSchema(table_name):
# sql= text(f"""
# SELECT table_schema
# FROM information_schema.tables
# WHERE table_name = :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]
# return schemas
# def importMetadata(database, schema=None, tables_selected=None, show_all=False):
# tables = {}
# if database == '':
# 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)
# # If show_all is True, expand the list to include related tables.
# if show_all:
# expand_to_include_related_tables(tables)
# # Fetch columns for each table.
# fetch_columns_for_tables(tables)
# # Fetch constraints (PK, FK, Unique) for each table.
# fetch_constraints_for_tables(tables)
# return tables
# def fetch_initial_tables(schema, tables_selected_list):
# 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
# if tables_selected_list:
# params['tables_selected'] = tables_selected_list
# result = db.session.execute(sql_tables, params)
# for row in result:
# tableName, tableSchema = row
# table = Table(tableName, tableSchema) # adding schema to the table comment
# tables[tableName] = table
# table.label = f"n{len(tables)}"
# return tables
# def expand_to_include_related_tables(tables):
# # 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})
# 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)
# 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})
# for col in column_result:
# name, datatype, nullable, default = col
# column = Column(table, name, '')
# column.setDataType({
# "type": datatype,
# "nullable": nullable == 'YES',
# "default": default
# })
# table.columns.append(column)
# return tables
# def fetch_constraints_for_tables(tables):
# # Fetching Unique Constraints
# for tableName, table in tables.items():
# sql_unique = text("""
......@@ -421,43 +819,178 @@ def fetch_constraints_for_tables(engine, tables):
# return tables
def createGraph(tables, theme, showColumns, showTypes, useUpperCase):
s = ('digraph {\n'
+ ' graph [ rankdir="LR" bgcolor="#ffffff" ]\n'
+ f' node [ style="filled" shape="{theme.shape}" gradientangle="180" ]\n'
+ ' edge [ arrowhead="none" arrowtail="none" dir="both" ]\n\n')
for name in tables:
s += tables[name].getDotShape(theme, showColumns, showTypes, useUpperCase)
s += "\n"
for name in tables:
s += tables[name].getDotLinks(theme)
s += "}\n"
return s
def generate_erd(graph_DOT):
graph_module = pydot.graph_from_dot_data(graph_DOT)
graph = graph_module[0]
png_image_data = graph.create_png()
encoded_image = base64.b64encode(png_image_data).decode('utf-8')
return encoded_image
def getThemes():
return {
"Common Gray": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
"#e0e0e0", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
"Blue Navy": Theme("#1a5282", "#1a5282", "#ffffff",
"#1a5282", "#000000", "#ffffff", "rounded", "Mrecord", "#0078d7", "2"),
#"Gradient Green": Theme("#716f64", "#008080:#ffffff", "#008080:#ffffff",
# "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
#"Blue Sky": Theme("#716f64", "#d3dcef:#ffffff", "#d3dcef:#ffffff",
# "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
"Common Gray Box": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
"#e0e0e0", "#000000", "#000000", "rounded", "record", "#696969", "1")
}
if __name__ == "__main__":
app.run(debug=True)
\ No newline at end of file
# def createGraph(tables, theme, showColumns, showTypes, useUpperCase):
# s = ('digraph {\n'
# + ' graph [ rankdir="LR" bgcolor="#ffffff" ]\n'
# + f' node [ style="filled" shape="{theme.shape}" gradientangle="180" ]\n'
# + ' edge [ arrowhead="none" arrowtail="none" dir="both" ]\n\n')
# for name in tables:
# s += tables[name].getDotShape(theme, showColumns, showTypes, useUpperCase)
# s += "\n"
# for name in tables:
# s += tables[name].getDotLinks(theme)
# s += "}\n"
# return s
# def generate_erd(graph_DOT):
# graph_module = pydot.graph_from_dot_data(graph_DOT)
# graph = graph_module[0]
# png_image_data = graph.create_png()
# encoded_image = base64.b64encode(png_image_data).decode('utf-8')
# return encoded_image
# def getThemes():
# return {
# "Common Gray": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
# "#e0e0e0", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
# "Blue Navy": Theme("#1a5282", "#1a5282", "#ffffff",
# "#1a5282", "#000000", "#ffffff", "rounded", "Mrecord", "#0078d7", "2"),
# #"Gradient Green": Theme("#716f64", "#008080:#ffffff", "#008080:#ffffff",
# # "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
# #"Blue Sky": Theme("#716f64", "#d3dcef:#ffffff", "#d3dcef:#ffffff",
# # "transparent", "#000000", "#000000", "rounded", "Mrecord", "#696969", "1"),
# "Common Gray Box": Theme("#6c6c6c", "#e0e0e0", "#f5f5f5",
# "#e0e0e0", "#000000", "#000000", "rounded", "record", "#696969", "1")
# }
# 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}&nbsp;</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()
\ No newline at end of file
No preview for this file type
from sqlalchemy.types import UserDefinedType
import re
class CompressedDataType(UserDefinedType):
def get_col_spec(self):
return "_timescaledb_internal.compressed_data"
class RegType(UserDefinedType):
def get_col_spec(self):
return "regtype"
class RegRole(UserDefinedType):
def get_col_spec(self):
return "regrole"
class Theme:
def __init__(self, color, fillcolor, fillcolorC,
bgcolor, icolor, tcolor, style, shape, pencolor, penwidth):
......
......@@ -2,11 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Database ERD Viewer</title>
<link rel="stylesheet" href="{{ url_for('static', filename='uikit-3.18.0/css/uikit.min.css') }}" />
<script src="{{ url_for('static', filename='uikit-3.18.0/js/uikit.min.js') }}"></script>
<script src="{{ url_for('static', filename='uikit-3.18.0/js/uikit-icons.min.js') }}"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/d3-graphviz@2.6.1/build/d3-graphviz.min.js"></script>
<style>
......@@ -22,7 +23,7 @@
}
#sidebar1 {
/* position: relative; */
width: 15%;
width: 20%;
min-height: 100vh;
border-right: 0px solid #ccc;
background-color: #add8e6;
......@@ -77,6 +78,9 @@
/* padding-top: 0px;
padding-bottom: 0px; */
}
.accordion-button {
height: 8px;
}
.custom-push-button {
font-size: 14px;
width: 30px;
......@@ -214,21 +218,24 @@
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script>
<div id="sidebar1"> <!--class="uk-panel uk-panel-scrollable" -->
<h4>Database:</h4>
<p style="margin-top: -10px; padding-left: 5px;">{{database}}.db</p>
<ul uk-accordion>
<!-- <ul uk-accordion>
<li class="uk-open">
<a class="uk-accordion-title" href="#" style="margin-top: -8px; margin-bottom: -1px;">Schema:</a>
<div class="uk-accordion-content" style="margin-top: -3px; border: 1px solid black;">
<form method="post">
<label>
<input class="uk-checkbox" style="margin: 2px 2px 2px 5px; border-color: black;" type="checkbox" name="show_all" value="True" onchange="this.form.submit()" {{ 'checked' if show_all else '' }}>
<span class="uk-label" style="margin: 1px;" >show all</span>
<input class="form-check-input" id="flexCheckDefault" style="margin: 2px 2px 2px 5px; border-color: black;" type="checkbox" name="show_all" value="True" onchange="this.form.submit()" {{ 'checked' if show_all else '' }}>
<label class="form-check-label" for="flexCheckDefault" style="margin-top: 3px;">
Primary Tables
</label>
<select class="uk-select" name="schema" onchange='this.form.submit()' style="padding: 0px 1px 2px 1px;">
<select class="form-select" name="schema" onchange='this.form.submit()' style="padding: 0px 1px 2px 1px;">
<option value="">Choose here</option>
{% for schema in schemas %}
<option value="{{ schema }}" {% if schema == schema_Selected %} selected {% endif %}>{{ schema }}</option>
......@@ -246,7 +253,72 @@
<li>
<a class="uk-accordion-title" href="#">Label:</a>
</li>
</ul>
</ul> -->
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Schema
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<form method="post">
<!-- <label> -->
<input class="form-check-input" id="flexCheckDefault" style="margin: 2px 2px 2px 5px; border-color: black;" type="checkbox" name="show_all" value="True" onchange="this.form.submit()" {{ 'checked' if show_all else '' }}>
<!-- <span class="uk-label" style="margin: 1px;" >show all</span> -->
<label class="form-check-label" for="flexCheckDefault" style="margin-top: 3px;">
Primary Tables
</label>
<!-- </label> -->
<select class="form-select" name="schema" onchange='this.form.submit()' style="padding: 0px 1px 2px 1px;">
<option value="">Choose here</option>
{% for schema in schemas %}
<option value="{{ schema }}" {% if schema == schema_Selected %} selected {% endif %}>{{ schema }}</option>
{% endfor %}
</select>
</form>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">
Time
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
<div class="accordion-body">
<!-- <strong>This is the first item's accordion body.</strong> It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow. -->
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Object
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<!-- <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow. -->
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Label
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<!-- <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow. -->
</div>
</div>
</div>
</div>
<h4 style="margin-top: 0px;">Tables:</h4>
<div id="table_list" style="margin-bottom: 5px; margin-top: -10px;">
......@@ -386,10 +458,15 @@
<!-- set_database.html -->
<form method="post" action="/">
<input type="text" name="database_uri" placeholder="Enter Database URI">
<input type="submit" value="Set Database">
<div class="mb-3">
<label for="exampleInputPassword1" name="database_uri" class="form-label">Database URI</label>
<input type="text" class="form-control" id="exampleInputPassword1" name="database_uri" placeholder="Enter Database URI">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
<div id="resize-handle-left" class="resize-handle-left"></div>
</div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment