diff --git a/bot/callbacks/courses.py b/bot/callbacks/courses.py
index 2ffadf6..f8d6bf0 100644
--- a/bot/callbacks/courses.py
+++ b/bot/callbacks/courses.py
@@ -84,7 +84,7 @@ def register(bot: TeleBot):
def create_course(message, data):
if Courses.createCourse(**data):
- bot.send_message(message.chat.id, "✅ Teacher created!",
+ bot.send_message(message.chat.id, "✅ Course created!",
reply_markup=startMarkup())
else:
bot.send_message(
@@ -132,7 +132,7 @@ def register(bot: TeleBot):
new_value = message.text
# Handle cancellation
- if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)":
+ if new_value.lower() == 'cancel' or new_value == f"{previous_value}":
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())
diff --git a/bot/callbacks/init.py b/bot/callbacks/init.py
index d834403..c7fa4d5 100644
--- a/bot/callbacks/init.py
+++ b/bot/callbacks/init.py
@@ -1,4 +1,4 @@
-from . import students, teachers, courses
+from . import students, teachers, courses, tags
from bot.handlers.start import startMarkup
@@ -7,6 +7,7 @@ def register_all_callbacks(bot):
students.register(bot)
teachers.register(bot)
courses.register(bot)
+ tags.register(bot)
# Cancel is common among all
@bot.callback_query_handler(func=lambda call: call.data == 'cancel')
diff --git a/bot/callbacks/students.py b/bot/callbacks/students.py
index 86c95aa..b464914 100644
--- a/bot/callbacks/students.py
+++ b/bot/callbacks/students.py
@@ -129,7 +129,7 @@ def register(bot: TeleBot):
new_value = message.text
# Handle cancellation
- if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)":
+ if new_value.lower() == 'cancel' or new_value == f"{previous_value}":
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())
diff --git a/bot/callbacks/tags.py b/bot/callbacks/tags.py
new file mode 100644
index 0000000..54abfe7
--- /dev/null
+++ b/bot/callbacks/tags.py
@@ -0,0 +1,156 @@
+from telebot import types, TeleBot
+from database.models import Tags
+from bot.handlers.start import startMarkup
+from bot.utils.crud_helpers import create_entity_markup
+
+# TODO: Add Option for showing courses with specific tags.
+
+
+def register(bot: TeleBot):
+ cancelMarkup = types.InlineKeyboardMarkup()
+ cancelMarkup.add(types.InlineKeyboardButton(
+ "Cancel", callback_data="cancel"))
+
+ TAG_FIELDS = [
+ ('name', "the tag's name"),
+ ('slug', "The slug for tag")
+ ]
+ EDITABLE_FIELDS = {
+ 'name': 1,
+ 'slug': 2
+ }
+
+ # Showing details of a Tag
+ @bot.callback_query_handler(func=lambda call: call.data.startswith('tag_'))
+ def show_teacher_details(call):
+ tag_id = call.data.split('_')[1]
+ tag = Tags.getTagById(tag_id)
+
+ if tag:
+ details = f"""
+ 🔖 Tag Profile
+
+ Name: {tag[1]}
+ slug: {tag[2]}
+ """
+ details.strip()
+ markup = create_entity_markup("tag", tag_id)
+
+ bot.send_message(call.message.chat.id, details,
+ reply_markup=markup, parse_mode="HTML")
+ else:
+ bot.send_message(call.message.chat.id, "Tag not found.")
+
+ bot.answer_callback_query(call.id)
+
+ # Creating a Tag Flow
+ @bot.callback_query_handler(func=lambda call: call.data == 'create_tag')
+ def start_tag_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 = TAG_FIELDS[step - 1][0]
+ data[field_name] = message.text
+
+ # Done collecting?
+ if step >= len(TAG_FIELDS):
+ show_confirmation(message, data)
+ return
+
+ # Ask next question
+ field_name, prompt = TAG_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 TAG_FIELDS
+ )
+ msg = bot.send_message(message.chat.id, summary,
+ reply_markup=cancelMarkup)
+ bot.register_next_step_handler(msg, create_tag, data)
+
+ def create_tag(message, data):
+ if Tags.createTag(**data):
+ bot.send_message(message.chat.id, "✅ Tag created!",
+ reply_markup=startMarkup())
+ else:
+ bot.send_message(
+ message.chat.id, "❌ Failed to create tag.", reply_markup=startMarkup())
+
+ # Editing a Tag Flow
+
+ @bot.callback_query_handler(func=lambda call: call.data.startswith('edit_tag_'))
+ def start_tag_editing(call):
+ tag_id = call.data.split('_')[2]
+ tag = Tags.getTagById(tag_id)
+
+ if not tag:
+ bot.send_message(call.message.chat.id, "Tag 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, tag)
+ bot.answer_callback_query(call.id)
+
+ def process_field_select(message, tag: 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 = tag[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, tag[0], field, current_value)
+
+ def process_value_edit(message, tag_id, field, previous_value):
+ new_value = message.text
+
+ # Handle cancellation
+ if new_value.lower() == 'cancel' or new_value == f"{previous_value}":
+ 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 Tags.updateTag(tag_id, **{field: new_value}):
+ bot.send_message(
+ message.chat.id, f"✅ Tag's {field} updated successfully.", reply_markup=startMarkup())
+ else:
+ bot.send_message(
+ message.chat.id, f"❌ Failed to update Tag's {field}.", reply_markup=startMarkup())
+
+ # Deleting a Teacher
+ @bot.callback_query_handler(func=lambda call: call.data.startswith('delete_tag_'))
+ def delete_tag(call):
+ tag_id = call.data.split('_')[2]
+ if (Tags.deleteTag(tag_id)):
+ bot.send_message(call.message.chat.id, "✅ Tag deleted.",
+ reply_markup=startMarkup())
+ else:
+ bot.send_message(call.message.chat.id, "❌ Failed to delete Tag.",
+ reply_markup=startMarkup())
+ bot.answer_callback_query(call.id)
diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py
index cb2d9d2..9d9cc34 100644
--- a/bot/callbacks/teachers.py
+++ b/bot/callbacks/teachers.py
@@ -139,7 +139,7 @@ def register(bot: TeleBot):
new_value = message.text
# Handle cancellation
- if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)":
+ if new_value.lower() == 'cancel' or new_value == f"{previous_value}":
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())
diff --git a/bot/handlers/init.py b/bot/handlers/init.py
index fbe885c..c3ef3f6 100644
--- a/bot/handlers/init.py
+++ b/bot/handlers/init.py
@@ -1,4 +1,4 @@
-from . import start, students, teachers, courses, common
+from . import start, students, teachers, courses, tags, common
from bot.callbacks.init import register_all_callbacks
@@ -9,6 +9,7 @@ def register_all_handlers(bot):
students.register(bot)
teachers.register(bot)
courses.register(bot)
+ tags.register(bot)
common.register(bot) # Must be last (catch-all)
# Callback handlers
diff --git a/bot/handlers/tags.py b/bot/handlers/tags.py
new file mode 100644
index 0000000..be6042a
--- /dev/null
+++ b/bot/handlers/tags.py
@@ -0,0 +1,31 @@
+import logging
+from telebot import types
+from database.models import Tags
+
+logger = logging.getLogger(__name__)
+
+
+def register(bot):
+ @bot.message_handler(func=lambda message: message.text == "Show Tags")
+ def get_tags(message):
+ try:
+ data = Tags.getAllTags()
+ if data:
+ markup = types.InlineKeyboardMarkup(row_width=2)
+
+ for row in data:
+ btn = types.InlineKeyboardButton(
+ f"{row[1]} | id#{row[0]}", callback_data=f"tag_{row[0]}")
+ markup.add(btn)
+
+ # add button for creating a new tag
+ markup.add(types.InlineKeyboardButton(
+ "➕ Create New Tag", callback_data="create_tag"))
+
+ 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 Tags' handler: {e}")
+ bot.reply_to(message, "Sorry, an error occurred.")
diff --git a/database/models.py b/database/models.py
index 73f45e7..e01ddc6 100644
--- a/database/models.py
+++ b/database/models.py
@@ -393,3 +393,132 @@ class Courses:
finally:
if conn:
connection.release_db_connection(conn)
+
+
+class Tags:
+ def getAllTags():
+ conn = None
+ try:
+ conn = connection.get_db_connection()
+ if not conn:
+ return None
+
+ cursor = conn.cursor()
+ cursor.execute("SELECT id, name, slug FROM tags")
+ result = cursor.fetchall()
+ cursor.close()
+
+ return result if result else None
+ except Exception as e:
+ logger.error(f"Database query error in getAllTags: {e}")
+ return None
+ finally:
+ if conn:
+ connection.release_db_connection(conn)
+
+ def getTagById(tag_id):
+ conn = None
+ try:
+ conn = connection.get_db_connection()
+ if not conn:
+ return None
+
+ cursor = conn.cursor()
+ cursor.execute(
+ "SELECT id, name, slug FROM tags WHERE id = %s", (tag_id, ))
+ result = cursor.fetchall()
+ cursor.close()
+
+ return result[0] if result else None
+ except Exception as e:
+ logger.error(f"Database query error in getTagById: {e}")
+ return None
+ finally:
+ if conn:
+ connection.release_db_connection(conn)
+
+ def createTag(name, slug):
+ conn = None
+ try:
+ conn = connection.get_db_connection()
+ if not conn:
+ return False
+
+ cursor = conn.cursor()
+ cursor.execute(
+ "INSERT INTO tags (name, slug) VALUES (%s, %s)",
+ (name, slug)
+ )
+ conn.commit()
+ cursor.close()
+
+ return True
+ except Exception as e:
+ logger.error(f"Database query error in createTag: {e}")
+ return False
+ finally:
+ if conn:
+ connection.release_db_connection(conn)
+
+ def updateTag(tag_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(tag_id)
+
+ query = f"""
+ UPDATE tags
+ 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 updateTag: {e}")
+ return False
+
+ finally:
+ if conn:
+ connection.release_db_connection(conn)
+
+ def deleteTag(tag_id):
+ conn = None
+ try:
+ conn = connection.get_db_connection()
+ if not conn:
+ return False
+
+ cursor = conn.cursor()
+ cursor.execute(
+ "DELETE FROM tags WHERE id = %s", (tag_id,))
+ conn.commit()
+ cursor.close()
+
+ return True
+ except Exception as e:
+ logger.error(f"Database query error in deleteTag: {e}")
+ return False
+ finally:
+ if conn:
+ connection.release_db_connection(conn)