diff --git a/.gitignore b/.gitignore index bee8a64..462b4ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__ +students.db diff --git a/app.py b/app.py index 0b04db8..7107b66 100644 --- a/app.py +++ b/app.py @@ -1,26 +1,28 @@ import sqlite3 +import csv +import io +# The 'flash' import is no longer needed from flask import Flask, render_template, request, redirect, url_for # Create a Flask application instance app = Flask(__name__) +# The 'secret_key' is no longer required as we are not using flash/sessions +# app.secret_key = 'your_super_secret_key_12345' + # --- Database Functions --- def get_db_connection(): """Creates and returns a connection to the SQLite database.""" conn = sqlite3.connect('students.db') - # Return rows as dictionaries, so we can access columns by name conn.row_factory = sqlite3.Row return conn def init_db(): - """Initializes the database and creates the 'students' table if it doesn't exist.""" + """Initializes the database. THIS IS ONLY RUN MANUALLY from the command line.""" conn = get_db_connection() - # Use a 'with' statement to automatically handle closing the connection with conn: - # Drop the table if it exists to ensure the new schema is applied conn.execute('DROP TABLE IF EXISTS students') - # Create the table with the new, expanded schema conn.execute(''' CREATE TABLE students ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -38,10 +40,9 @@ def init_db(): ''') print("Database initialized and 'students' table created with the new schema.") -# Command to initialize the database from the command line: flask init-db @app.cli.command('init-db') def init_db_command(): - """Clear existing data and create new tables.""" + """Clears existing data and creates new tables.""" init_db() @@ -49,43 +50,119 @@ def init_db_command(): @app.route('/') def index(): - """Renders the main page with the list of students.""" + """Renders the main page and displays any messages passed in the URL.""" + # Get the message from the URL query parameters + message = request.args.get('message', None) + category = request.args.get('category', None) + conn = get_db_connection() - # Select all columns to display in the table students = conn.execute('SELECT * FROM students ORDER BY id').fetchall() conn.close() - # The 'students' variable is passed to the HTML template - return render_template('index.html', students=students) + + # Pass the message and students to the template + return render_template('index.html', students=students, message=message, category=category) @app.route('/add', methods=['POST']) def add_student(): - """Handles the form submission for adding a new student.""" - # Get all the new data from the form submitted in index.html - student_name = request.form['student_name'] - age = request.form['age'] - parent_name = request.form['parent_name'] - parent_phone_1 = request.form['parent_phone_1'] - parent_phone_2 = request.form.get('parent_phone_2') # .get() for optional fields - student_phone = request.form.get('student_phone') # .get() for optional fields - grade = request.form['grade'] - school_name = request.form['school_name'] - address = request.form['address'] - memorizing = request.form['memorizing'] + """Handles the form submission for adding a single new student.""" + try: + student_name = request.form['student_name'] + age = request.form['age'] + parent_name = request.form['parent_name'] + parent_phone_1 = request.form['parent_phone_1'] + parent_phone_2 = request.form.get('parent_phone_2') + student_phone = request.form.get('student_phone') + grade = request.form['grade'] + school_name = request.form['school_name'] + address = request.form['address'] + memorizing = request.form['memorizing'] + + if not all([student_name, age, parent_name, parent_phone_1, grade, school_name, address, memorizing]): + # Instead of flashing, redirect with a message + return redirect(url_for('index', message='الرجاء تعبئة جميع الحقول المطلوبة.', category='danger')) + conn = get_db_connection() + conn.execute(''' + INSERT INTO students (student_name, age, parent_name, parent_phone_1, parent_phone_2, student_phone, grade, school_name, address, memorizing) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (student_name, age, parent_name, parent_phone_1, parent_phone_2, student_phone, grade, school_name, address, memorizing)) + conn.commit() + conn.close() + + message = 'تمت إضافة الطالب الجديد بنجاح!' + category = 'success' + except Exception as e: + message = f'حدث خطأ أثناء إضافة الطالب: {e}' + category = 'danger' - # Insert the new student record into the database with all fields - conn = get_db_connection() - conn.execute(''' - INSERT INTO students (student_name, age, parent_name, parent_phone_1, parent_phone_2, student_phone, grade, school_name, address, memorizing) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', (student_name, age, parent_name, parent_phone_1, parent_phone_2, student_phone, grade, school_name, address, memorizing)) - conn.commit() - conn.close() + return redirect(url_for('index', message=message, category=category)) + +@app.route('/import', methods=['POST']) +def import_csv(): + """Handles CSV import and redirects with a status message in the URL.""" + if 'file' not in request.files or request.files['file'].filename == '': + return redirect(url_for('index', message='لم يتم اختيار أي ملف.', category='danger')) + + file = request.files['file'] + + if file and file.filename.endswith('.csv'): + students_to_add = [] # This is our "2D vector" + try: + stream = io.StringIO(file.stream.read().decode("utf-8-sig"), newline=None) + csv_reader = csv.reader(stream) + + # First, read all rows from CSV into the students_to_add list + for i, row in enumerate(csv_reader, start=1): + if len(row) != 10: + msg = f'خطأ في ملف CSV في السطر رقم {i}: يجب أن يحتوي السطر على 10 أعمدة.' + return redirect(url_for('index', message=msg, category='danger')) + try: + # Validate and prepare the row data + student_data = ( + row[0], int(row[1]), row[2], row[3], + row[4] if row[4] else None, row[5] if row[5] else None, + row[6], row[7], row[8], row[9] + ) + students_to_add.append(student_data) + except ValueError: + msg = f"خطأ في ملف CSV في السطر رقم {i}: يجب أن يكون العمر (العمود الثاني) رقماً." + return redirect(url_for('index', message=msg, category='danger')) + + # If the list was successfully built, proceed to add to the database + if students_to_add: + conn = get_db_connection() + # Use a try/except block for the database operation + try: + # Now, loop through the "2D vector" and add each student one by one + for student in students_to_add: + conn.execute(''' + INSERT INTO students (student_name, age, parent_name, parent_phone_1, parent_phone_2, student_phone, grade, school_name, address, memorizing) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', student) + + # If all insertions succeed, commit them to the database + conn.commit() + message = f'تم استيراد وإضافة {len(students_to_add)} طالب بنجاح!' + category = 'success' + except sqlite3.Error as e: + # If any error occurs during insertion, the transaction is rolled back automatically + message = f'حدث خطأ في قاعدة البيانات أثناء الإضافة: {e}' + category = 'danger' + finally: + # Always close the connection + conn.close() + else: + message = 'ملف CSV فارغ أو لا يمكن معالجته.' + category = 'warning' + + except Exception as e: + message = f'حدث خطأ غير متوقع أثناء عملية الاستيراد: {e}' + category = 'danger' + + return redirect(url_for('index', message=message, category=category)) + else: + return redirect(url_for('index', message='صيغة الملف غير صالحة. الرجاء رفع ملف .csv فقط.', category='danger')) - # Redirect back to the main page to see the updated list - return redirect(url_for('index')) if __name__ == '__main__': - # You can remove the init_db() call from here if you are using the flask command - # It's good practice to manage the DB initialization separately. app.run(debug=True) diff --git a/templates/index.html b/templates/index.html index c60b993..7a4d29d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,104 +1,150 @@ - +
-{{ message }}
+Enter and view student details below.
+أدخل بيانات الطلاب يدوياً أو قم باستيراد ملف CSV.