feat: init flask skeleton with auth and pages

main
ccicnce113424 1 month ago
commit a880eb667b

@ -0,0 +1,2 @@
SECRET_KEY=change-me
DATABASE_URL=mysql+pymysql://user:password@host:3306/dbname

@ -0,0 +1 @@
use nix

73
.gitignore vendored

@ -0,0 +1,73 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
.venv/
venv/
ENV/
env/
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# VS Code
.vscode/
# Environment variables and secrets
.env
.env.*
!.env.example
# Logs
*.log
# OS-generated files
.DS_Store
Thumbs.db
.direnv

@ -0,0 +1,48 @@
# 课堂点名系统(最小可运行版本)
## 安装依赖
```bash
cd class-random-caller
python -m venv venv
source venv/bin/activate # Windows 使用 venv\\Scripts\\activate
pip install -r requirements.txt
```
## 配置数据库
在项目根目录创建 `.env` 文件,内容示例:
```env
SECRET_KEY=your-secret-key
DATABASE_URL=mysql+pymysql://user:password@host:3306/dbname
```
请将 `user/password/host/dbname` 替换为你在免费 MySQL 服务上的实际信息。
## 初始化数据库
在 Python 交互环境或单独脚本中执行:
```python
from run import app
from app import db
from app.models import User
from werkzeug.security import generate_password_hash
with app.app_context():
db.create_all()
# 创建一个管理员账号(只需执行一次)
if not User.query.filter_by(username="admin").first():
u = User(username="admin", password_hash=generate_password_hash("admin123"))
db.session.add(u)
db.session.commit()
```
## 运行项目
```bash
python run.py
```
浏览器打开 `http://127.0.0.1:5000/auth/login`,使用 `admin / admin123` 登录。

@ -0,0 +1,25 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from .config import Config
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = "auth.login"
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
login_manager.init_app(app)
from .auth.routes import auth_bp
from .main.routes import main_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
return app

@ -0,0 +1,28 @@
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required
from werkzeug.security import check_password_hash
from app.models import User
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
login_user(user)
return redirect(url_for("main.rollcall"))
flash("用户名或密码错误")
return render_template("login.html")
@auth_bp.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("auth.login"))

@ -0,0 +1,13 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key")
SQLALCHEMY_DATABASE_URI = os.getenv(
"DATABASE_URL",
"mysql+pymysql://user:password@host:3306/dbname",
)
SQLALCHEMY_TRACK_MODIFICATIONS = False

@ -0,0 +1,29 @@
from flask import Blueprint, render_template
from flask_login import login_required
main_bp = Blueprint("main", __name__)
@main_bp.route("/")
@login_required
def index():
return render_template("rollcall.html")
@main_bp.route("/rollcall")
@login_required
def rollcall():
return render_template("rollcall.html")
@main_bp.route("/stats")
@login_required
def stats():
return render_template("stats.html")
@main_bp.route("/manage")
@login_required
def manage():
return render_template("manage.html")

@ -0,0 +1,41 @@
from datetime import datetime
from flask_login import UserMixin
from . import db, login_manager
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
student_no = db.Column(db.String(32), unique=True, nullable=False)
name = db.Column(db.String(64), nullable=False)
major = db.Column(db.String(128))
total_score = db.Column(db.Float, default=0.0)
attendance_count = db.Column(db.Integer, default=0)
random_called_count = db.Column(db.Integer, default=0)
class RollCall(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
student_id = db.Column(db.Integer, db.ForeignKey("student.id"), nullable=False)
call_time = db.Column(db.DateTime, default=datetime.utcnow)
mode = db.Column(db.String(16), nullable=False) # random / sequence
status = db.Column(
db.String(16), nullable=False
) # absent / distracted / attended
score_change = db.Column(db.Float, nullable=False, default=0.0)
student = db.relationship("Student", backref="roll_calls")
# user 关系可按需在查询中使用,不强制 backref
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

@ -0,0 +1,36 @@
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}课堂点名系统{% endblock %}</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('main.rollcall') }}">课堂点名</a>
<div>
<a class="btn btn-outline-light me-2" href="{{ url_for('main.rollcall') }}">点名</a>
<a class="btn btn-outline-light me-2" href="{{ url_for('main.stats') }}">统计</a>
<a class="btn btn-outline-light me-2" href="{{ url_for('main.manage') }}">管理</a>
<a class="btn btn-warning" href="{{ url_for('auth.logout') }}">退出</a>
</div>
</div>
</nav>
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-warning">
{% for m in messages %} {{ m }}<br /> {% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

@ -0,0 +1,50 @@
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>登录 - 课堂点名系统</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-4">
<div class="card">
<div class="card-header text-center">管理员登录</div>
<div class="card-body">
<form method="post">
<div class="mb-3">
<label class="form-label">用户名</label>
<input class="form-control" name="username" required />
</div>
<div class="mb-3">
<label class="form-label">密码</label>
<input
type="password"
class="form-control"
name="password"
required
/>
</div>
<button class="btn btn-primary w-100" type="submit">
登录
</button>
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-warning mt-3">
{% for m in messages %} {{ m }}<br /> {% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
</div>
</div>
</div>
</div>
</body>
</html>

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block title %}管理 - 课堂点名系统{% endblock %}
{% block content %}
<h4>管理页面占位</h4>
<p>学生总表,以及 Excel 导入功能,将在这里实现。</p>
{% endblock %}

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% block title %}点名 - 课堂点名系统{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<h4>点名区(稍后实现逻辑)</h4>
<p>这里将显示大字姓名和学号,以及随机/顺序选择。</p>
</div>
<div class="col-md-6">
<h4>打分区(稍后实现逻辑)</h4>
<p>缺勤 / 走神 / 加分 按钮区域。</p>
</div>
</div>
{% endblock %}

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block title %}统计 - 课堂点名系统{% endblock %}
{% block content %}
<h4>统计页面占位</h4>
<p>上半部分:积分随时间走势图;下半部分:排行榜;右侧:点名记录。</p>
{% endblock %}

@ -0,0 +1,7 @@
Flask==3.0.3
Flask-Login==0.6.3
Flask_SQLAlchemy==3.1.1
pymysql==1.1.1
pandas==2.2.3
openpyxl==3.1.5
python-dotenv==1.0.1

@ -0,0 +1,8 @@
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)

@ -0,0 +1,9 @@
{
pkgs ? import <nixpkgs> { },
...
}:
pkgs.mkShell {
packages = with pkgs; [
python313Packages.uv
];
}
Loading…
Cancel
Save