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
10 changes: 9 additions & 1 deletion openedx_wikilearn_features/wikimedia_general/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import admin
from openedx.core.djangoapps.user_api.models import UserPreference

from django.contrib import admin
from openedx_wikilearn_features.wikimedia_general.models import Topic


@admin.register(UserPreference)
Expand All @@ -13,3 +14,10 @@ class UserPreferenceAdmin(admin.ModelAdmin):
exclude = ("id",)
list_per_page = 100
list_max_show_all = 100


@admin.register(Topic)
class TopicAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ("name",)
list_per_page = 100
17 changes: 17 additions & 0 deletions openedx_wikilearn_features/wikimedia_general/api/v0/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from rest_framework import serializers
from openedx_wikilearn_features.wikimedia_general.models import Topic


class TopicSerializer(serializers.ModelSerializer):
"""Serializer for Topic model"""

class Meta:
model = Topic
fields = ['id', 'name', 'created', 'modified']
read_only_fields = ['id', 'created', 'modified']

def validate_name(self, value):
"""Ensure topic name is unique (case-insensitive)"""
if Topic.objects.filter(name__iexact=value.strip()).exists():
raise serializers.ValidationError("A topic with this name already exists.")
return value.strip()
8 changes: 5 additions & 3 deletions openedx_wikilearn_features/wikimedia_general/api/v0/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

from openedx_wikilearn_features.wikimedia_general.api.v0.views import (
RetrieveLMSTabs,
RetrieveWikiMetaData,
create_topic,
get_language_selector_is_enabled,
get_released_languages,
RetrieveWikiMetaData,
)

app_name = "general_api_v0"
Expand All @@ -23,8 +24,9 @@
name="language_selector_is_enabled",
),
re_path(
fr'^wiki_metadata/{settings.COURSE_KEY_PATTERN}',
rf"^wiki_metadata/{settings.COURSE_KEY_PATTERN}",
RetrieveWikiMetaData.as_view(),
name='course_font',
name="course_font",
),
re_path(r"topics", create_topic, name="create_topic"),
]
38 changes: 34 additions & 4 deletions openedx_wikilearn_features/wikimedia_general/api/v0/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
"""

from django.contrib.auth.decorators import login_required
from lms.djangoapps.courseware.courses import get_course_by_id
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.lang_pref.api import header_language_selector_is_enabled, released_languages
from rest_framework import generics, status, permissions
from rest_framework import generics, permissions, status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from opaque_keys.edx.keys import CourseKey

from lms.djangoapps.courseware.courses import get_course_by_id

from openedx_wikilearn_features.wikimedia_general.api.v0.utils import (
get_authenticated_header_tabs,
get_unauthenticated_header_tabs,
)

from .serializers import TopicSerializer


class RetrieveLMSTabs(generics.RetrieveAPIView):
"""
Expand Down Expand Up @@ -96,3 +97,32 @@ def get(self, request, *args, **kwargs):
}

return Response(data, status=status.HTTP_200_OK)


@api_view(['POST'])
@login_required
def create_topic(request):
"""
Create a new Topic.

POST /api/v0/topics/
Body: {"name": "Topic Name"}

Returns:
201: Topic created successfully
400: Invalid data or topic already exists
"""
serializer = TopicSerializer(data=request.data)

if serializer.is_valid():
serializer.save()
return Response(
serializer.data,
status=status.HTTP_201_CREATED
)

return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.20 on 2025-12-18 09:10

from django.db import migrations, models
import django.utils.timezone
import model_utils.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Topic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('name', models.CharField(max_length=250)),
],
),
]
Empty file.
15 changes: 15 additions & 0 deletions openedx_wikilearn_features/wikimedia_general/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.db import models

from model_utils.models import TimeStampedModel

class Topic(TimeStampedModel):
"""
Model for storing the names of possible Topics that a course can have.
"""
class Meta:
app_label = "wikimedia_general"

name = models.CharField(max_length=250)

def __str__(self):
return self.name
50 changes: 44 additions & 6 deletions openedx_wikilearn_features/wikimedia_general/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

import pytz
import six
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from common.djangoapps.student.views import (
get_course_enrollments,
get_org_black_and_whitelist_for_site,
)
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Q
Expand All @@ -25,7 +25,6 @@
from lms.djangoapps.grades.api import CourseGradeFactory
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.core.djangoapps.user_api.models import UserPreference
from openedx.features.course_experience.utils import get_course_outline_block_tree
Expand All @@ -40,6 +39,7 @@
from openedx_wikilearn_features.wikimedia_general.djangoapps_patches.instructor_task.patches import (
EnhancedSubtaskStatus,
)
from openedx_wikilearn_features.wikimedia_general.models import Topic

log = logging.getLogger(__name__)
User = get_user_model()
Expand Down Expand Up @@ -106,6 +106,9 @@ def get_course_users_with_preference(post_id):
list: A list of User objects who have the specified preference set. These users include students, instructors,
and staff.
"""
# Lazy import to avoid circular import
CourseEnrollment = apps.get_model('student', 'CourseEnrollment')

course_key = CourseKey.from_string(post_id)
log.info(f"Fetching users with forum roles for course_key: {course_key}")

Expand Down Expand Up @@ -203,6 +206,9 @@ def get_follow_up_courses(user):
"""
Returns courses which have courses in course_keys as their prerequisite
"""
# Lazy import to avoid circular import
CourseOverview = apps.get_model('course_overviews', 'CourseOverview')

follow_up_courses = []
if user.is_authenticated:
course_keys = get_user_completed_course_keys(user)
Expand Down Expand Up @@ -265,10 +271,10 @@ def get_users_course_completion_stats(users, users_enrollments, course_keys):

def get_course_enrollment_and_completion_stats(course_id) -> dict:
"""Returns the count of student completed the provided course"""
enrollments = CourseEnrollment.objects.filter(
course_id=course_id,
is_active=True,
)
# Lazy import to avoid circular import
CourseEnrollment = apps.get_model('student', 'CourseEnrollment')

enrollments = CourseEnrollment.objects.filter(course_id=course_id, is_active=True)

total_learners_completed = 0
total_cert_generated = 0
Expand Down Expand Up @@ -449,6 +455,38 @@ def _get_thread_url_weekly_digest(thread_context,common_context):
return urljoin(base_url, permalink(thread_content))


def update_other_course_settings(other_settings, course, user):
"""
Updates the 'other course settings' for a specified course.

This function merges the provided `other_settings` into the existing
`other_course_settings` of the course object. It then updates the course
settings and logs the action.

Args:
other_settings (dict): A dictionary containing the new settings to
be added or updated in the course's other settings.
course (Course): The course object to be updated.
user (User): The user performing the update, typically used for
permission checks and auditing.
"""
# Lazy import to avoid circular import
from cms.djangoapps.contentstore.views.course import update_course_advanced_settings # noqa: F401

updated_other_course_settings = {**course.other_course_settings, **other_settings}
update_data = {"other_course_settings": {"value": updated_other_course_settings}}
update_course_advanced_settings(course, update_data, user)
log.info(f"Updated course with key: {course.id} with updated other settings: {updated_other_course_settings}")


def get_topics():
"""
Returns the list of current topics available
"""
topics = Topic.objects.values_list('name', flat=True)
return topics


def patch_function(modules, func_name, replacement):
"""Patch a loaded module with a proxy object that has all the override registry properties.
Arguments:
Expand Down