Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions elearn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# E‑Learn (Flask + SQLite)

A minimal e‑learning app:
- User auth (email/password)
- Courses with lessons (optionally with embedded video URLs)
- Enrollments and progress tracking
- Quizzes with multiple‑choice questions and scoring
- Simple Admin to create courses, lessons, and quizzes

## Quickstart

1. Create and activate a virtualenv (recommended):

```bash
python3 -m venv .venv
source .venv/bin/activate
```

2. Install dependencies:

```bash
pip install -r requirements.txt
```

3. Run the app:

```bash
python app.py
```

The app will be available at http://localhost:5000

## Notes
- Database file: `elearn.db` (auto-created on first request)
- Default roles: users register as `student`. To make an admin, update the `users.role` column to `admin` using any SQLite tool.
- Video: for YouTube embeds, paste an embed URL like `https://www.youtube.com/embed/VIDEO_ID`.
Binary file added elearn/__pycache__/db.cpython-313.pyc
Binary file not shown.
247 changes: 247 additions & 0 deletions elearn/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import os
from typing import Dict, Optional
from flask import Flask, render_template, request, redirect, url_for, session, flash, abort
from werkzeug.security import generate_password_hash, check_password_hash

import db


def create_app() -> Flask:
app = Flask(__name__, template_folder='templates', static_folder='static')
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-change-me')

# Ensure database exists
@app.before_request
def _ensure_db():
db.initialize_database()

def current_user() -> Optional[dict]:
user_id = session.get('user_id')
if not user_id:
return None
user_row = db.get_user_by_id(user_id)
if not user_row:
return None
return dict(user_row)

def login_required():
if not session.get('user_id'):
return redirect(url_for('login', next=request.path))
return None

def admin_required():
user = current_user()
if not user or user.get('role') != 'admin':
abort(403)

@app.route('/')
def index():
user = current_user()
courses = db.list_courses()
return render_template('index.html', user=user, courses=courses)

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
name = request.form.get('name', '').strip()
email = request.form.get('email', '').strip().lower()
password = request.form.get('password', '')

if not name or not email or not password:
flash('All fields are required.', 'error')
return redirect(url_for('register'))

existing = db.get_user_by_email(email)
if existing:
flash('Email is already registered.', 'error')
return redirect(url_for('register'))

password_hash = generate_password_hash(password)
user_id = db.create_user(email=email, name=name, password_hash=password_hash)
session['user_id'] = user_id
session['role'] = 'student'
flash('Welcome! Your account has been created.', 'success')
return redirect(url_for('dashboard'))

return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get('email', '').strip().lower()
password = request.form.get('password', '')
next_url = request.args.get('next') or url_for('dashboard')
user_row = db.get_user_by_email(email)
if not user_row or not check_password_hash(user_row['password_hash'], password):
flash('Invalid credentials.', 'error')
return redirect(url_for('login'))
session['user_id'] = user_row['id']
session['role'] = user_row['role']
flash('Logged in successfully.', 'success')
return redirect(next_url)
return render_template('login.html')

@app.route('/logout')
def logout():
session.clear()
flash('Logged out.', 'info')
return redirect(url_for('index'))

@app.route('/dashboard')
def dashboard():
redirect_response = login_required()
if redirect_response:
return redirect_response
user = current_user()
enrolled_courses = db.list_user_enrollments(user_id=user['id'])
progress_by_course: Dict[int, tuple] = {}
for course in enrolled_courses:
progress_by_course[course['id']] = db.get_course_progress(user['id'], course['id'])
return render_template('dashboard.html', user=user, enrolled_courses=enrolled_courses, progress_by_course=progress_by_course)

@app.route('/courses')
def courses():
user = current_user()
courses_list = db.list_courses()
return render_template('courses.html', user=user, courses=courses_list)

@app.route('/courses/<int:course_id>')
def course_detail(course_id: int):
user = current_user()
course = db.get_course(course_id)
if not course:
abort(404)
lessons = db.list_lessons_for_course(course_id)
completed, total = (0, len(lessons))
if user:
completed, total = db.get_course_progress(user['id'], course_id)
return render_template('course_detail.html', user=user, course=course, lessons=lessons, progress=(completed, total))

@app.route('/courses/<int:course_id>/enroll', methods=['POST'])
def enroll(course_id: int):
redirect_response = login_required()
if redirect_response:
return redirect_response
user = current_user()
if not db.get_course(course_id):
abort(404)
db.enroll_user_in_course(user_id=user['id'], course_id=course_id)
flash('Enrolled in course.', 'success')
return redirect(url_for('course_detail', course_id=course_id))

@app.route('/lessons/<int:lesson_id>')
def lesson_detail(lesson_id: int):
user = current_user()
lesson = db.get_lesson(lesson_id)
if not lesson:
abort(404)
course = db.get_course(lesson['course_id'])
quiz = db.get_quiz_by_lesson(lesson_id)
latest_attempt = None
if user and quiz:
latest_attempt = db.get_latest_quiz_attempt(user['id'], quiz['id'])
return render_template('lesson_detail.html', user=user, lesson=lesson, course=course, quiz=quiz, latest_attempt=latest_attempt)

@app.route('/lessons/<int:lesson_id>/complete', methods=['POST'])
def complete_lesson(lesson_id: int):
redirect_response = login_required()
if redirect_response:
return redirect_response
user = current_user()
if not db.get_lesson(lesson_id):
abort(404)
db.mark_lesson_completed(user['id'], lesson_id)
flash('Lesson marked complete.', 'success')
lesson = db.get_lesson(lesson_id)
return redirect(url_for('course_detail', course_id=lesson['course_id']))

@app.route('/quiz/<int:quiz_id>', methods=['GET', 'POST'])
def quiz_take(quiz_id: int):
redirect_response = login_required()
if redirect_response:
return redirect_response
user = current_user()
quiz = db.get_quiz(quiz_id)
if not quiz:
abort(404)
questions = db.get_questions_for_quiz(quiz_id)

if request.method == 'POST':
score = 0
total = len(questions)
for q in questions:
chosen = request.form.get(f'q{q["id"]}')
if chosen and chosen == q['correct_choice']:
score += 1
db.record_quiz_attempt(user['id'], quiz_id, score, total)
flash(f'Quiz submitted. Score: {score}/{total}', 'success')
return redirect(url_for('lesson_detail', lesson_id=quiz['lesson_id']))

return render_template('quiz.html', user=user, quiz=quiz, questions=questions)

@app.route('/admin', methods=['GET', 'POST'])
def admin():
redirect_response = login_required()
if redirect_response:
return redirect_response
admin_required()

if request.method == 'POST':
action = request.form.get('action')

if action == 'create_course':
title = request.form.get('title', '').strip()
description = request.form.get('description', '').strip()
if title and description:
db.create_course(title, description)
flash('Course created.', 'success')
else:
flash('Title and description required.', 'error')

elif action == 'create_lesson':
course_id = int(request.form.get('course_id', '0'))
title = request.form.get('lesson_title', '').strip()
content = request.form.get('content', '').strip()
video_url = request.form.get('video_url', '').strip() or None
order_index = int(request.form.get('order_index', '0'))
if db.get_course(course_id) and title and content:
db.create_lesson(course_id, title, content, video_url, order_index)
flash('Lesson created.', 'success')
else:
flash('Valid course, title, and content required.', 'error')

elif action == 'create_quiz':
lesson_id = int(request.form.get('lesson_id', '0'))
title = request.form.get('quiz_title', '').strip()
if db.get_lesson(lesson_id) and title:
quiz_id = db.create_quiz(lesson_id, title)
# Optional: add one sample question if provided
q_text = request.form.get('q_text', '').strip()
a = request.form.get('choice_a', '').strip()
b = request.form.get('choice_b', '').strip()
c = request.form.get('choice_c', '').strip()
d = request.form.get('choice_d', '').strip()
correct = request.form.get('correct_choice', 'a').strip()
if q_text and a and b and c and d and correct in {'a', 'b', 'c', 'd'}:
db.add_question(quiz_id, q_text, a, b, c, d, correct)
flash('Quiz created.', 'success')
else:
flash('Valid lesson and title required.', 'error')

courses_list = db.list_courses()
selected_course_id = request.args.get('course_id')
lessons_for_selected = []
if selected_course_id:
try:
lessons_for_selected = db.list_lessons_for_course(int(selected_course_id))
except Exception:
lessons_for_selected = []
return render_template('admin.html', user=current_user(), courses=courses_list, lessons=lessons_for_selected)

return app


if __name__ == '__main__':
application = create_app()
port = int(os.environ.get('PORT', '5000'))
application.run(host='0.0.0.0', port=port, debug=True)
Loading