from flask import Flask, jsonify, render_template, request, redirect, url_for from flask_sqlalchemy import SQLAlchemy import os from werkzeug.utils import secure_filename app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///coursels.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False UPLOAD_FOLDER = 'static/uploads' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) db = SQLAlchemy(app) class Subject(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) icon_data = db.Column(db.LargeBinary, nullable=True) # Image data stored in DB icon_filename = db.Column(db.String(200), nullable=True) # Original filename icon_mimetype = db.Column(db.String(100), nullable=True) # MIME type categories = db.relationship('Category', backref='subject', lazy=True) class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) subject_id = db.Column(db.Integer, db.ForeignKey('subject.id'), nullable=False) articles = db.relationship('Article', backref='category', lazy=True) class Article(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) icon_data = db.Column(db.LargeBinary, nullable=True) # Image data stored in DB icon_filename = db.Column(db.String(200), nullable=True) # Original filename icon_mimetype = db.Column(db.String(100), nullable=True) # MIME type class Attachment(db.Model): id = db.Column(db.Integer, primary_key=True) data = db.Column(db.LargeBinary, nullable=False) filename = db.Column(db.String(200), nullable=False) mimetype = db.Column(db.String(100), nullable=False) @app.route('/') def home(): subjects = Subject.query.all() # Add icon_url to each subject for template use for subject in subjects: if subject.icon_data: subject.icon_url = url_for('get_subject_icon', subject_id=subject.id) else: subject.icon_url = None return render_template('home.html', subjects=subjects) @app.route('/subjects//categories') def categories(subject_id): subject = Subject.query.get_or_404(subject_id) categories = Category.query.filter_by(subject_id=subject_id).all() return render_template('categories.html', subject=subject, categories=categories) @app.route('/categories//articles') def articles(category_id): category = Category.query.get_or_404(category_id) articles = Article.query.filter_by(category_id=category_id).all() return render_template('articles.html', category=category, articles=articles) @app.route('/articles/') def article(article_id): article = Article.query.get_or_404(article_id) return render_template('article.html', article=article) # API endpoints for JSON if needed @app.route('/api/subjects') def api_subjects(): subjects = Subject.query.all() subject_list = [] for s in subjects: icon_url = url_for('get_subject_icon', subject_id=s.id) if s.icon_data else None subject_list.append({'id': s.id, 'name': s.name, 'icon': icon_url}) return jsonify(subject_list) @app.route('/api/subjects//categories') def api_categories(subject_id): categories = Category.query.filter_by(subject_id=subject_id).all() return jsonify([{'id': c.id, 'name': c.name} for c in categories]) @app.route('/api/categories//articles') def api_articles(category_id): articles = Article.query.filter_by(category_id=category_id).all() return jsonify([{'id': a.id, 'title': a.title} for a in articles]) @app.route('/api/articles/') def api_article(article_id): article = Article.query.get_or_404(article_id) return jsonify({'id': article.id, 'title': article.title, 'content': article.content}) # Admin routes @app.route('/admin') def admin_home(): return render_template('admin_home.html') @app.route('/admin/articles') def admin_articles(): articles = Article.query.all() return render_template('admin_articles.html', articles=articles) @app.route('/admin/articles/edit/', methods=['GET', 'POST']) def admin_edit_article(article_id): article = Article.query.get_or_404(article_id) if request.method == 'POST': article.title = request.form['title'] article.content = request.form['content'] # Handle file upload if provided if 'icon' in request.files and request.files['icon'].filename: file = request.files['icon'] file_data = file.read() filename = secure_filename(file.filename) article.icon_data = file_data article.icon_filename = filename article.icon_mimetype = file.mimetype db.session.commit() return redirect(url_for('admin_articles')) return render_template('admin_edit_article.html', article=article) @app.route('/admin/articles/new', methods=['GET', 'POST']) def admin_new_article(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] category_id = request.form['category_id'] new_article = Article(title=title, content=content, category_id=category_id) db.session.add(new_article) db.session.commit() # Handle file upload if provided if 'icon' in request.files and request.files['icon'].filename: file = request.files['icon'] file_data = file.read() filename = secure_filename(file.filename) new_article.icon_data = file_data new_article.icon_filename = filename new_article.icon_mimetype = file.mimetype db.session.commit() return redirect(url_for('admin_articles')) categories = Category.query.all() return render_template('admin_new_article.html', categories=categories) @app.route('/admin/articles/delete/', methods=['POST']) def admin_delete_article(article_id): article = Article.query.get_or_404(article_id) db.session.delete(article) db.session.commit() return redirect(url_for('admin_articles')) @app.route('/admin/subjects') def admin_subjects(): subjects = Subject.query.all() return render_template('admin_subjects.html', subjects=subjects) @app.route('/admin/subjects/new', methods=['GET', 'POST']) def admin_new_subject(): if request.method == 'POST': name = request.form['name'] new_subject = Subject(name=name) db.session.add(new_subject) db.session.commit() # Handle file upload if provided if 'icon' in request.files and request.files['icon'].filename: file = request.files['icon'] file_data = file.read() filename = secure_filename(file.filename) new_subject.icon_data = file_data new_subject.icon_filename = filename new_subject.icon_mimetype = file.mimetype db.session.commit() return redirect(url_for('admin_subjects')) return render_template('admin_new_subject.html') @app.route('/admin/subjects/edit/', methods=['GET', 'POST']) def admin_edit_subject(subject_id): subject = Subject.query.get_or_404(subject_id) if request.method == 'POST': subject.name = request.form['name'] # Handle file upload if provided if 'icon' in request.files and request.files['icon'].filename: file = request.files['icon'] file_data = file.read() filename = secure_filename(file.filename) subject.icon_data = file_data subject.icon_filename = filename subject.icon_mimetype = file.mimetype db.session.commit() return redirect(url_for('admin_subjects')) return render_template('admin_edit_subject.html', subject=subject) @app.route('/admin/subjects/delete/', methods=['POST']) def admin_delete_subject(subject_id): subject = Subject.query.get_or_404(subject_id) db.session.delete(subject) db.session.commit() return redirect(url_for('admin_subjects')) @app.route('/admin/categories') def admin_categories(): categories = Category.query.all() return render_template('admin_categories.html', categories=categories) @app.route('/admin/categories/new', methods=['GET', 'POST']) def admin_new_category(): if request.method == 'POST': name = request.form['name'] subject_id = request.form['subject_id'] new_category = Category(name=name, subject_id=subject_id) db.session.add(new_category) db.session.commit() return redirect(url_for('admin_categories')) subjects = Subject.query.all() return render_template('admin_new_category.html', subjects=subjects) @app.route('/admin/categories/edit/', methods=['GET', 'POST']) def admin_edit_category(category_id): category = Category.query.get_or_404(category_id) if request.method == 'POST': category.name = request.form['name'] category.subject_id = request.form['subject_id'] db.session.commit() return redirect(url_for('admin_categories')) subjects = Subject.query.all() return render_template('admin_edit_category.html', category=category, subjects=subjects) @app.route('/admin/categories/delete/', methods=['POST']) def admin_delete_category(category_id): category = Category.query.get_or_404(category_id) db.session.delete(category) db.session.commit() return redirect(url_for('admin_categories')) def add_sample_data(): if Subject.query.count() == 0: math = Subject(name="Mathématiques") physics = Subject(name="Physique") db.session.add(math) db.session.add(physics) db.session.commit() algebra = Category(name="Algèbre", subject=math) geometry = Category(name="Géométrie", subject=math) mechanics = Category(name="Mécanique", subject=physics) db.session.add(algebra) db.session.add(geometry) db.session.add(mechanics) db.session.commit() Article(title="Équations linéaires", content="

Les équations linéaires sont de la forme ax + b = 0.

", category=algebra) Article(title="Triangles", content="

Un triangle a trois côtés.

", category=geometry) Article(title="Lois de Newton", content="

La première loi de Newton...

", category=mechanics) db.session.commit() @app.route('/upload/subject/', methods=['POST']) def upload_subject_icon(subject_id): subject = Subject.query.get_or_404(subject_id) if 'file' not in request.files: return jsonify({'error': 'No file'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 # Read file data file_data = file.read() filename = secure_filename(file.filename) # Store in database subject.icon_data = file_data subject.icon_filename = filename subject.icon_mimetype = file.mimetype db.session.commit() return jsonify({'success': True, 'filename': filename}) @app.route('/upload/article/', methods=['POST']) def upload_article_image(article_id): article = Article.query.get_or_404(article_id) if 'file' not in request.files: return jsonify({'error': 'No file'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 # Read file data file_data = file.read() filename = secure_filename(file.filename) # Store in database article.icon_data = file_data article.icon_filename = filename article.icon_mimetype = file.mimetype db.session.commit() return jsonify({'success': True, 'filename': filename}) @app.route('/upload', methods=['POST']) def upload_attachment(): if 'file' not in request.files: return jsonify({'error': 'No file'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 # Read file data file_data = file.read() filename = secure_filename(file.filename) # Store in database attachment = Attachment(data=file_data, filename=filename, mimetype=file.mimetype) db.session.add(attachment) db.session.commit() url = url_for('get_attachment', attachment_id=attachment.id) return jsonify({'url': url}) @app.route('/subject//icon') def get_subject_icon(subject_id): subject = Subject.query.get_or_404(subject_id) if subject.icon_data: return subject.icon_data, 200, {'Content-Type': subject.icon_mimetype or 'image/png'} else: return '', 404 @app.route('/article//image') def get_article_image(article_id): article = Article.query.get_or_404(article_id) if article.icon_data: return article.icon_data, 200, {'Content-Type': article.icon_mimetype or 'image/png'} else: return '', 404 @app.route('/attachment/') def get_attachment(attachment_id): attachment = Attachment.query.get_or_404(attachment_id) return attachment.data, 200, {'Content-Type': attachment.mimetype} if __name__ == '__main__': with app.app_context(): db.create_all() add_sample_data() app.run(debug=True)