added crud operations for Course
This commit is contained in:
@@ -0,0 +1,159 @@
|
|||||||
|
from telebot import TeleBot, types
|
||||||
|
from database.models import Courses
|
||||||
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
|
from bot.utils.formatters import format_course_info
|
||||||
|
from bot.handlers.start import startMarkup
|
||||||
|
|
||||||
|
# TODO: Add Option for selecting Teacher in a more user-friendly way (name instead of ID) + pagination for it.
|
||||||
|
# TODO: Add Buttons for offering multiple choices for fields like language and difficulty.
|
||||||
|
|
||||||
|
|
||||||
|
def register(bot: TeleBot):
|
||||||
|
cancelMarkup = types.InlineKeyboardMarkup()
|
||||||
|
cancelMarkup.add(types.InlineKeyboardButton(
|
||||||
|
"Cancel", callback_data="cancel"))
|
||||||
|
|
||||||
|
COURSE_FIELDS = [
|
||||||
|
('name', "the course's name"),
|
||||||
|
('teacher_id', "The course's Teacher ID"),
|
||||||
|
('description', "description (<Optional>)"),
|
||||||
|
('language', "language ('english', 'spanish', 'german', 'french', 'persian')"),
|
||||||
|
('difficulty', "difficulty (<Optional>) ('beginner', 'intermediate', 'expert')"),
|
||||||
|
]
|
||||||
|
EDITABLE_FIELDS = {
|
||||||
|
'name': 1,
|
||||||
|
'description': 5,
|
||||||
|
'difficulty': 6,
|
||||||
|
'language': 7
|
||||||
|
}
|
||||||
|
|
||||||
|
# Showing details of a Course
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('course_'))
|
||||||
|
def show_course_details(call):
|
||||||
|
course_id = call.data.split('_')[1]
|
||||||
|
course = Courses.getCourseById(course_id)
|
||||||
|
|
||||||
|
if not course:
|
||||||
|
bot.send_message(call.message.chat.id, "Course not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
details = format_course_info(course)
|
||||||
|
markup = create_entity_markup("course", course_id)
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
f"👨🏫 Teacher's Info", callback_data=f"teacher_{course[3]}"))
|
||||||
|
|
||||||
|
bot.send_message(call.message.chat.id, details,
|
||||||
|
reply_markup=markup, parse_mode="HTML")
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Creating a Course Flow
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data == 'create_course')
|
||||||
|
def start_course_creation(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 = COURSE_FIELDS[step - 1][0]
|
||||||
|
data[field_name] = message.text
|
||||||
|
|
||||||
|
# Done collecting?
|
||||||
|
if step >= len(COURSE_FIELDS):
|
||||||
|
show_confirmation(message, data)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ask next question
|
||||||
|
field_name, prompt = COURSE_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 COURSE_FIELDS
|
||||||
|
)
|
||||||
|
msg = bot.send_message(message.chat.id, summary,
|
||||||
|
reply_markup=cancelMarkup)
|
||||||
|
bot.register_next_step_handler(msg, create_course, data)
|
||||||
|
|
||||||
|
def create_course(message, data):
|
||||||
|
if Courses.createCourse(**data):
|
||||||
|
bot.send_message(message.chat.id, "✅ Teacher created!",
|
||||||
|
reply_markup=startMarkup())
|
||||||
|
else:
|
||||||
|
bot.send_message(
|
||||||
|
message.chat.id, "❌ Failed to create course.", reply_markup=startMarkup())
|
||||||
|
|
||||||
|
# Editing a Course Flow
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_course_'))
|
||||||
|
def start_course_editing(call):
|
||||||
|
course_id = call.data.split('_')[2]
|
||||||
|
course = Courses.getCourseById(course_id)
|
||||||
|
|
||||||
|
if not course:
|
||||||
|
bot.send_message(call.message.chat.id, "Course 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, course)
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
def process_field_select(message, course: 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 = course[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)
|
||||||
|
bot.register_next_step_handler(
|
||||||
|
msg, process_value_edit, course[0], field, current_value)
|
||||||
|
|
||||||
|
def process_value_edit(message, course_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 teacher
|
||||||
|
if Courses.updateCourse(course_id, **{field: new_value}):
|
||||||
|
bot.send_message(
|
||||||
|
message.chat.id, f"✅ Course's {field} updated successfully.", reply_markup=startMarkup())
|
||||||
|
else:
|
||||||
|
bot.send_message(
|
||||||
|
message.chat.id, f"❌ Failed to update Course's {field}.", reply_markup=startMarkup())
|
||||||
|
|
||||||
|
# Deleting a Teacher
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_course_'))
|
||||||
|
def delete_course(call):
|
||||||
|
course_id = call.data.split('_')[2]
|
||||||
|
if (Courses.deleteCourse(course_id)):
|
||||||
|
bot.send_message(call.message.chat.id, "✅ Course deleted.",
|
||||||
|
reply_markup=startMarkup())
|
||||||
|
else:
|
||||||
|
bot.send_message(call.message.chat.id, "❌ Failed to delete Course.",
|
||||||
|
reply_markup=startMarkup())
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
+11
-1
@@ -1,7 +1,17 @@
|
|||||||
from . import students, teachers
|
from . import students, teachers, courses
|
||||||
|
from bot.handlers.start import startMarkup
|
||||||
|
|
||||||
|
|
||||||
def register_all_callbacks(bot):
|
def register_all_callbacks(bot):
|
||||||
"""Register all callback handlers"""
|
"""Register all callback handlers"""
|
||||||
students.register(bot)
|
students.register(bot)
|
||||||
teachers.register(bot)
|
teachers.register(bot)
|
||||||
|
courses.register(bot)
|
||||||
|
|
||||||
|
# Cancel is common among all
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data == 'cancel')
|
||||||
|
def cancel_action(call):
|
||||||
|
bot.clear_step_handler(call.message)
|
||||||
|
bot.send_message(call.message.chat.id,
|
||||||
|
"Action cancelled.", reply_markup=startMarkup())
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ from bot.utils.formatters import format_teacher_info
|
|||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
from bot.utils.crud_helpers import create_entity_markup
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
|
|
||||||
|
# TODO: Add Buttons for skipping Optional Fields (Add to all entities).
|
||||||
|
# TODO: Add Option for handling all updates at once.
|
||||||
|
# TODO: Add Authentication for sensitive actions and info.
|
||||||
|
|
||||||
|
# TODO: Add Option for seeing courses taught by a teacher in Teacher Details.
|
||||||
|
|
||||||
|
|
||||||
def register(bot: TeleBot):
|
def register(bot: TeleBot):
|
||||||
cancelMarkup = types.InlineKeyboardMarkup()
|
cancelMarkup = types.InlineKeyboardMarkup()
|
||||||
@@ -91,13 +97,6 @@ def register(bot: TeleBot):
|
|||||||
bot.send_message(
|
bot.send_message(
|
||||||
message.chat.id, "❌ Failed to create teacher.", reply_markup=startMarkup())
|
message.chat.id, "❌ Failed to create teacher.", reply_markup=startMarkup())
|
||||||
|
|
||||||
@bot.callback_query_handler(func=lambda call: call.data == 'cancel')
|
|
||||||
def cancel_action(call):
|
|
||||||
bot.clear_step_handler(call.message)
|
|
||||||
bot.send_message(call.message.chat.id,
|
|
||||||
"Action cancelled.", reply_markup=startMarkup())
|
|
||||||
bot.answer_callback_query(call.id)
|
|
||||||
|
|
||||||
# Editing a Teacher Flow
|
# Editing a Teacher Flow
|
||||||
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_teacher_'))
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_teacher_'))
|
||||||
def start_teacher_editing(call):
|
def start_teacher_editing(call):
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import logging
|
||||||
|
from telebot import types
|
||||||
|
from database.models import Courses
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def register(bot):
|
||||||
|
@bot.message_handler(func=lambda message: message.text == "Show Courses")
|
||||||
|
def get_students(message):
|
||||||
|
try:
|
||||||
|
data = Courses.getAllCourses()
|
||||||
|
if data:
|
||||||
|
markup = types.InlineKeyboardMarkup(row_width=2)
|
||||||
|
for row in data:
|
||||||
|
btn = types.InlineKeyboardButton(
|
||||||
|
f"{row[1][:26] + "..."} | id#{row[0]}", callback_data=f"course_{row[0]}")
|
||||||
|
markup.add(btn)
|
||||||
|
|
||||||
|
# add button for creating a new course
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
"➕ Create New Course", callback_data="create_course"))
|
||||||
|
|
||||||
|
bot.send_message(
|
||||||
|
message.chat.id, "Here is the data:", reply_markup=markup)
|
||||||
|
else:
|
||||||
|
bot.reply_to(message, "No data found.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in (Show Courses) handler: {e}")
|
||||||
|
bot.reply_to(message, "Sorry, an error occurred.")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from . import start, students, teachers, common
|
from . import start, students, teachers, courses, common
|
||||||
from bot.callbacks.init import register_all_callbacks
|
from bot.callbacks.init import register_all_callbacks
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ def register_all_handlers(bot):
|
|||||||
start.register(bot)
|
start.register(bot)
|
||||||
students.register(bot)
|
students.register(bot)
|
||||||
teachers.register(bot)
|
teachers.register(bot)
|
||||||
|
courses.register(bot)
|
||||||
common.register(bot) # Must be last (catch-all)
|
common.register(bot) # Must be last (catch-all)
|
||||||
|
|
||||||
# Callback handlers
|
# Callback handlers
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from telebot import types
|
from telebot import types
|
||||||
from bot.utils.auth import is_authenticated
|
from bot.utils.auth import is_authenticated
|
||||||
|
|
||||||
|
# TODO: Add Pagincation to Showing rows of entities (To all entities)
|
||||||
|
|
||||||
|
|
||||||
def register(bot):
|
def register(bot):
|
||||||
@bot.message_handler(commands=["start"])
|
@bot.message_handler(commands=["start"])
|
||||||
@@ -16,11 +18,6 @@ def register(bot):
|
|||||||
bot.reply_to(message, "Use buttons to fetch from database.",
|
bot.reply_to(message, "Use buttons to fetch from database.",
|
||||||
reply_markup=markup)
|
reply_markup=markup)
|
||||||
|
|
||||||
@bot.message_handler(func=lambda message: message.text in ["Show Courses", "Show Tag", "Show Categories"])
|
|
||||||
def handle_test_options(message):
|
|
||||||
option = message.text
|
|
||||||
bot.send_message(message.chat.id, f"You selected: {option}")
|
|
||||||
|
|
||||||
|
|
||||||
def startMarkup():
|
def startMarkup():
|
||||||
markup = types.ReplyKeyboardMarkup(
|
markup = types.ReplyKeyboardMarkup(
|
||||||
|
|||||||
+40
-6
@@ -32,15 +32,15 @@ def calculate_age(birthday_string):
|
|||||||
except:
|
except:
|
||||||
return "N/A"
|
return "N/A"
|
||||||
|
|
||||||
|
|
||||||
# Student Info Formatter
|
# Student Info Formatter
|
||||||
|
|
||||||
|
|
||||||
def format_student_info(student):
|
def format_student_info(student):
|
||||||
"""
|
"""
|
||||||
Format student data into a nice message
|
Format student data into a nice message
|
||||||
student is a tuple: (id, username, name, created_at, email, phone_number, last_seen, is_verified, birthday)
|
student is a tuple: (id, username, name, created_at, email, phone_number, last_seen, is_verified, birthday)
|
||||||
"""
|
"""
|
||||||
print(student)
|
# print(student)
|
||||||
|
|
||||||
id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday = student
|
id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday = student
|
||||||
|
|
||||||
verified_status = "✅ Verified" if is_verfied else "❌ Not Verified"
|
verified_status = "✅ Verified" if is_verfied else "❌ Not Verified"
|
||||||
@@ -73,10 +73,11 @@ def format_student_info(student):
|
|||||||
# Teacher Info Formatter
|
# Teacher Info Formatter
|
||||||
def format_teacher_info(teacher):
|
def format_teacher_info(teacher):
|
||||||
"""
|
"""
|
||||||
Format student data into a nice message
|
Format teacher data into a nice message
|
||||||
student is a tuple: (id, username, created_at, email, phone_number, last_seen, is_verified, birthday)
|
teacher is a tuple: (id, username, name, created_at, email, phone_number, last_seen, is_verified, birthday, about_me, job_title)
|
||||||
"""
|
"""
|
||||||
print(teacher)
|
# print(teacher)
|
||||||
|
|
||||||
id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title = teacher
|
id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title = teacher
|
||||||
|
|
||||||
verified_status = "✅ Verified" if is_verfied else "❌ Not Verified"
|
verified_status = "✅ Verified" if is_verfied else "❌ Not Verified"
|
||||||
@@ -107,3 +108,36 @@ def format_teacher_info(teacher):
|
|||||||
<b>ID:</b> <code>{id}</code>
|
<b>ID:</b> <code>{id}</code>
|
||||||
"""
|
"""
|
||||||
return details.strip()
|
return details.strip()
|
||||||
|
|
||||||
|
# Course Info Formatter
|
||||||
|
|
||||||
|
|
||||||
|
def format_course_info(course):
|
||||||
|
"""
|
||||||
|
Format course data into a nice message
|
||||||
|
course is a tuple: (id, name, created_at, teacher_id, updated_at, description, difficulty, language)
|
||||||
|
"""
|
||||||
|
# print(course)
|
||||||
|
|
||||||
|
id, name, created_at, teacher_id, updated_at, description, difficulty, language = course
|
||||||
|
|
||||||
|
# Format dates nicely
|
||||||
|
created_date = format_date(created_at)
|
||||||
|
updated_at = format_date(updated_at)
|
||||||
|
|
||||||
|
details = f"""
|
||||||
|
<b>📚 Course Profile</b>
|
||||||
|
|
||||||
|
<b>Name:</b> {name}
|
||||||
|
<b>Description:</b>
|
||||||
|
{description}
|
||||||
|
|
||||||
|
<b>Language:</b> {language}
|
||||||
|
<b>Difficulty:</b> {difficulty}
|
||||||
|
|
||||||
|
<b>📅 Course Info</b>
|
||||||
|
<b>Created At:</b> {created_date}
|
||||||
|
<b>Last Updated At:</b> {updated_at}
|
||||||
|
<b>ID:</b> <code>{id}</code>
|
||||||
|
"""
|
||||||
|
return details.strip()
|
||||||
|
|||||||
@@ -264,3 +264,132 @@ class Teachers:
|
|||||||
finally:
|
finally:
|
||||||
if conn:
|
if conn:
|
||||||
connection.release_db_connection(conn)
|
connection.release_db_connection(conn)
|
||||||
|
|
||||||
|
|
||||||
|
class Courses:
|
||||||
|
def getAllCourses():
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = connection.get_db_connection()
|
||||||
|
if not conn:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT id, name FROM courses")
|
||||||
|
result = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return result if result else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Database query error in getAllCourses: {e}")
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
connection.release_db_connection(conn)
|
||||||
|
|
||||||
|
def getCourseById(course_id):
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = connection.get_db_connection()
|
||||||
|
if not conn:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT id, name, created_at, teacher_id, updated_at, description, difficulty, language FROM courses WHERE id = %s", (course_id, ))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return result[0] if result else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Database query error in getCourseById: {e}")
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
connection.release_db_connection(conn)
|
||||||
|
|
||||||
|
def createCourse(name, teacher_id, description, language, difficulty):
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = connection.get_db_connection()
|
||||||
|
if not conn:
|
||||||
|
return False
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT INTO courses (name, teacher_id, description, language, difficulty) VALUES (%s, %s, %s, %s, %s)",
|
||||||
|
(name, teacher_id, description, language, difficulty)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Database query error in createCourse: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
connection.release_db_connection(conn)
|
||||||
|
|
||||||
|
def updateCourse(course_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")
|
||||||
|
values.append(value)
|
||||||
|
|
||||||
|
values.append(course_id)
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
UPDATE courses
|
||||||
|
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 updateCourse: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
connection.release_db_connection(conn)
|
||||||
|
|
||||||
|
def deleteCourse(course_id):
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = connection.get_db_connection()
|
||||||
|
if not conn:
|
||||||
|
return False
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM courses WHERE id = %s", (course_id,))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Database query error in deleteCourse: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
connection.release_db_connection(conn)
|
||||||
|
|||||||
Reference in New Issue
Block a user