C++课程设计:3.2-所有项目

Home Doc Changelog Download About

需求

  • 设计数据库结构,重构schema.sqlmodels.py 中的class Project
  • 时间数据直接使用Python字典,但实际存储为JSON字符串
  • 实现新建项目(create.html)的POST接口,跳转,数据存储
  • 完善所有项目(all.html)的前端htmlcss设计
    • 新建项目移动到header右侧
    • 设置CSS使得<div class=data></div>中的所有<li></li>数据排为一行
  • record按钮与/create_record路由
    • record(inactive) -- stop(active)
    • (没有)调用到create_record
    • 刷新页面
  • 添加activate属性后的数据库初始化与原始数据的兼容性迁移
    • 从头remake
    • flask db upgrade, flask db init SQL命令
  • 实现所有项目(all.html)页面的projects数据根据project.all_time所转换的时间长度的值进行降序排列
  • status按照状态分区展示:纵列/横排(看板)
    • In Planning
    • In Process
    • Pause
    • Completed
    • Abandon
  • more按钮与deleterename,edit(status,note)
  • 完善profile
    • close_account 注销账号,删除账号密码及其所有相关数据
    • edit 修改账号密码,用户名
    • 点击每个模块修改相应数据??
  • 课程设计实验报告
  • 进阶(后续)
    • 渲染成日历
    • 使用邮箱/手机/二维码登录

传入的start_time,end_time为'2023-09-04 10:05:37.000000', '2023-09-04 10:05:39.000000',而非'2023-09-04 10:05:37', '2023-09-04 10:05:39',哪一步出了问题?如何解决?给出具体代码

已知
def parse_datetime(datetime_str):
    # 解析日期时间字符串并返回 datetime 对象
    return datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')

record = Record(
    start_time=parse_datetime(start_time),
    end_time=parse_datetime(end_time),
    project_id=project_id
  )
修改
def __init__(self, start_time, end_time, project_id):

    self.start_time = start_time  # 初始化记录的开始时间

    self.end_time = end_time  # 初始化记录的结束时间

    # 计算并设置记录的时长,timedelta类型转换为字符串

    self.duration = str(end_time - start_time)  #str(datetime.timedelta)

    self.project_id = project_id  # 初始化记录所属项目ID

    #self.project.update_time_stats()
中的
self.duration = str(end_time - start_time)  #str(datetime.timedelta)
是duration成为 HH:MM::SS格式的String


···




<div class="container">

  {% for project in projects %}

  <div class="project-card">

    <ul>

      <!--项目排名序号-->

      <li class="project-rank">{{ loop.index }}</li>

      <!--计时按钮,status='Pause'时文本显示为'开始计时',status='In Process'文本显示为停止计时-->

      <li class="project-timer">

        <button id="timer-btn" project-id="{{ project.id }}">Record</button>

      </li>  

      <!--项目名称-->

      <li class="project-name">{{ project.name }}</li>

      <!--项目状态-->

      <li class="project-status">{{ project.status }}</li>

      <!--项目总计时间-->

      <li class="project-total-time">{{ project.all_time }}</li>

      <!--项目更多按钮-->

      <li class="project-more">

        <button class="more" onclick="">更多</button>

      </li>

    </ul>

  </div>

  {% endfor %}

</div>

从以上代码可以发现,这里有若干个project组成的模块,每个模块里都有一个id="timer-btn"的按钮,其有一个参数为id="timer-btn",因project而异。

这是project中数据存储的格式 all_time = db.Column(db.String)
daily_time = db.Column(JSONEncodedDict, default={})
weekly_time = db.Column(JSONEncodedDict, default={})
monthly_time = db.Column(JSONEncodedDict, default={})
yearly_time = db.Column(JSONEncodedDict, default={})
他们都是基于文本的格式,而非时间。
当我进行update_time_stats(self,record)时,传入的record中包含的数据有:id = db.Column(db.Integer, primary_key=True,autoincrement=True)  # 记录ID,整数类型,主键

  project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=False)  # 项目ID,整数类型,外键,不能为空

  start时间 = db.Column(db.DateTime, nullable=False)  # 记录开始时间,日期时间类型,不能为空

  end_time = db.Column(db.DateTime, nullable=False)  # 记录结束时间,日期时间类型,不能为空

  duration = db.Column(db.String, nullable=False, default='00:00:00')  # 记录时长,字符串类型,不能为空
其中duration为文本类型数据,我需要一个函数来进行对文本存储的时长的计算,提取record中的duration为某一个标准类型,然后update_time_stats(self,record)调用该格式转换函数进行计算日期,更新project的数据参数。update_time_stats(self,record):定义如下,可进行修改,给出所有相关代码:

  def update_time_stats(self,record):

    date = record.start_time.date()

    day = date.strftime("%Y-%m-%d")

    week = date.strftime("%Y-W%W")

    month = date.strftime("%Y-%m")

    year = date.strftime("%Y")

    if date.isoformat() in self.daily_time:

      self.daily_time[day] += record.duration

    else:

      self.daily_time[day] = record.duration

    if week in self.weekly_time:

      self.weekly_time[week] += record.duration

    else:

      self.weekly_time[week] = record.duration

    if month in self.monthly_time:

      self.monthly_time[month] += record.duration

    else:

      self.monthly_time[month] = record.duration

    if year in self.yearly_time:

      self.yearly_time[year] += record.duration

    else:

      self.yearly_time[year] = record.duration

    self.calculate_time()

    db.session.commit()

all.html展示所有项目数据。以若干个卡片列表的形式。每一个卡片从左到右依次有开始计时/停止计时按钮,默认为开始计时,状态为暂停,按下按钮后状态切换为进行中,同时按钮文本切换为停止计时,记录下此时的DateTime作为本次Recordstart_time,再次点击按钮(此时钮文本为停止计时),状态从进行中切换为暂停,按钮文本切换回开始计时,记录下此时的DateTime作为本次Recordend_time,完成一次Record,存入数据库中,计算duration=end_time=start_time. 在all_time上+=本次record的duration,在daily_time查询当天日期对应的JSON{date:YYYY-MM-DD,time:HH:MM:SS},并使得time的值+=duration。若未查询到,则新建一个JSON数据,time默认初始化为0后+=duration。数据实时更新到all.html上,weekly,monthly,yearly以此类推。

默认文本显示为record,按钮状态为inactive,按下按钮后状态切换为active,同时按钮文本切换为stop,记录下此时的DateTime作为本次Recordstart_time,再次点击按钮(此时钮文本为stop),状态从active切换为inactive,按钮文本切换回record,记录下此时的DateTime作为本次Recordend_time,完成一次record=Record(start_time,end_time),commit到数据库中,调用project.update_time_stats(record)在all_time上+=本次record的duration,在daily_time查询当天日期对应的JSON{date:YYYY-MM-DD,time:HH:MM:SS},并使得time的值+=duration。若未查询到,则新建一个JSON数据,time默认初始化为0后+=duration。数据实时更新到all.html上,weekly,monthly,yearly以此类推。

编辑按钮的html和路由,给出具体完整的代码

  1. 实现按钮文本随着按钮状态active/inactive的改变而改变

stript.js schema.sql models.py views.py

# /create  POST 

current_user
request{
	name,
	status,
}

project(
	id =#生成
	name = request.form['name'],
	status = request.form['status'],
	user_id = current_user.id,
)

###
sqlalchemy.exc.StatementError: (builtins.TypeError) SQLite DateTime type only accepts Python datetime and date objects as input.
[SQL: INSERT INTO projects (name, status, user_id, all_time, daily_time, weekly_time, monthly_time, yearly_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]
[parameters: [{'status': 'In Planning', 'user_id': 1, 'name': '1', 'monthly_time': None, 'daily_time': None, 'yearly_time': None, 'weekly_time': None}]]  
###

#创建一条record

record = Record(
  project_id=project.id,
  start_time=datetime.now(),#第一次按钮,开始计时
  end_time=datetime.now() #第二次按钮,结束计时
)

record.duration=0
db.session.add(record)
db.session.commit()

project.update_time_stats(record)

针对project类,其schema.sql如下:

-- 创建 projects 表
CREATE TABLE IF NOT EXISTS Project (
  id INTEGER PRIMARY KEY,
  user_id INTEGER NOT NULL,
  name TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'In Planning', // 项目状态 InPlanning, InProgress, Completed, Abandoned, Paused
  all_time DATETIME NOT NULL DEFAULT '00:00:00',
  daily_time JSON, -- 日时间,TEXT类型存储JSON
  weekly_time JSON,  
  monthly_time JSON,
  yearly_time JSON,
  FOREIGN KEY (user_id) REFERENCES User (id)
);

其中'monthly_time': None, 'daily_time': None, 'yearly_time': None, 'weekly_time': None} 默认为None,非JSON格式。当一个project被创建出来而还没有records的时候,如何将这些JSON数据指定一个默认值?

sqlalchemy.exc.StatementError: (builtins.TypeError) SQLite DateTime type only accepts Python datetime and date objects as input.
[SQL: INSERT INTO projects (name, status, user_id, all_time, daily_time, weekly_time, monthly_time, yearly_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]
[parameters: [{'name': 'a', 'status': 'In Planning', 'user_id': 1}]]
  
  

@bp.route('/project/<int:id>/delete', methods=['DELETE'])

def delete(id):

  project = Project.query.get(id)

  db.session.delete(project)

  db.session.commit()

  return jsonify(status='success')

  

@bp.route('/project/<int:id>/edit', methods=['GET','POST'])

def edit(id):

  project = Project.query.get(id)

  if request.method == 'POST':

    project.name = request.form['name']

    project.status = request.form['status']

    db.session.commit()

    return redirect(url_for('main.all'))

  return render_template('edit.html', project=project)

  

@bp.route('/project/<int:id>/start', methods=['POST'])

def start(id):

  project = Project.query.get(id)

  record = Record(project_id=project.id)

  db.session.add(record)

  db.session.commit()

  return jsonify(status='success')

  

@bp.route('/project/<int:id>/stop', methods=['PUT'])

def stop(id):

  record = Record.query.filter_by(project_id=id).order_by(Record.start_time.desc()).first()

  record.end_time = datetime.now()

  db.session.commit()

  return jsonify(status='success')

  

@bp.route('/project/<int:id>/delete_record', methods=['DELETE'])

def delete_record(id):

  record = Record.query.get(id)

  db.session.delete(record)

  db.session.commit()

  return jsonify(status='success')

  

@bp.route('/project/<int:id>/update_record', methods=['PUT'])

def update_record(id):

  record = Record.query.get(id)

  record.start_time = datetime.strptime(request.form['start_time'],'%Y-%m-%d %H:%M:%S')

  record.end_time = datetime.strptime(request.form['end_time'],'%Y-%m-%d %H:%M:%S')

  record.duration = record.end_time - record.start_time

  db.session.commit()

  return jsonify(status='success')

  

@bp.route('/project/<int:id>/records')

def get_records(id):

  records = Record.query.filter_by(project_id=id).order_by(Record.start_time.desc()).all()

  return jsonify(records=[record.serialize() for record in records])

  

@bp.route('/project/<int:id>/records/<int:page>')

def get_records_by_page(id,page):

  records = Record.query.filter_by(project_id=id).order_by(Record.start_time.desc()).paginate(page=page, per_page=10)

  return jsonify(records=[record.serialize() for record in records.items])

  

@bp.route('/project/<int:id>/records/<int:page>/<int:per_page>')

def get_records_by_page_and_per_page(id,page,per_page):

  records = Record.query.filter_by(project_id=id).order_by(Record.start_time.desc()).paginate(page=page, per_page=per_page)

  return jsonify(records=[record.serialize() for record in records.items])

  

@bp.route('/project/<int:id>/records/<string:start_time>/<string:end_time>')

def get_records_by_time(id,start_time,end_time):

  records = Record.query.filter_by(project_id=id).filter(Record.start_time.between(start_time,end_time)).order_by(Record.start_time.desc()).all()

  return jsonify(records=[record.serialize() for record in records])

  

@bp.route('/project/<int:id>/records/<string:start_time>/<string:end_time>/<int:page>')

def get_records_by_time_and_page(id,start_time,end_time,page):

  records = Record.query.filter_by(project_id=id).filter(Record.start_time.between(start_time,end_time)).order_by(Record.start_time.desc()).paginate(page=page, per_page=10)

  return jsonify(records=[record.serialize() for record in records.items])

  

@bp.route('/project/<int:id>/records/<string:start_time>/<string:end_time>/<int:page>/<int:per_page>')

def get_records_by_time_and_page_and_per_page(id,start_time,end_time,page,per_page):

  records = Record.query.filter_by(project_id=id).filter(Record.start_time.between(start_time,end_time)).order_by(Record.start_time.desc()).paginate(page=page, per_page=per_page)

  return jsonify(records=[record.serialize() for record in records.items])

  

# 项目详情

@bp.route('/project/<int:id>')

def project(id):

  project = Project.query.get(id)

  return render_template('views/project.html', project=project)

  

# 项目详情

@bp.route('/project/<int:id>/status', methods=['GET'])

def get_project_status(id):

  project = Project.query.get(id)

  return jsonify(status=project.status)
<div class="container">

    {% for project in projects %}

    <ul>

      <li></li><!--添加一个排序的序号-->

      <li><button class="actions" onclick="toggleStatus({{project.id}})">按钮</button></li>

        <!--`开始计时`/`停止计时`按钮,默认为`开始计时`,状态为`暂停`,按下按钮后状态切换为`进行中`,同时按钮文本切换为`停止计时`,记录下此时的DateTime作为本次`Record`的`start_time`,再次点击按钮(此时钮文本为`停止计时`),状态从`进行中`切换为`暂停`,按钮文本切换回`开始计时`,记录下此时的DateTime作为本次`Record`的`end_time`,完成一次`Record`,存入数据库中,计算duration=end_time=start_time. 在all_time上+=本次record的duration,在daily_time查询当天日期对应的JSON{date:YYYY-MM-DD,time:HH:MM:SS},并使得time的值+=duration。若未查询到,则新建一个JSON数据,time默认初始化为0后+=duration。数据实时更新到all.html上,weekly,monthly,yearly以此类推。-->

      <li class="details">{{ project.name }}</li>

      <li class="details">{{ project.status }}</li>

      <li class="details">{{ project.total_time }}</li>

      <li><button class="more" onclick="">more</button></li> <!--more按钮,点击可选择delete,rename等按钮-->

    </ul>

    {% endfor %}

</div>
Array.from(timerBtns).forEach(function(btn) {

  btn.addEventListener('click', function() {

    let projectId = btn.dataset.projectId;

    if (projectId) {

      if (btn.classList.contains('active')) {

        // Stop timer

        btn.classList.remove('active');

        btn.innerText = 'Record';

        let endTime = new Date();

        let record = {

          project_id: projectId,

          start_time: formatDateTime(startTime),

          end_time: formatDateTime(endTime)

        };

        createRecord(record);

        startTime = null;

      } else {

        // Start timer

        startTime = new Date();

        btn.classList.add('active');

        btn.innerText = 'Stop';

      }

    }

  });

});