我编写了一些代码,从我的FitBit中提取数据,并将其存储在GCP数据库中以供进一步分析。该项目可在这里使用。,但我特别想问的是Flask应用程序&用于为该项目提供web界面的代码:
from datetime import date
import os
from flask import Flask, make_response, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField
from fitnick.activity.activity import Activity
from fitnick.database.database import Database
from fitnick.heart_rate.time_series import HeartRateTimeSeries
from fitnick.heart_rate.models import heart_daily_table
app = Flask(__name__)
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY
month_options = [i for i in range(1, 13)]
day_options = [i for i in range(1, 32)]
year_options = range(2020, 2021)
class DateForm(FlaskForm):
date = StringField('date')
@app.route("/", methods=['GET'])
def index():
"""
Currently serves as the endpoint for the get_heart_rate_zone methods, even
though it's currently set to the index page
:return:
"""
heart_rate_zone = HeartRateTimeSeries(config={'database': 'fitbit'})
statement = heart_daily_table.select().where(heart_daily_table.columns.date == str(date.today()))
rows = [i for i in Database(database='fitbit', schema='heart').engine.execute(statement)]
# retrieve rows for today already in database, if there are none then get rows via fitnick
if len(rows) == 0:
rows = heart_rate_zone.get_heart_rate_zone_for_day(database='fitbit')
rows = [i for i in rows]
form = DateForm(request.form)
if request.method == 'GET':
return render_template(
template_name_or_list="index.html",
rows=rows,
form=form,
month_options=month_options,
day_options=day_options,
year_options=year_options
)
def set_search_date(request, search_date):
if request.method == 'POST':
# collect search date information from the dropdown forms if they're all supplied.
if all([request.form.get('month_options'), request.form.get('day_options'), request.form.get('year_options')]):
search_date = '-'.join(
[f"{request.form['year_options']}",
f"{request.form['month_options']}".zfill(2),
f"{request.form['day_options']}".zfill(2)])
else:
# use the search_date value we set in lines 59-62
pass
return search_date
@app.route("/get_heart_rate_zone_today", methods=['GET', 'POST'])
def get_heart_rate_zone_today():
"""
Endpoint for getting heart rate zone data from the FitBit API.
:return:
"""
heart_rate_zone = HeartRateTimeSeries(config={'database': 'fitbit'})
form = DateForm(request.form)
value = 'Updated heart rate zone data for {}.'
if form.date._value(): # set search_date, default to today if none supplied
search_date = form.date._value()
else:
search_date = str(date.today())
if request.method == 'POST':
# collect search date information from the dropdown forms if they're all supplied.
search_date = set_search_date(request, search_date)
rows = heart_rate_zone.get_heart_rate_zone_for_day(
database='fitbit',
target_date=search_date)
rows = [i for i in rows]
else: # request.method == 'GET'
# no date supplied, just return data for today.
heart_rate_zone.config = {'base_date': date.today(), 'period': '1d'}
statement = heart_daily_table.select().where(heart_daily_table.columns.date == str(date.today()))
rows = Database(database='fitbit', schema='heart').engine.execute(statement)
return render_template(
template_name_or_list="index.html",
value=value.format(search_date),
rows=rows,
form=form,
month_options=month_options,
day_options=day_options,
year_options=year_options
)
@app.route("/get_activity_today", methods=['GET', 'POST'])
def get_activity_today():
"""
Endpoint for getting activity data for a given date from the FitBit API.
:return:
"""
activity = Activity(config={'database': 'fitbit'})
form = DateForm(request.form)
search_date = form.date._value()
if request.method == 'POST':
search_date = set_search_date(request, search_date)
row = activity.get_calories_for_day(day=search_date)
value = 'Updated activity data for {}.'.format(search_date)
else:
row, value = {}, ''
return render_template(
template_name_or_list='activity.html',
form=form,
row=row,
value=value,
month_options=month_options,
day_options=day_options,
year_options=year_options
)
@app.route('/<page_name>')
def other_page(page_name):
"""
Stand-in endpoint for any undefined URL.
:param page_name:
:return:
"""
response = make_response(f'The page named {page_name} does not exist.', 404)
return response
if __name__ == '__main__':
app.run(debug=True)具体来说,我试图减少一些代码重复。我目前有两页,一页用于获取心率区域数据,另一页用于收集活动数据。当检索在任何页面上输入的表单中的日期&然后查询数据库并返回适当的结果时,似乎会有大量的代码重复。
它感觉应该有一种方法来折叠index、get_heart_rate_zone_today和get_activity_today函数中的代码,其中所检查的本质上是如果请求是GET或POST,如果是POST,则从FitBit API查询给定日期的数据,将其添加到数据库中&返回数据(当它是GET请求时,我们只返回数据库中的数据)。get_heart_rate_zone_today函数的这一部分是我的意思的一个例子:
if form.date._value(): # set search_date, default to today if none supplied
search_date = form.date._value()
else:
search_date = str(date.today())
if request.method == 'POST':
# collect search date information from the dropdown forms if they're all supplied.
search_date = set_search_date(request, search_date)
rows = heart_rate_zone.get_heart_rate_zone_for_day(
database='fitbit',
target_date=search_date)
rows = [i for i in rows]
else: # request.method == 'GET'
# no date supplied, just return data for today.
heart_rate_zone.config = {'base_date': date.today(), 'period': '1d'}
statement = heart_daily_table.select().where(heart_daily_table.columns.date == str(date.today()))
rows = Database(database='fitbit', schema='heart').engine.execute(statement)但是,从代码的角度来看(代码应该采取的不同操作取决于是否检索到了活动或心率区域数据),到目前为止我发现的最简单的方法是为每个检索方法编写单独的函数。
如果有人能检查一下我下面的烧瓶代码,了解你对它的看法,我想做什么,以及它是否是解决我目前问题的正确主意,那就是半重复的代码,我会非常感激的。谢谢你给我时间!
发布于 2020-11-23 22:20:21
我的建议是重组这个项目,把它分成几个文件。在这个文件中,您将所有东西混合在一起,您正在实例化Flask实例,定义路由,并且在两者之间有函数。
这个文件只会随着时间的推移而增长,并且会变得不堪重负,这使得代码维护从长远来看更加繁琐和困难。
要启动Flask,其想法是创建一个类似于以下内容的小型启动文件:
from flask import Flask
app = Flask(__name__)
app.config.from_object('config.Config')以及一个配置文件,如下所示:
from os import environ, path
from dotenv import load_dotenv
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))
class Config:
"""Base config."""
SECRET_KEY = environ.get('SECRET_KEY')
SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME')
STATIC_FOLDER = 'static'
TEMPLATES_FOLDER = 'templates'
class ProdConfig(Config):
FLASK_ENV = 'production'
DEBUG = False
TESTING = False
DATABASE_URI = environ.get('PROD_DATABASE_URI')
class DevConfig(Config):
FLASK_ENV = 'development'
DEBUG = True
TESTING = True
DATABASE_URI = environ.get('DEV_DATABASE_URI')资料来源和推荐阅读:配置您的瓶应用程序。我使用类似的方案可以轻松地在测试和生产环境之间切换,而无需触及代码库。相反,将对配置文件进行更改。请注意,在您的应用程序中有硬编码:app.run(debug=True)
我也会将这些路线保存在一个不同的文件中。导入路由的简单示例:
from flask import Flask
def create_app():
"""Initialize the core application."""
app = Flask(__name__, instance_relative_config=False)
app.config.from_object('config.Config')
with app.app_context():
# Include our Routes
from . import routes
return app来源:水瓶座应用厂
如果您有一些帮助函数,它们也可以转到另一个文件。不需要把代码库弄乱。
简而言之,我的建议是花更多的时间阅读烧瓶,并花时间构建一个好的样板,您可以在未来的项目中重用。
我不熟悉您的数据库方案,但是很明显,您的代码(如:get_heart_rate_zone_for_day(database='fitbit')或:Database(database='fitbit', schema='heart') )都有重复。DB名称被多次提到。应该在您的应用程序开始时一劳永逸地设置它。至少,您可以在代码顶部定义一个常量。
我认为代码顶部的这样的东西应该可以做到:
FITBIT_DB = Database(database='fitbit', schema='heart')然后从现在开始使用FITBIT_DB来引用DB。这就能让代码打火机。
在代码的顶部,您有以下内容:
month_options = [i for i in range(1, 13)]
day_options = [i for i in range(1, 32)]
year_options = range(2020, 2021)我猜你的金甲模板里有三个组合盒。要使代码具有未来的安全性,而不必每年都对其进行检查,您应该使年份范围具有动态:
from datetime import datetime
current_year = datetime.now().year
year_options = range(current_year, current_year + 1)但是,您似乎没有使用WTF来验证日期。2月30日这样的日期应该会触发一个错误,即使代码没有崩溃并且最终没有返回任何数据。
不确定此代码是否正确:
def set_search_date(request, search_date):
if request.method == 'POST':
# collect search date information from the dropdown forms if they're all supplied.
if all([request.form.get('month_options'), request.form.get('day_options'), request.form.get('year_options')]):首先,没有必要把它列成一个清单。
提示: all函数所做的工作:检查列表中的所有项目是否为真。所以我会检查这段代码是否像你真正认为的那样。如果有疑问,您可以转储POST请求的内容,自己看看会发生什么。
发布于 2020-11-23 17:22:12
基于代码的一些一般性建议
1.-组织。这可能是一个讨厌的项目,但您应该避免对视图执行db查询,这将导致维护代码困难,正如您现在注意到的那样。尝试拥有一个单独的文件来处理所有查询,然后使用如果您想要在上面定义的them.stants之间通信的函数,这可能适合bett。
2.尽可能少地保持视图中的逻辑。创建可重用的函数,例如从db检索数据或填充搜索数据
3.-常数。如果月份、年份等.不会改变,考虑为它们使用元组。而且,您正在创建的范围只有一年时间。
这样,您就可以看到可重用的代码,特别是db查询。
祝好运!
发布于 2020-11-23 18:25:36
这种模式可以在代码中看到:
[i for i in range(1, 13)]在上述情况下,没有必要对列表进行理解,只需直接保持范围。在这种情况下:
rows = [i for i in rows]最好调用列表构造函数,即
rows = list(rows)或者更好的是,如果可以的话,不可变变量的元组构造函数:
rows = tuple(rows)这是:
if request.method == 'GET':写在只接受index的GET方法上,因此if是多余的,可以删除。
这是一种奇怪的方法。首先,它没有设置任何东西-它返回一个日期。除非我遗漏了什么,否则把这个重命名为get_search_date。还可以组合谓词:
if (
request.method == 'POST'
and all(
f'{k}_options' in request.form
for k in (
'month', 'day', 'year',
)
)
):
return '-'.join # ...
return search_datehttps://codereview.stackexchange.com/questions/252077
复制相似问题