Python 动态生成系统数据库设计到word文档

2023-11-09

背景

经常需要交付一些系统文档而且基本都是word的,其中又有系统数据库介绍模块, 看着数据库里的几百张表于是我开始怀疑人生, 所以咱手写一个

涉及知识

  • pymysql 操作数据库
    -tkinter GUI图形库
  • threading 线程
  • queue 阻塞队列
  • pandas python数据计算模型库(DataFame)
  • python-docx 操作word文档

1、功能介绍

填写相关数据库信息后, 支持生成系统数据库设计到word文档节省写文档时间

  • 支持按自定义SQL结果导出
  • 支持导出所有表结构信息
  • 支持导出数据库表清单
  • 支持测试连接

界面如下:

在这里插入图片描述

1.1 自定义SQL导出

在文本框内输入sql,然后勾选导出模式为 自定义SQL, 最后点击导出,生成word文档如下
在这里插入图片描述

在这里插入图片描述

在自定义SQL模式时, 如果勾选了对所有表执行一遍, 并且配置了模版变量 #{tableName}. 则会遍历所有表执行一遍该模版SQL, 如下图。
在这里插入图片描述

最终结果:
在这里插入图片描述

1.2 导出所有表结构

勾选导出模式为表结构,生成word文档如下
在这里插入图片描述

1.3 导出数据库表清单

勾选导出模式为表清单,生成word文档如下
在这里插入图片描述

2、 代码实现


import queue
import threading
import time
import traceback
from tkinter import Tk, Button, messagebox, Label, Frame, Entry, IntVar, Radiobutton, StringVar, \
    constants, filedialog, Text, font, Scrollbar, BooleanVar, Checkbutton
from tkinter.font import Font
from tkinter.messagebox import showinfo, showerror
from tkinter.ttk import Progressbar
from enum import Enum
import logging


from pymysql import OperationalError


class MsgEnum(Enum):
    """
        消息类型枚举
    """
    START = 0
    STOP = 1
    EXIT = 3

class InputValue:
    def __init__(self, host, port, user, password, database, sql,calc_mode,isAllTable):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.database = database
        self.sql = sql
        self.calc_mode = calc_mode
        self.isAllTable = isAllTable


class GuiTempldate:
    class Cache:
        RUNING = False

    def __init__(self) -> None:
        # 1、Gui消息队列
        self.msg_center = MsgCenter(self)

        # 2、窗口设置
        self.root = Tk()
        self.root.title("数据库导出工具")
        # self.root.wm_resizable(False, False) # 设置窗口大小不可拉伸
        self.root.geometry('600x700+500+200')  # 设置窗口: 宽 x 高 + 窗口位置x坐标 + 窗口位置y坐标
        self.root.protocol("WM_DELETE_WINDOW", self.close_event)

        # 4、初始化各个组件和布局
        self.initGui()

    def initGui(self):
        # 1- 标签
        text_str = """版本: 1.0.0
        #author burukeyou
        #说明:
            1) 支持导出数据库所有表结构信息
            2)支持导出数据库表清单
            3)支持自定义sql导出
        """
        Label(self.root, text=text_str, justify='left', fg='red').pack(anchor=constants.W)

        # 4- 文本框
        fm02 = Frame(self.root)
        self.ip_address = StringVar(value="localhost:3306")
        fm02.pack(anchor=constants.W, fill=constants.X)
        Label(fm02, text='服务器地址 ').pack(side=constants.LEFT)
        self.name_btn = Entry(fm02, width=40, textvariable=self.ip_address)
        self.name_btn.pack(side=constants.RIGHT)

        # 4- 文本框
        fm02 = Frame(self.root)
        self.user_name = StringVar(value="root")
        fm02.pack(anchor=constants.W, fill=constants.X)
        Label(fm02, text='帐号').pack(side=constants.LEFT)
        self.name_btn = Entry(fm02, width=40, textvariable=self.user_name)
        self.name_btn.pack(side=constants.RIGHT)

        # 4- 文本框
        fm02 = Frame(self.root)
        self.password = StringVar(value="123456")
        fm02.pack(anchor=constants.W, fill=constants.X)
        Label(fm02, text='密码').pack(side=constants.LEFT)
        self.name_btn = Entry(fm02, width=40, textvariable=self.password)
        self.name_btn.pack(side=constants.RIGHT)

        # 4- 文本框
        fm02 = Frame(self.root)
        self.database = StringVar(value="")
        fm02.pack(anchor=constants.W, fill=constants.X)
        Label(fm02, text='数据库').pack(side=constants.LEFT)
        self.name_btn = Entry(fm02, width=40, textvariable=self.database)
        self.name_btn.pack(side=constants.RIGHT)

        # 3- 单选框
        self.mode_var = IntVar(value=1)
        fm01 = Frame(self.root)  # , bg='blue'
        fm01.pack(anchor=constants.W)
        Label(fm01, text='导出模式').grid(row=0, column=0, sticky='W')
        Radiobutton(fm01, text='表结构', variable=self.mode_var, value=1).grid(row=0, column=1, sticky='E', padx=40)
        Radiobutton(fm01, text='表清单', variable=self.mode_var, value=2).grid(row=0, column=2, sticky='E', padx=40)
        Radiobutton(fm01, text='自定义SQL', variable=self.mode_var, value=3).grid(row=0, column=3, sticky='E', padx=40)

        # 5- 勾选框
        self.is_all_flag = BooleanVar()
        self.is_all_table_btn = Checkbutton(self.root, variable=self.is_all_flag, text='所有表执行一遍(自定义SQL可使用模版变量#{tableName})', onvalue=True, offvalue=False)
        self.is_all_table_btn.pack(anchor=constants.W)

        # 9- 多行文本框
        Label(self.root, text='自定义SQL输入').pack(anchor=constants.W)
        fm22 = Frame(self.root)
        fm22.pack(anchor=constants.W, fill=constants.X)
        scroll = Scrollbar(fm22)
        scroll.pack(side=constants.RIGHT, fill=constants.Y)
        ft = Font(family='宋体', size=14, weight=font.BOLD)

        self.text_btn = Text(fm22, height=12, fg="black", font=ft, bg="white", insertbackground="black")
        self.text_btn.pack(side=constants.LEFT)
        # text 联动 scroll
        scroll.config(command=self.text_btn.yview)
        self.text_btn.config(yscrollcommand=scroll.set)

        # 进度条
        self.progressbar = Progressbar(self.root, length=600, mode="determinate", orient=constants.HORIZONTAL)
        self.progressbar.pack(anchor=constants.W)
        self.progressbar['maximum'] = 100

        # 10、开始结束按钮
        fm06 = Frame(self.root)
        fm06.pack(side=constants.BOTTOM)

        self.start_btn = Button(fm06, text="导出", width=6, height=1, command=self.start_event)
        self.start_btn.grid(row=0, column=0, sticky='W', padx=20, pady=20)

        self.stop_btn = Button(fm06, text="停止", width=6, height=1, command=self.stop_event)
        self.stop_btn.grid(row=0, column=1, sticky='E', padx=20, pady=20)

        self.test_btn = Button(fm06, text="测试连接", width=6, height=1, command=self.test_connection)
        self.test_btn.grid(row=0, column=2, sticky='E', padx=20, pady=20)


    def start_event(self):
        self.msg_center.put(MsgEnum.START)
        self.Cache.RUNING = True
        self.main_calc_thread: threading.Thread = threading.Thread(target=start_clac, args=(self,))
        self.main_calc_thread.start()

    def stop_event(self):
        self.msg_center.put(MsgEnum.STOP)
        self.Cache.RUNING = False

    def close_event(self):
        # 关闭窗口
        if self.Cache.RUNING and not messagebox.askokcancel("警告", "任务还在执行中,确定要关闭吗?"):
            return

        self.root.destroy()
        self.msg_center.put(MsgEnum.EXIT)

    def test_connection(self) -> bool:
        input_value = self.getInputValue()
        if not checkParam(input_value):
            return False

        conn = None
        try:
            conn = connectDB(input_value)
            showinfo(message="测试连接成功")
            return True
        except OperationalError as e:
            showerror(message="测试连接数据库失败,请确认数据库信息是否正确。 \n" + str(e))
        finally:
            if conn:
                conn.close()


    def showUI(self):
        # 启动消息队列
        threading.Thread(target=self.msg_center.mainloop).start()
        # 这个方法会循环阻塞住,监听gui的事件,得放到主线程最后
        self.root.mainloop()

    def getInputValue(self) -> InputValue:
        ipaddress: str = self.ip_address.get().strip()
        if ipaddress == "" or ipaddress.find(":") < 0:
            showerror(message="请输入正确的ip地址")
            return

        ip = ipaddress.split(":")[0].strip()
        port = ipaddress.split(":")[1].strip()
        database = self.database.get().strip()
        user_name = self.user_name.get().strip()
        password = self.password.get().strip()
        calc_mode = self.mode_var.get()
        custom_sql = self.text_btn.get("1.0", "end-1c").strip()
        isAllTable: bool = self.is_all_flag.get()

        return InputValue(ip, port, user_name, password,database, custom_sql,calc_mode,isAllTable)



def start_clac(gui: GuiTempldate):
    try:
        # 实际计算任务
        start_demo(gui)
    finally:
        # 发布结束事件
        gui.stop_event()

class MsgCenter:
    """
        消息队列
            主要处理窗口控件消息
    """
    def __init__(self, obj: GuiTempldate) -> None:
        self.queue = queue.Queue()
        self.obj = obj

    def put(self, msg: Enum):
        self.queue.put(msg)

    def mainloop(self):
        while True:
            try:
                # 阻塞获取消息
                msg = self.queue.get()
                print("消费消息: {}".format(msg))

                if msg == MsgEnum.START:
                    MsgStrategy.start_strategy(self.obj)
                elif msg == MsgEnum.STOP:
                    MsgStrategy.stop_strategy(self.obj)
                elif msg == MsgEnum.EXIT:
                    break
                else:
                    pass

            except queue.Empty:
                traceback.print_exc()


class MsgStrategy:
    @classmethod
    def start_strategy(cls, gui: GuiTempldate):
        gui.start_btn.config(state=constants.DISABLED)
        gui.stop_btn.config(state=constants.NORMAL)

        #
        gui.progressbar['value'] = 0
        for i in range(100):
            time.sleep(0.1)
            gui.progressbar['value'] += 1
            if i >= 50:
                gui.progressbar['value'] = 100
                gui.root.update()
                break
            gui.root.update()

    @classmethod
    def stop_strategy(cls, gui: GuiTempldate):
        gui.start_btn.config(state=constants.NORMAL)
        gui.stop_btn.config(state=constants.DISABLED)




# ===============================业务逻辑 ======================================================

from typing import List

import pymysql

import pandas as pd
from docx import Document
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
from datetime import datetime

from docx.table import Table, _Cell


def checkParam(input_value: InputValue) -> bool:
    if input_value.database == "" or input_value.user == "" or input_value.password == "" or input_value.host == "" or input_value.port == "":
        showerror(message="请填写完整的数据库连接参数")
        return False

    return True

def connectDB(input_value: InputValue):
     return pymysql.connect(host=input_value.host,
                            user=input_value.user,
                            password=input_value.password,
                            db=input_value.database,
                            port=int(input_value.port), charset="utf8")


def start_demo(gui: GuiTempldate):
    """ 自定义计算任务
    :param gui:             gui组件对象
    :return:
    """
    input_value = gui.getInputValue()

    # 校验参数
    if not checkParam:
        return

    conn = None
    try:
        conn: pymysql.Connection = connectDB(input_value)
        print("建立连接成功")

        download(conn, input_value)
    except OperationalError as e:
        logging.error(e)
        showerror(message="连接数据库失败,请确认数据库信息是否正确")
    except Exception as e1:
        # 打印堆栈信息
        e1.with_traceback(None)
        showerror(message="导出失败")
    finally:
        if conn is not None:
            conn.close()
            print("关闭连接成功")


"""
    表结构SQL
"""
def tableSchemaSql(table_name,dataBase):
   return f"""
   select ORDINAL_POSITION 序号,
    COLUMN_NAME 字段名,
    COLUMN_TYPE 字段类型,
    -- DATA_TYPE 字段类型,
    -- CHARACTER_MAXIMUM_LENGTH 长度,
    -- IS_NULLABLE 是否为空,
    case when COLUMN_DEFAULT is null then '无' else COLUMN_DEFAULT end 默认值,
    COLUMN_COMMENT 字段说明
    FROM
    INFORMATION_SCHEMA.COLUMNS
    where
    table_schema ='{dataBase}'
    and table_name = '{table_name}';
   """

"""
    数据库表清单SQL
"""
def databaseTableListSql(database):
    return f"""select table_name as '表名',TABLE_COMMENT '表中文名称','业务表',TABLE_COMMENT '说明'
               from information_schema.tables
               where table_schema='{database}' and  LOWER(table_type) ='base table'
    """

def readAllTableToDataFrame(conn,dataBase) -> dict:
    # 获取所有表的信息
    tables = pd.read_sql_query("SHOW TABLES;", conn)
    df_map = {}
    for _, table in tables.iterrows():
        table_name = table[0]
        if table_name is None or table_name == "":
            continue
        sql = tableSchemaSql(table_name,dataBase)
        df = pd.read_sql_query(sql, conn)
        df = df.reset_index(drop=True)
        df.index = df.index + 1

        df_map[table_name] = df

    return df_map


def readDataBaseTableListToDataFrame(conn, dataBase):
    df_map = {}
    sql = databaseTableListSql(dataBase)
    df = pd.read_sql_query(sql, conn)
    df = df.reset_index(drop=True)
    df.index = df.index + 1
    df_map["tmp"] = df
    return df_map


def readByCustomSqlToDataFrame(conn, input_value):
    # sql模版
    sql = input_value.sql

    # 获取所有表的信息
    tables = pd.read_sql_query("SHOW TABLES;", conn)
    df_map = {}
    for _, table in tables.iterrows():
        table_name = table[0]
        if table_name is None or table_name == "":
            continue

        # 替换变量名 #{taleName}
        tmp_sql = sql.replace("#{tableName}", table_name)

        df = pd.read_sql_query(tmp_sql, conn)
        df = df.reset_index(drop=True)
        df.index = df.index + 1

        df_map[table_name] = df

    return df_map

def downloadToWord(df_map, save_path, heading):
    # 创建Word文档
    doc = Document()
    for table_name in df_map:
        df = df_map[table_name]

        # 添加标题
        if heading:
            doc.add_heading(heading, level=3)
        else:
            doc.add_heading(f"【{table_name}表】", level=3)

        # 转成list表格数据
        table_data: List = [df.columns.tolist()] + df.values.tolist()

        # 创建 rows x col 表格
        table: Table = doc.add_table(rows=len(table_data), cols=len(table_data[0]))

        # 设置表格样式
        table.style = "Table Grid"
        # 设置表格字体和大小
        #table.style.font.name = "宋体"
        #table.style.font.size = 5

        # 填充表格内容
        for i, row in enumerate(table_data):
            for j, value in enumerate(row):
                cell: _Cell = table.cell(i, j)
                cell.text = str(value)

                # 设置第一行
                if i == 0:
                    # 设置文本加粗
                    for run in cell.paragraphs[0].runs:
                        run.bold = True


        #  设置第一行背景色为浅灰色D9D9D9
        for i, cell in enumerate(table.rows[0].cells):
            shading_elm_1 = parse_xml(r'<w:shd {} w:fill="D9D9D9"/>'.format(nsdecls('w')))
            cell._tc.get_or_add_tcPr().append(shading_elm_1)


    # 保存Word文档
    doc.save(save_path)
    print("导出" + save_path + "成功")





def download(conn, input_value):
    dataBase = input_value.database
    calc_mode = input_value.calc_mode
    custom_sql = input_value.sql

    # 读取所有表结构到DataFrame
    heading = None
    df_map = {}
    if calc_mode == 1:
        # 所有表结构
        print("执行模式1")
        df_map = readAllTableToDataFrame(conn, dataBase)
    elif calc_mode == 2:
        # 表清单
        print("执行模式2")
        df_map = readDataBaseTableListToDataFrame(conn, dataBase)
        heading = f"【{dataBase}数据库表清单】"
    else:
        # 自定义sql
        if custom_sql is None or custom_sql == "":
            showinfo("", "请输入自定义SQL")
            return

        # 配置了都执行一遍并且sql模版真中包含#{tableName}变量
        if input_value.isAllTable == True and custom_sql.find("#{tableName}") != -1:
            print("自定义SQL对所有表执行一遍")
            df_map = readByCustomSqlToDataFrame(conn, input_value)
        else:
            df = pd.read_sql_query(custom_sql, conn)
            df_map["tmp"] = df
            heading = f"【{dataBase}自定义SQL数据】"

    # 选择导出位置
    now = datetime.now().strftime("%y%m%d%H%M%S")
    file_name = dataBase + f"数据库数据-{now}.docx"
    save_path = filedialog.asksaveasfilename(title=u'保存文件', filetypes=[("word文件", ".docx")], initialfile=file_name)
    print("保存位置目录", save_path)

    if save_path == "" or save_path is None:
        return

    # 导出到word
    downloadToWord(df_map, save_path, heading)

    showinfo("", "导出成功")


if __name__ == '__main__':
    gui = GuiTempldate()
    gui.showUI()


最后

如果有用,求打赏或者评论点赞下

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 动态生成系统数据库设计到word文档 的相关文章

随机推荐

  • 图神经网络(1):图卷积神经网络GCN ICLR 2017

    图卷积神经网络GCN ICLR 2017 是曾经在美国加州大学UCI教授 现在荷兰阿姆斯特丹大学教授 Max Welling团队的大作 Max是图灵奖获得者Hinton的弟子 第一作者T N Kipf已经成为这个领域有名的学者和工程师 如果
  • DHCP协议详细解析

    一 DHCP定义 DHCP 动态主机配置协议 是一个应用层的网络协议 指的是由服务器控制一段IP地址范围 客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码 当我们将客户主机ip地址设置为动态获取方式时 DHCP服务器就会根据D
  • Java学习--- MySQL多表查询与自连接

    目录 一 多表查询 二 笛卡尔集 三 非等值连接 四 自连接 五 练习 一 多表查询 基于两个和两个以上的表查询 在实际应用中 单表查询不能满足需求 当在多表查询的条件不能少于表的个数 1 不然会出现笛卡尔集 二 笛卡尔集 多表查询 查询员
  • 在多个浏览器中添加IDM插件

    许多朋友下载了IDM Internet Download Manager 不知如何使用 把包含视频的链接放到软件新建任务 下载下来的的却是网页而不是视频 该软件下载视频的其中一个方法 需安装浏览器插件 通过插件抓取视频下载源 正常来说 我们
  • multicycle path的概念和用法

    通常情况下 两个同步的reg进行timing check时 组合逻辑的delay必须在一个时钟周期内到达 才能满足setup的时序 但是在某些情况下 从一个寄存器输出到另外一个寄存器的data端需要不止一个cycle的时间 而且又不影响逻辑
  • c++之Vector(动态 )

    原文 Vector 向量 C 中的一种数据结构 确切的说是一个类 它相当于一个动态的数组 当无法知道数组的规模有多大时 用来解决问题可以达到最大节约空间的目的 1 用法 include
  • 《结构化思维》读书笔记

    读书使人进步 每天进步一点点 本周小萌精心读的一本书是 结构化思维 主要是讲结构化思考 很棒的一本书 推荐读 麦肯锡金字塔原理 以及其他的麦肯锡结构化思考方法 第一章 初识思维 思维是我们解读事实的起点 是产生行为的源头 是决定结果的根本
  • honeyd路由拓扑

    create router 创建路由器模版 set router personality Cisco 7206 running IOS 11 1 24 指纹 add router tcp port 23 usr share honeyd s
  • python+unittest+ddt,从0到1搭建接口自动化框架

    最近在学习纯代码的接口自动化框架 在网上查阅大量资料后 结合了多个文章 最后使用python unittest ddt实现了一个简单的接口自动化工具 可以实现读取excel中的测试用例 生成测试报告 并发送邮件的功能 下图是项目的框架结构
  • Activiti7正式版- Activiti Cloud SR1

    2019年3月18版本 Activiti7正式版 Activiti Cloud SR1正式发布 我很高兴地宣布第一个服务版本的Activiti Cloud 和Activiti Core artefacts 在从我们的第一次GA迭代中获得大量
  • Andorid平台GB28181设备接入端如何生成黑帧并推送至国标平台

    我们在做Android平台GB28181设备接入模块的时候 遇到这样的需求 做移动对讲的时候 是不需要视频数据的 但是国标平台侧 没有视频的话 大多平台又无法播纯音频打包的数据 网页端大多基于http flv或webrtc实现 基于此 我们
  • python3 nonetype_“ NoneType”对象在python3中不可迭代

    TypeError Traceback most recent call last in 15 execute Align rsUnitedSpecPolicyDataFeed 16 gt 17 df pd read sql sql con
  • mpaas小程序如何实现摇一摇功能

    因为公司需要特意研究一下mpaas小程序框架 公司要实现摇一摇功能 如下 手机晃动调用其代码api 实现其功能 代码如下 axml片段
  • python-sklearn数据拆分与决策树的实现

    python sklearn数据拆分与决策树的实现 前言 一 数据拆分的sklearn实现 1 拆分为训练集与测试集 2 交叉验证法 1 留一交叉验证 2 验证集验证 3 k折交叉验证 4 s折交叉验证 s fold 3 sklearn交叉
  • .md即markdown文件的基本常用编写语法(图文并茂)

    序言 很久没有写博客了 感觉只要是不写博客 人就很变得很懒 学的知识点感觉还是记不住 渐渐地让我明白 看的越多 懂的越少 你这话不是有毛病吗 应该是看的越多 懂的越多才对 此话怎讲 当你在茫茫的前端知识库里面东看看 西看看的时候 很快就被海
  • 知乎热议:国家何时整治程序员的高薪现象?

    国家何时整治程序员的高薪现象 看到这个标题 可能大多数人的第一反应都是 提出这种问题的人 非蠢即坏 我当时看到突然吓了一跳 难道这是要拿程序员开刀 本身知乎平台上就有不少程序员群体活跃 马上就吸引来了很多人参与回答 其中 下面这位知友的回答
  • 最强自动化测试框架Playwright(35)-API测试

    playwright可以进行API测试 APIRequestContext可以通过网络发送各种HTTP S 请求 以下示例演示如何使用 Playwright 通过 GitHub API 测试问题创建 测试套件将执行以下操作 在运行测试之前创
  • Django图书商城系统实战开发 - 实现个人订单管理

    Django图书商城系统实战开发 实现个人订单管理 在实战开发Django图书商城系统中 实现个人订单管理是提供给用户的重要功能之一 以下是总结的要点 订单列表 创建一个订单列表页面 展示个人的订单历史 使用Django的模型和视图来获取和
  • Shopify商品列表页实现自动加载下一页产品功能Loading More

    找到你要编辑的主题 然后单击 Action gt Edit code 打开文件 theme liquid 或者在商品列表文件中 引用一个JS文件 在Assets中新增一个名为loadingmore的JS文件 添加如下代码 保存 loadin
  • Python 动态生成系统数据库设计到word文档

    背景 经常需要交付一些系统文档而且基本都是word的 其中又有系统数据库介绍模块 看着数据库里的几百张表于是我开始怀疑人生 所以咱手写一个 涉及知识 pymysql 操作数据库 tkinter GUI图形库 threading 线程 que