From 4767a1af15c2251c8b9604d4bff3e6ae877856a3 Mon Sep 17 00:00:00 2001 From: Hosein Date: Thu, 1 Jan 2026 11:56:45 +0330 Subject: [PATCH] added CRUD for Students --- bot/callbacks/students.py | 138 +++++++++++++++++++++++++++++++++++++- bot/callbacks/teachers.py | 12 ++-- bot/handlers/start.py | 11 +-- database/models.py | 91 ++++++++++++++++++++++++- 4 files changed, 232 insertions(+), 20 deletions(-) diff --git a/bot/callbacks/students.py b/bot/callbacks/students.py index 278f56e..86c95aa 100644 --- a/bot/callbacks/students.py +++ b/bot/callbacks/students.py @@ -1,10 +1,33 @@ -from telebot import types +from telebot import types, TeleBot from database.models import Students from bot.utils.formatters import format_student_info from bot.utils.crud_helpers import create_entity_markup +from bot.handlers.start import startMarkup -def register(bot): +def register(bot: TeleBot): + cancelMarkup = types.InlineKeyboardMarkup() + cancelMarkup.add(types.InlineKeyboardButton( + "Cancel", callback_data="cancel")) + + STUDENT_FIELDS = [ + ('name', "the student's name"), + ('email', "email"), + ('phone_number', "phone ()"), + ('password', "password"), + ('username', "username"), + ('birthday', "birthday (YY/MM/DD)"), + ] + EDITABLE_FIELDS = { + 'name': 2, + 'email': 4, + 'phone_number': 5, + 'password': 3, + 'username': 1, + 'birthday': 8, + } + + # Showing details of a Student @bot.callback_query_handler(func=lambda call: call.data.startswith('student_')) def show_student_details(call): student_id = call.data.split('_')[1] @@ -20,3 +43,114 @@ def register(bot): bot.send_message(call.message.chat.id, "Student not found.") bot.answer_callback_query(call.id) + + # Creating a Student Flow + @bot.callback_query_handler(func=lambda call: call.data == 'create_student') + def create_student(call): + msg = bot.send_message(call.message.chat.id, + "Please enter following data: (enter any key to start)", + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, collect_field, {}, 0) + bot.answer_callback_query(call.id) + + def collect_field(message, data, step): + # Save previous field + if step > 0: + field_name = STUDENT_FIELDS[step - 1][0] + data[field_name] = message.text + + # Done collecting? + if step >= len(STUDENT_FIELDS): + show_confirmation(message, data) + return + + # Ask next question + field_name, prompt = STUDENT_FIELDS[step] + msg = bot.send_message(message.chat.id, f"Now enter {prompt}:", + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, collect_field, data, step + 1) + + def show_confirmation(message, data): + summary = "Is this correct? (enter any key to continue or cancel to exit)\n\n" + "\n".join( + f"{name.replace('_', ' ').title()}: {data[name]}" + for name, _ in STUDENT_FIELDS + ) + msg = bot.send_message(message.chat.id, summary, + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, create_student, data) + + def create_student(message, data): + if Students.createStudent(**data): + bot.send_message(message.chat.id, "✅ Student created!", + reply_markup=startMarkup()) + else: + bot.send_message( + message.chat.id, "❌ Failed to create student.", reply_markup=startMarkup()) + + # Editing a Student Flow + @bot.callback_query_handler(func=lambda call: call.data.startswith('edit_student_')) + def start_student_editing(call): + student_id = call.data.split('_')[2] + student = Students.getStudentById(student_id) + + if not student: + bot.send_message(call.message.chat.id, "Student not found.") + bot.answer_callback_query(call.id) + return + + editMarkup = types.ReplyKeyboardMarkup( + resize_keyboard=True, one_time_keyboard=True) + for field in list(EDITABLE_FIELDS.keys()) + ['Cancel']: + editMarkup.add(types.KeyboardButton(field.capitalize())) + + msg = bot.send_message(call.message.chat.id, + "Please enter the field you want to edit: ", reply_markup=editMarkup) + + bot.register_next_step_handler( + msg, process_field_select, student) + bot.answer_callback_query(call.id) + + def process_field_select(message, student: tuple): + field = message.text.lower() + if field == 'cancel' or field not in EDITABLE_FIELDS: + msg = "Action cancelled." if field == 'cancel' else "Invalid field. Action cancelled." + bot.send_message(message.chat.id, msg, + reply_markup=startMarkup()) + return + + current_value = student[EDITABLE_FIELDS[field]] + + msg = bot.send_message( + message.chat.id, f"Current value is: {current_value if field != 'password' else '********'}.\n Please enter new value for {field}:", reply_markup=cancelMarkup) + bot.register_next_step_handler( + msg, process_value_edit, student[0], field, current_value) + + def process_value_edit(message, student_id, field, previous_value): + new_value = message.text + + # Handle cancellation + if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)": + msg = "Action cancelled." if new_value.lower( + ) == 'cancel' else f"No changes made to {field}." + bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) + return + + # Update student + if Students.updateStudent(student_id, **{field: new_value}): + bot.send_message( + message.chat.id, f"✅ Student's {field} updated successfully.", reply_markup=startMarkup()) + else: + bot.send_message( + message.chat.id, f"❌ Failed to update Student's {field}.", reply_markup=startMarkup()) + + # Deleting a Teacher + @bot.callback_query_handler(func=lambda call: call.data.startswith('delete_student_')) + def delete_student(call): + student_id = call.data.split('_')[2] + if (Students.deleteStudent(student_id)): + bot.send_message(call.message.chat.id, "✅ Student deleted.", + reply_markup=startMarkup()) + else: + bot.send_message(call.message.chat.id, "❌ Failed to delete Student.", + reply_markup=startMarkup()) + bot.answer_callback_query(call.id) diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py index 303d676..5f152d6 100644 --- a/bot/callbacks/teachers.py +++ b/bot/callbacks/teachers.py @@ -123,20 +123,16 @@ def register(bot: TeleBot): def process_field_select(message, teacher: tuple): field = message.text.lower() - if field == 'cancel': - bot.send_message(message.chat.id, "Action cancelled.", + if field == 'cancel' or field not in EDITABLE_FIELDS: + msg = "Action cancelled." if field == 'cancel' else "Invalid field. Action cancelled." + bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) return - if field not in EDITABLE_FIELDS: - bot.send_message( - message.chat.id, "Invalid field. Action cancelled.") - return - current_value = teacher[EDITABLE_FIELDS[field]] msg = bot.send_message( - message.chat.id, f"Current value is: {current_value}.\n Please enter new value for {field}:", reply_markup=cancelMarkup) + message.chat.id, f"Current value is: {current_value if field != 'password' else '********'}.\n Please enter new value for {field}:", reply_markup=cancelMarkup) bot.register_next_step_handler( msg, process_value_edit, teacher[0], field, current_value) diff --git a/bot/handlers/start.py b/bot/handlers/start.py index f36624e..e5cbe64 100644 --- a/bot/handlers/start.py +++ b/bot/handlers/start.py @@ -12,14 +12,7 @@ def register(bot): bot.send_message( message.chat.id, "Welcome! Use buttons to fetch from database.") - markup = types.ReplyKeyboardMarkup( - row_width=2, one_time_keyboard=True, resize_keyboard=True) - btn1 = types.KeyboardButton("Show Students") - btn2 = types.KeyboardButton("Show Teachers") - btn3 = types.KeyboardButton("Show Courses") - btn4 = types.KeyboardButton("Show Tag") - btn5 = types.KeyboardButton("Show Categories") - markup.add(btn1, btn2, btn3, btn4, btn5) + markup = startMarkup() bot.reply_to(message, "Use buttons to fetch from database.", reply_markup=markup) @@ -35,7 +28,7 @@ def startMarkup(): btn1 = types.KeyboardButton("Show Students") btn2 = types.KeyboardButton("Show Teachers") btn3 = types.KeyboardButton("Show Courses") - btn4 = types.KeyboardButton("Show Tag") + btn4 = types.KeyboardButton("Show Tags") btn5 = types.KeyboardButton("Show Categories") markup.add(btn1, btn2, btn3, btn4, btn5) return markup diff --git a/database/models.py b/database/models.py index 97cdfe0..afc0d20 100644 --- a/database/models.py +++ b/database/models.py @@ -46,6 +46,94 @@ class Students: if conn: connection.release_db_connection(conn) + def createStudent(name, email, phone_number, password, username, birthday): + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return False + + cursor = conn.cursor() + cursor.execute( + "INSERT INTO students (name, email, phone_number, hashed_password, username, birthday) VALUES (%s, %s, %s, %s, %s, %s)", + (name, email, phone_number, password, + username, birthday) + ) + conn.commit() + cursor.close() + + return True + except Exception as e: + logger.error(f"Database query error in createStudent: {e}") + return False + finally: + if conn: + connection.release_db_connection(conn) + + def updateStudent(student_id, **fields): + if not fields: + return False # nothing to update + + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return False + + cursor = conn.cursor() + + # Build dynamic SET clause + columns = [] + values = [] + + for key, value in fields.items(): + columns.append(f"{key} = %s" if key != + 'password' else "hashed_password = %s") + values.append(value) + + values.append(student_id) + + query = f""" + UPDATE students + SET {', '.join(columns)} + WHERE id = %s + """ + + cursor.execute(query, tuple(values)) + conn.commit() + cursor.close() + + return True + + except Exception as e: + logger.error(f"Database query error in updateStudent: {e}") + return False + + finally: + if conn: + connection.release_db_connection(conn) + + def deleteStudent(student_id): + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return False + + cursor = conn.cursor() + cursor.execute( + "DELETE FROM students WHERE id = %s", (student_id,)) + conn.commit() + cursor.close() + + return True + except Exception as e: + logger.error(f"Database query error in deleteStudent: {e}") + return False + finally: + if conn: + connection.release_db_connection(conn) + class Teachers: def getAllTeachers(): @@ -130,7 +218,8 @@ class Teachers: values = [] for key, value in fields.items(): - columns.append(f"{key} = %s") + columns.append(f"{key} = %s" if key != + 'password' else "hashed_password = %s") values.append(value) values.append(teacher_id)