Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,81 @@ Optionally, you may checkout the `edly-io/forum_v2 <https://github.com/edly-io/e
git checkout forum_v2
tutor mounts add .

Forum v2 Waffle Flag
********************

Description
-----------
The ``forum_v2.enable_mysql_backend`` waffle flag is used to toggle the use of the MySQL backend instead of the MongoDB backend.

Details
-------
- **Toggle Name**: ``forum_v2.enable_mysql_backend``
- **Implementation**: CourseWaffleFlag
- **Default**: False
- **Description**: Waffle flag to use the MySQL backend instead of Mongo backend.
- **Use Cases**: temporary, open_edx
- **Creation Date**: 2025-12-05
- **Target Removal Date**: 2025-12-05

Usage
-----
This waffle flag is automatically enabled for a course when the ``migrate_courses_to_mysql`` command is run for that course. It can also be manually added through the Django admin interface.

Migration from MongoDB to MySQL backend:
****************************************

python manage.py forum_migrate_course_from_mongodb_to_mysql

Description
-----------
This command migrates data from MongoDB to MySQL for the specified courses.

Usage
-----
To migrate data for a specific course(s), run the command with the course ID(s) as argument:

python manage.py forum_migrate_course_from_mongodb_to_mysql <course_id_1> <course_id_2>

To migrate data for all courses, run the command with the ``all`` argument:

python manage.py forum_migrate_course_from_mongodb_to_mysql all

What the command does
---------------------
The command performs the following steps:

1. **Migrates user data**: Migrates user data from MongoDB to MySQL.
2. **Migrates content data**: Migrates content data from MongoDB to MySQL.
3. **Migrates read state data**: Migrates read state data from MongoDB to MySQL.
4. **Enables waffle flag**: Enables the ``forum_v2.enable_mysql_backend`` waffle flag for the specified course.


``python manage.py forum_delete_course_from_mongodb``

Description
-----------
This command deletes course data from MongoDB for the specified courses.

Usage
-----
To delete data for a specific course(s), run the command with the course ID(s) as an argument:

python manage.py forum_delete_course_from_mongodb <course_id_1> <course_id_2>

To delete data for all courses, run the command with the ``all`` argument:

python manage.py forum_delete_course_from_mongodb all

Options
-------
* ``--dry-run``: Perform a dry run without actually deleting data.

Example
-------

python manage.py forum_delete_course_from_mongodb <course_id> --dry-run

.. Deploying
.. *********

Expand Down
23 changes: 16 additions & 7 deletions forum/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@
from forum.backends.mysql.api import MySQLBackend


def is_mysql_backend_enabled(course_id: str | None) -> bool:
"""
Return True if mysql backend is enabled for the course.
"""
try:
from forum.toggles import ENABLE_MYSQL_BACKEND # pylint: disable=C0415
except ImportError:
return True

return ENABLE_MYSQL_BACKEND.is_enabled(course_id)


def get_backend(
course_id: Optional[str] = "",
course_id: Optional[str] = None,
) -> Callable[[], MongoBackend | MySQLBackend]:
"""Return a factory function that lazily loads the backend API based on course_id."""

def _get_backend() -> MongoBackend | MySQLBackend:
if not course_id:
# Lazy loading MongoBackend
return MongoBackend()
# TODO: add condition for course waffle flag.
# Lazy loading MySQLBackend
return MySQLBackend()
if is_mysql_backend_enabled(course_id):
return MySQLBackend()
return MongoBackend()

return _get_backend
52 changes: 52 additions & 0 deletions forum/management/commands/forum_delete_course_from_mongodb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Deletion command for a course data in mongodb."""

from typing import Any

from django.core.management.base import BaseCommand, CommandParser

from forum.migration_helpers import delete_course_data, get_all_course_ids
from forum.mongo import get_database


class Command(BaseCommand):
"""Deletion command for a course data in mongodb."""

help = "Delete data from MongoDB for specific courses or all courses"

def add_arguments(self, parser: CommandParser) -> None:
parser.add_argument(
"courses", nargs="+", type=str, help="List of course IDs or `all`"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Perform a dry run without actually deleting data",
)

def handle(self, *args: str, **options: dict[str, Any]) -> None:
"""Handle method for command."""
db = get_database()

courses: list[str] = list(options["courses"])
dry_run: bool = bool(options["dry_run"])

if dry_run:
self.stdout.write(
self.style.WARNING("Performing dry run. No data will be deleted.")
)

if "all" in courses:
courses = get_all_course_ids(db)

for course_id in courses:
self.stdout.write(f"Deleting data for course: {course_id}")
delete_course_data(db, course_id, dry_run, self.stdout)

if dry_run:
self.stdout.write(
self.style.SUCCESS("Dry run completed. No data was deleted.")
)
else:
self.stdout.write(
self.style.SUCCESS("Data deletion completed successfully")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Migration command for courses from mongodb to mysql."""

from typing import Any

from django.core.management.base import BaseCommand
from django.core.management.base import CommandParser

from forum.migration_helpers import (
enable_mysql_backend_for_course,
get_all_course_ids,
migrate_content,
migrate_read_states,
migrate_users,
)
from forum.mongo import get_database


class Command(BaseCommand):
"""Migration command for courses from mongodb to mysql."""

help = "Migrate data from MongoDB to MySQL"

def add_arguments(self, parser: CommandParser) -> None:
"""Add arguments to the command."""
parser.add_argument(
"courses", nargs="+", type=str, help="List of course IDs or `all`"
)

def handle(self, *args: str, **options: dict[str, Any]) -> None:
"""Handle the command."""
db = get_database()

courses = list(options["courses"])
if "all" in courses:
courses = get_all_course_ids(db)

for course_id in courses:
self.stdout.write(f"Migrating data for course: {course_id}")
migrate_users(db, course_id)
migrate_content(db, course_id)
migrate_read_states(db, course_id)
enable_mysql_backend_for_course(course_id)
self.stdout.write(
f"Enabled mysql backend waffle flag for course {course_id}."
)

self.stdout.write(self.style.SUCCESS("Data migration completed successfully"))
Loading