diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4422bbabaf3827596ee2f2c7b4d04e954d4cba3 Binary files /dev/null and b/__pycache__/config.cpython-311.pyc differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d2f3793b9c2f65c79872514cb2f47f4b7eb4c055 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,21 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +# 定义模å—级别的 db 对象 +db = SQLAlchemy() + +# 定义模å—级别的 app 对象 +app = None + +def create_app(): + global app # 使用 global 关键å—å°† app 定义为全局å˜é‡ + app = Flask(__name__) + # app.config.from_object('config.Config') + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///assessments.db' + db.init_app(app) + + with app.app_context(): + from . import routes # 导入路由 + db.create_all() # 创建数æ®åº“表 + + return app \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acb3e65bf3c0377ea0a698f531f843adc8d08e78 Binary files /dev/null and b/app/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/__pycache__/models.cpython-311.pyc b/app/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bea3d9da4400740161b10fce2711b2c9f610f78 Binary files /dev/null and b/app/__pycache__/models.cpython-311.pyc differ diff --git a/app/__pycache__/routes.cpython-311.pyc b/app/__pycache__/routes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3020cd639e47287a28ac0d8be660de8f076c40fe Binary files /dev/null and b/app/__pycache__/routes.cpython-311.pyc differ diff --git a/app/assessments.db b/app/assessments.db new file mode 100644 index 0000000000000000000000000000000000000000..849772d88c6bb75561bfacb38769c75f79d59d81 Binary files /dev/null and b/app/assessments.db differ diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000000000000000000000000000000000000..801a97197fc423fa41da693c79626999f4f77c37 --- /dev/null +++ b/app/models.py @@ -0,0 +1,24 @@ +from . import db +from datetime import datetime + +class Question(db.Model): + id = db.Column(db.Integer, primary_key=True) + text = db.Column(db.String(500), nullable=False) + type = db.Column(db.String(50), nullable=False) # e.g., 'multiple_choice', 'true_false' + correct_answer = db.Column(db.String(500), nullable=False) + assessment_id = db.Column(db.Integer, db.ForeignKey('assessment.id'), nullable=False) + +class Assessment(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + type = db.Column(db.String(50), nullable=False) # 'formative' or 'summative' + deadline = db.Column(db.DateTime, nullable=True) + questions = db.relationship('Question', backref='assessment', lazy=True) + +class StudentResponse(db.Model): + id = db.Column(db.Integer, primary_key=True) + student_id = db.Column(db.Integer, nullable=False) + question_id = db.Column(db.Integer, db.ForeignKey('question.id'), nullable=False) + response = db.Column(db.String(500), nullable=False) + is_correct = db.Column(db.Boolean, nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..e9d0534f59d443af5b030c1afe6a12d86e30f316 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,110 @@ +from flask import request, jsonify, render_template +from . import app, db +from .models import Assessment, Question, StudentResponse +from datetime import datetime +from sqlalchemy import func + +# 定义路由 +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/create_assessment', methods=['GET', 'POST']) +def create_assessment(): + if request.method == 'POST': + data = request.json + new_assessment = Assessment( + title=data['title'], + type=data['type'], + deadline=datetime.strptime(data['deadline'], '%Y-%m-%d %H:%M:%S') if data.get('deadline') else None + ) + db.session.add(new_assessment) + db.session.commit() + return jsonify({"message": "Assessment created", "id": new_assessment.id}), 201 + else: + return render_template('create_assessment.html') + +@app.route('/add_question', methods=['POST']) +def add_question(): + data = request.json + new_question = Question( + text=data['text'], + type=data['type'], + correct_answer=data['correct_answer'], + assessment_id=data['assessment_id'] + ) + db.session.add(new_question) + db.session.commit() + return jsonify({"message": "Question added", "id": new_question.id}), 201 + +# æ˜¾ç¤ºè¯„ä¼°é€‰æ‹©é¡µé¢ +@app.route('/take_assessment', methods=['GET']) +def take_assessment(): + return render_template('take_assessment.html') + +#显示形æˆæ€§è¯„ä¼°é¡µé¢ +@app.route('/take_assessment/formative_test', methods=['GET']) +def formative_test(): + # 查询所有形æˆæ€§è¯„ä¼° + assessment = Assessment.query.filter_by(type='formative').first() + if assessment: + questions = Question.query.filter_by(assessment_id=assessment.id).all() + return render_template('formative_test.html', assessment=assessment, questions=questions) + else: + return "No formative assessment available.", 404 + +# æ˜¾ç¤ºæ€»ç»“æ€§è¯„ä¼°é¡µé¢ +@app.route('/take_assessment/summative_test', methods=['GET']) +def summative_test(): + # 查询所有总结性评估 + assessment = Assessment.query.filter_by(type='summative').first() + if assessment: + questions = Question.query.filter_by(assessment_id=assessment.id).all() + return render_template('summative_test.html', assessment=assessment, questions=questions) + else: + return "No summative assessment available.", 404 + +# @app.route('/take_assessment/<int:assessment_id>', methods=['GET']) +# def take_assessment(assessment_id): +# assessment = Assessment.query.get_or_404(assessment_id) +# questions = Question.query.filter_by(assessment_id=assessment_id).all() +# return render_template('take_assessment.html', assessment=assessment, questions=questions) + +# 管ç†é—®é¢˜é¡µé¢ +@app.route('/manage_questions', methods=['GET']) +def manage_questions(): + return render_template('manage_questions.html') + +@app.route('/submit_response', methods=['POST']) +def submit_response(): + data = request.json + question = Question.query.get_or_404(data['question_id']) + is_correct = data['response'] == question.correct_answer + new_response = StudentResponse( + student_id=data['student_id'], + question_id=data['question_id'], + response=data['response'], + is_correct=is_correct + ) + db.session.add(new_response) + db.session.commit() + return jsonify({"message": "Response submitted", "is_correct": is_correct}), 201 + +@app.route('/get_results/<int:assessment_id>', methods=['GET']) +def get_results(assessment_id): + assessment = Assessment.query.get_or_404(assessment_id) + responses = StudentResponse.query.join(Question).filter(Question.assessment_id == assessment_id).all() + results = [{"student_id": r.student_id, "question_id": r.question_id, "response": r.response, "is_correct": r.is_correct} for r in responses] + + # è®¡ç®—ç»Ÿè®¡ä¿¡æ¯ + total_students = db.session.query(StudentResponse.student_id).distinct().count() + average_score = db.session.query(func.avg(StudentResponse.is_correct)).filter(Question.assessment_id == assessment_id).scalar() + most_incorrect_question = db.session.query(Question.text, func.count(StudentResponse.id)).join(StudentResponse).filter(StudentResponse.is_correct == False).group_by(Question.text).order_by(func.count(StudentResponse.id).desc()).first() + + statistics = { + "total_students": total_students, + "average_score": average_score, + "most_incorrect_question": most_incorrect_question[0] if most_incorrect_question else None + } + + return jsonify({"results": results, "statistics": statistics}), 200 \ No newline at end of file diff --git a/app/templates/create_assessment.html b/app/templates/create_assessment.html new file mode 100644 index 0000000000000000000000000000000000000000..b03b4408037975651ee5c5d83623ae19e2628f4c --- /dev/null +++ b/app/templates/create_assessment.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Create Assessment</title> +</head> +<body> + <h1>Create Assessment</h1> + <form id="createAssessmentForm"> + <label for="title">Title:</label> + <input type="text" id="title" name="title" required> + <br> + <label for="type">Type:</label> + <select id="type" name="type" required> + <option value="formative">Formative</option> + <option value="summative">Summative</option> + </select> + <br> + <label for="deadline">Deadline (for summative):</label> + <input type="datetime-local" id="deadline" name="deadline"> + <br> + <button type="submit">Create Assessment</button> + </form> + + <script> + document.getElementById('createAssessmentForm').onsubmit = async function(e) { + e.preventDefault(); + const formData = { + title: document.getElementById('title').value, + type: document.getElementById('type').value, + deadline: document.getElementById('deadline').value + }; + const response = await fetch('/create_assessment', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + const result = await response.json(); + alert(result.message); + }; + </script> +</body> +</html> \ No newline at end of file diff --git a/app/templates/formative_test.html b/app/templates/formative_test.html new file mode 100644 index 0000000000000000000000000000000000000000..45cd1d4c52934e99ea38a74509c13fdb17a80b62 --- /dev/null +++ b/app/templates/formative_test.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Formative Test</title> +</head> +<body> + <h1>Formative Test: {{ assessment.title }}</h1> + <div id="questions"> + {% if questions %} + {% for question in questions %} + <div> + <p><strong>Question {{ loop.index }}:</strong> {{ question.text }}</p> + <input type="text" id="response_{{ question.id }}" placeholder="Your answer"> + </div> + {% endfor %} + {% else %} + <p>No questions available for this assessment.</p> + {% endif %} + </div> + <button onclick="submitResponses()">Submit</button> + + <script> + async function submitResponses() { + const assessmentId = {{ assessment.id | tojson | safe }}; + const studentId = 1; // å‡è®¾å¦ç”Ÿ ID 为 1 + const questions = document.querySelectorAll('[id^="response_"]'); + for (const question of questions) { + const questionId = question.id.split('_')[1]; + const response = question.value; + await fetch('/submit_response', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + student_id: studentId, + question_id: questionId, + response: response + }) + }); + } + alert('Responses submitted!'); + } + </script> +</body> +</html> \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..d1a8ab12059c5319dcbf90a2762c22f744c90eea --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Automated Assessment Tool</title> +</head> +<body> + <h1>Automated Assessment Tool</h1> + <button onclick="location.href='/create_assessment'">Create Assessment</button> + <button onclick="location.href='/take_assessment'">Take Assessment</button> + <button onclick="location.href='/manage_questions'">Manage Questions</button> +</body> +</html> \ No newline at end of file diff --git a/app/templates/manage_questions.html b/app/templates/manage_questions.html new file mode 100644 index 0000000000000000000000000000000000000000..2561cae1549f303d1264021d8b73d221ac6ac531 --- /dev/null +++ b/app/templates/manage_questions.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Manage Questions</title> +</head> +<body> + <h1>Manage Questions</h1> + <form id="addQuestionForm"> + <label for="question_text">Question Text:</label> + <input type="text" id="question_text" name="question_text" required> + <br> + <label for="question_type">Question Type:</label> + <select id="question_type" name="question_type" required> + <option value="multiple_choice">Multiple Choice</option> + <option value="true_false">True/False</option> + </select> + <br> + <label for="correct_answer">Correct Answer:</label> + <input type="text" id="correct_answer" name="correct_answer" required> + <br> + <button type="submit">Add Question</button> + </form> + + <script> + document.getElementById('addQuestionForm').onsubmit = async function(e) { + e.preventDefault(); + const formData = { + text: document.getElementById('question_text').value, + type: document.getElementById('question_type').value, + correct_answer: document.getElementById('correct_answer').value, + assessment_id: 1 // å‡è®¾è¯„ä¼° ID 为 1 + }; + const response = await fetch('/add_question', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + const result = await response.json(); + alert(result.message); + }; + </script> +</body> +</html> \ No newline at end of file diff --git a/app/templates/summaritive_test.html b/app/templates/summaritive_test.html new file mode 100644 index 0000000000000000000000000000000000000000..d2a8ff3e9cec65a5f776d73d3fb7ac87ab9b6d5e --- /dev/null +++ b/app/templates/summaritive_test.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Summative Test</title> +</head> +<body> + <h1>Summative Test: {{ assessment.title }}</h1> + <div id="questions"> + {% if questions %} + {% for question in questions %} + <div> + <p><strong>Question {{ loop.index }}:</strong> {{ question.text }}</p> + <input type="text" id="response_{{ question.id }}" placeholder="Your answer"> + </div> + {% endfor %} + {% else %} + <p>No questions available for this assessment.</p> + {% endif %} + </div> + <button onclick="submitResponses()">Submit</button> + + <script> + async function submitResponses() { + const assessmentId = {{ assessment.id | tojson | safe }}; + const studentId = 1; // å‡è®¾å¦ç”Ÿ ID 为 1 + const questions = document.querySelectorAll('[id^="response_"]'); + for (const question of questions) { + const questionId = question.id.split('_')[1]; + const response = question.value; + await fetch('/submit_response', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + student_id: studentId, + question_id: questionId, + response: response + }) + }); + } + alert('Responses submitted!'); + } + </script> +</body> +</html> \ No newline at end of file diff --git a/app/templates/take_assessment.html b/app/templates/take_assessment.html new file mode 100644 index 0000000000000000000000000000000000000000..6466dc4caa92bd2a6814c1ebb19e47047d6c34c2 --- /dev/null +++ b/app/templates/take_assessment.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Take Assessment</title> +</head> +<body> + <h1>Take Assessment</h1> + <div> + <button onclick="window.location.href='/take_assessment/formative_test'">Formative Test</button> + <button onclick="window.location.href='/take_assessment/summative_test'">Summative Test</button> + </div> +</body> +</html> \ No newline at end of file diff --git a/assessments.db b/assessments.db new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/config.py b/config.py new file mode 100644 index 0000000000000000000000000000000000000000..badcfa777a42deb76b0b61b363582027bb373e46 --- /dev/null +++ b/config.py @@ -0,0 +1,6 @@ +import os + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess' + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///assessments.db' + SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file diff --git a/init_db.py b/init_db.py new file mode 100644 index 0000000000000000000000000000000000000000..1bf902e4d54f1d83ad97f3f9303c64c6dc1be082 --- /dev/null +++ b/init_db.py @@ -0,0 +1,45 @@ +from app import create_app, app, db +from app.models import Assessment, Question +from datetime import datetime + +with app.app_context(): + # 创建数æ®åº“表 + db.create_all() + + # 创建形æˆæ€§è¯„ä¼° + formative_assessment = Assessment(title="Formative Test", type="formative") + db.session.add(formative_assessment) + db.session.commit() + + # æ·»åŠ å½¢æˆæ€§è¯„估的问题 + formative_questions = [ + {"text": "What is 2 + 2?", "type": "multiple_choice", "correct_answer": "4"}, + {"text": "What is the capital of France?", "type": "multiple_choice", "correct_answer": "Paris"}, + {"text": "Is the sky blue?", "type": "true_false", "correct_answer": "True"}, + {"text": "What is 5 * 5?", "type": "multiple_choice", "correct_answer": "25"}, + {"text": "Is water H2O?", "type": "true_false", "correct_answer": "True"} + ] + for q in formative_questions: + question = Question(text=q['text'], type=q['type'], correct_answer=q['correct_answer'], assessment_id=formative_assessment.id) + db.session.add(question) + db.session.commit() + + # 创建总结性评估 + summative_assessment = Assessment(title="Summative Test", type="summative", deadline=datetime(2023, 12, 31, 23, 59, 59)) + db.session.add(summative_assessment) + db.session.commit() + + # æ·»åŠ æ€»ç»“æ€§è¯„ä¼°çš„é—®é¢˜ + summative_questions = [ + {"text": "What is 10 - 4?", "type": "multiple_choice", "correct_answer": "6"}, + {"text": "What is the capital of Germany?", "type": "multiple_choice", "correct_answer": "Berlin"}, + {"text": "Is the Earth flat?", "type": "true_false", "correct_answer": "False"}, + {"text": "What is 8 / 2?", "type": "multiple_choice", "correct_answer": "4"}, + {"text": "Is the sun a star?", "type": "true_false", "correct_answer": "True"} + ] + for q in summative_questions: + question = Question(text=q['text'], type=q['type'], correct_answer=q['correct_answer'], assessment_id=summative_assessment.id) + db.session.add(question) + db.session.commit() + + print("Database initialized with test data.") \ No newline at end of file diff --git a/instance/assessments.db b/instance/assessments.db new file mode 100644 index 0000000000000000000000000000000000000000..05e765dcf522083f6b2f17d394293f802f140fa3 Binary files /dev/null and b/instance/assessments.db differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3d5ec8a895b742b28f7e1239fefd0803128633cf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask==2.0.1 +Flask-SQLAlchemy==2.5.1 +werkzeug==2.0.3 +SQLAlchemy==1.4.41 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000000000000000000000000000000000000..3cc98270f9ec829c3a62b6d70ca09e382e67d99e --- /dev/null +++ b/run.py @@ -0,0 +1,6 @@ +from app import create_app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file