如何将程序代码重构为mvc模型?

时间:2019-05-22 15:15:19

标签: python user-interface model-view-controller bokeh

我编写了一个webapp,它根据一些输入(开始/结束日期时间,机器ID和参数ID)从数据库中查询数据,并以散景图显示它:

enter image description here

到目前为止,您可以看到它可以按预期工作,但是我有一些计划进一步扩展此应用程序:

  1. 允许将来自不同批次(具有不同开始/结束时间戳记)的数据加载到同一图形中。
  2. 对不同批次进行一些统计分析,例如平均值,标准偏差,控制极限等。
  3. 获取不同机器和/或参数的实时流参数更新。

因此,我现在正处于应用程序开始变得更加复杂的时刻,我想将代码重构为可维护和可扩展的格式。目前,该代码是按程序编写的,我想移至类似MVC的模型,以将数据查询与bokeh可视化和统计计算分开,但我不确定如何最好地实现。

如何最好地重构代码?

import logging
import pymssql, pandas

from dateutil import parser
from datetime import datetime, timedelta

from bokeh import layouts, models, plotting, settings
from bokeh.models import widgets

SETTINGS = {
    'server': '',
    'user': '',
    'password': '',
    'database': ''
}

def get_timestamps(datetimes):
    """ DB timestamps are in milliseconds """
    return [int(dt.timestamp()*1000) for dt in datetimes]

def get_db_names(timestamps):
    logging.debug('Started getting DB names ...')
    query = """ 
        SELECT
            [DBName]
        FROM [tblDBNames]
        WHERE {timestamp_ranges}
    """.format(
        timestamp_ranges = ' OR '.join([f'({timestamp} BETWEEN [LStart] AND [LStop])' for timestamp in timestamps])
    )
    logging.debug(query)
    db_names = []
    with pymssql.connect(**SETTINGS) as conn:
        with conn.cursor(as_dict=True) as cursor:
            cursor.execute(query)
            for row in cursor:
                db_names.append(row['DBName'])
    #logging.debug(db_names)    
    logging.debug('Finished getting DB names')
    return list(set(db_names))

def get_machines():
    logging.debug('Started getting machines ...')
    query = """
        SELECT 
            CONVERT(VARCHAR(2),[ID]) AS [ID], 
            [Name]
        FROM [tblMaschinen]
        WHERE NOT [Name] = 'TestLine4'
        ORDER BY [Name]
    """
    logging.debug(query)
    with pymssql.connect(**SETTINGS) as conn:
        with conn.cursor(as_dict=False) as cursor:
            cursor.execute(query)
            data = cursor.fetchall()
    #logging.debug(data)
    logging.debug('Finished getting machines')
    return data

def get_parameters(machine_id, parameters):
    logging.debug('Started getting process parameteres ...')
    query = """
        SELECT
            CONVERT(VARCHAR(4), TrendConfig.ID) AS [ID],
            TrendConfig_Text.description AS [Description]
        FROM [TrendConfig]
        INNER JOIN TrendConfig_Text 
            ON TrendConfig.ID = TrendConfig_Text.ID
        WHERE (TrendConfig_Text.languageText_KEY = 'nl')
        AND TrendConfig.MaschinenID = {machine_id}
        AND TrendConfig_Text.description IN ('{parameters}')
        ORDER BY TrendConfig_Text.description
    """.format(
        machine_id = machine_id,
        parameters = "', '".join(parameters)
    )
    logging.debug(query)
    with pymssql.connect(**SETTINGS) as conn:
        with conn.cursor(as_dict=False) as cursor:
            cursor.execute(query)
            data = cursor.fetchall()
    #logging.debug(data)
    logging.debug('Finished getting process parameters')
    return data

def get_process_data(query):
    logging.debug('Started getting process data ...')
    with pymssql.connect(**SETTINGS) as conn:
        return pandas.read_sql(query, conn, parse_dates={'LTimestamp': 'ms'}, index_col='LTimestamp')
    logging.debug('Finished getting process data')

batches = widgets.Slider(start=1, end=10, value=1, step=1, title="Batches")

# TODO: make custom datetime widget - https://bokeh.pydata.org/en/latest/docs/user_guide/extensions_gallery/widget.html
now, min_date = datetime.now(), datetime.fromtimestamp(1316995200)
date_start = widgets.DatePicker(title="Start date:", value=str(now.date()), min_date=str(min_date), max_date=str(now.date()))
time_start = widgets.TextInput(title="Start time:", value=str((now-timedelta(hours=1)).replace(microsecond=0).time()))
start_row = layouts.Row(children=[date_start, time_start], width = 300)

date_end = widgets.DatePicker(title="End date:", value=str(now.date()), min_date=str(min_date), max_date=str(now.date()))
time_end = widgets.TextInput(title="End time:", value=str(now.replace(microsecond=0).time()))
end_row = layouts.Row(children=[date_end, time_end], width = 300)

datetimes = layouts.Column(children=[start_row, end_row])

## Machine list
machines = get_machines()

def select_machine_cb(attr, old, new):
    logging.debug(f'Changed machine ID: old={old}, new={new}')
    parameters = get_parameters(select_machine.value, default_params)
    select_parameters.options = parameters
    select_parameters.value = [parameters[0][0]]

select_machine = widgets.Select(
    options = machines,
    value = machines[0][0],
    title = 'Machine:'
)
select_machine.on_change('value', select_machine_cb)

## Parameters list
default_params = [
    'Debiet acuteel',
    'Extruder energie',
    'Extruder kWh/kg',
    'Gewicht bunker',
    'RPM Extruder acuteel',
    'Temperatuur Kop'
]

parameters = get_parameters(select_machine.value, default_params)

select_parameters = widgets.MultiSelect(
    options = parameters,
    value = [parameters[0][0]],
    title = 'Parameter:'
)

def btn_update_cb(arg):
    logging.debug('btn_update clicked')

    datetime_start = parser.parse(f'{date_start.value} {time_start.value}')
    datetime_end = parser.parse(f'{date_end.value} {time_end.value}')
    datetimes = [datetime_start, datetime_end]

    timestamps = get_timestamps(datetimes)
    db_names = get_db_names(timestamps)

    machine_id = select_machine.value
    parameter_ids = select_parameters.value

    query = """
        SELECT
            [LTimestamp],
            [TrendConfigID],
            [Text],
            [Value]
        FROM ({derived_table}) [Trend]
        LEFT JOIN [TrendConfig] AS [TrendConfig]
            ON [Trend].[TrendConfigID] = [TrendConfig].[ID]
        WHERE [LTimestamp] BETWEEN {timestamp_range}
        AND [Trend].[TrendConfigID] IN ({id_range})
    """.format(
        derived_table = ' UNION ALL '.join([f'SELECT * FROM [{db_name}].[dbo].[Trend_{machine_id}]' for db_name in db_names]),
        timestamp_range = ' AND '.join(map(str,timestamps)),
        id_range = ' ,'.join(parameter_ids)
    )
    logging.debug(query)
    df = get_process_data(query)
    ds = models.ColumnDataSource(df)
    plot.renderers = [] # clear plot
    #view = models.CDSView(source=ds, filters=[models.GroupFilter(column_name='TrendConfigID', group='')])
    #plot = plotting.figure(plot_width=600, plot_height=300, x_axis_type='datetime')
    plot.line(x='LTimestamp', y='Value', source=ds, name='line')

btn_update = widgets.Button(
    label="Update", 
    button_type="primary", 
    width = 150
)
btn_update.on_click(btn_update_cb)

btn_row = layouts.Row(children=[btn_update])

column = layouts.Column(children=[batches, datetimes, select_machine, select_parameters, btn_row], width = 300)

plot = plotting.figure(plot_width=600, plot_height=300, x_axis_type='datetime')

row = layouts.Row(children=[column, layouts.Spacer(width=20), plot])

tab1 = models.Panel(child=row, title="Viewer")
tab2 = models.Panel(child=layouts.Spacer(), title="Settings")
tabs = models.Tabs(tabs=[tab1, tab2])

plotting.curdoc().add_root(tabs)

1 个答案:

答案 0 :(得分:0)

我建议使用OOP(面向对象编程)。 为此,您需要:

  1. 使用正在绘制图形的标准化对象。对于 示例(点:{值:x,开始时间:a,结束时间:y})
  2. 将获得的对象(或对象列表)提取到单独的位置 类/文件(示例可在此处找到:http://www.qtrac.eu/pyclassmulti.html
  3. 使用标准化界面加入。对于 例如,该接口可以称为“批处理”,并且可以具有 方法“ obtainPoints”,返回定义的“ Point”对象的列表 在步骤1。

现在,您可以创建实现该接口的多个批处理实现,并且主类可以在单独的实现上调用gainPoints方法并对其进行图形化。最后,您将具有1个批处理的接口(批处理)X实现(即SQLDatabaseBatch,LDAPBatch等...)和一个利用所有这些批处理实现并创建图形的主类。