diff --git a/common/djangoapps/student/admin.py b/common/djangoapps/student/admin.py index 1c73937c1571..35163a2dd4e7 100644 --- a/common/djangoapps/student/admin.py +++ b/common/djangoapps/student/admin.py @@ -2,6 +2,7 @@ from functools import wraps +from dal import autocomplete from config_models.admin import ConfigurationModelAdmin from django import forms @@ -16,7 +17,7 @@ from django.db import models, router, transaction from django.http import HttpResponseRedirect from django.http.request import QueryDict -from django.urls import reverse +from django.urls import reverse, path from django.utils.translation import ngettext from django.utils.translation import gettext_lazy as _ from opaque_keys import InvalidKeyError @@ -45,6 +46,7 @@ UserProfile, UserTestGroup ) +from common.djangoapps.student.constants import LANGUAGE_CHOICES from common.djangoapps.student.roles import REGISTERED_ACCESS_ROLES from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order @@ -309,9 +311,25 @@ def get_queryset(self, request): return super().get_queryset(request).select_related('user') # lint-amnesty, pylint: disable=no-member, super-with-arguments +class LanguageAutocomplete(autocomplete.Select2ListView): + def get_list(self): + return [lang for lang in LANGUAGE_CHOICES if self.q.lower() in lang.lower()] + +class UserProfileInlineForm(forms.ModelForm): + language = forms.CharField( + required=False, + widget=autocomplete.ListSelect2(url='admin:language-autocomplete') + ) + + class Meta: + model = UserProfile + fields = '__all__' + + class UserProfileInline(admin.StackedInline): """ Inline admin interface for UserProfile model. """ model = UserProfile + form = UserProfileInlineForm can_delete = False verbose_name_plural = _('User profile') @@ -359,6 +377,17 @@ def get_readonly_fields(self, request, obj=None): return django_readonly + ('username',) return django_readonly + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path( + 'language-autocomplete/', + LanguageAutocomplete.as_view(), + name='language-autocomplete' + ), + ] + return custom_urls + urls + @admin.register(UserAttribute) class UserAttributeAdmin(admin.ModelAdmin): diff --git a/common/djangoapps/student/constants.py b/common/djangoapps/student/constants.py new file mode 100644 index 000000000000..9ed0da02a82c --- /dev/null +++ b/common/djangoapps/student/constants.py @@ -0,0 +1,3 @@ +import pycountry + +LANGUAGE_CHOICES = sorted({lang.name for lang in pycountry.languages if hasattr(lang, 'alpha_2')}) diff --git a/lms/envs/common.py b/lms/envs/common.py index 719e12fb8b0d..acc280dde200 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3042,6 +3042,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'django.contrib.sessions', 'django.contrib.sites', + 'dal', + 'dal_select2', + # Tweaked version of django.contrib.staticfiles 'openedx.core.djangoapps.staticfiles.apps.EdxPlatformStaticFilesConfig', diff --git a/openedx/core/djangoapps/site_configuration/admin.py b/openedx/core/djangoapps/site_configuration/admin.py index 64140699429e..d6584eb6e729 100644 --- a/openedx/core/djangoapps/site_configuration/admin.py +++ b/openedx/core/djangoapps/site_configuration/admin.py @@ -1,17 +1,81 @@ """ Django admin page for Site Configuration models """ +from dal import autocomplete - +from django import forms +from django.urls import path +from django.utils.translation import gettext_lazy as _ from django.contrib import admin +from .constants import FEATURE_FLAGS from .models import SiteConfiguration, SiteConfigurationHistory +class FeatureFlagAutocomplete(autocomplete.Select2ListView): + def get_list(self): + return list(FEATURE_FLAGS.keys()) + + def get_result_label(self, item): + return item + + def get_result_value(self, item): + return item + +class SiteConfigurationForm(forms.ModelForm): + feature_flags = forms.Field( + required=False, + widget=autocomplete.Select2Multiple( + url='admin:feature-flag-autocomplete', + attrs={ + 'multiple': 'multiple', + 'data-tags': 'true', + 'data-placeholder': 'Select features' + } + ), + label="Enabled Features", + ) + + class Meta: + model = SiteConfiguration + fields = '__all__' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['site_values'].widget = forms.HiddenInput() + current_values = self.instance.site_values or {} + selected_labels = [] + for label, mapping in FEATURE_FLAGS.items(): + if all(current_values.get(k) == v for k, v in mapping.items()): + selected_labels.append(label) + + self.fields['feature_flags'].initial = selected_labels + self.fields['feature_flags'].widget.choices = [(v, v) for v in selected_labels] + + + def clean(self): + cleaned = super().clean() + + selected_flags = self.data.getlist('feature_flags') + if not isinstance(selected_flags, list): + selected_flags = [selected_flags] if selected_flags else [] + + site_values = {} + for label in selected_flags: + site_values.update(FEATURE_FLAGS.get(label, {})) + + cleaned['feature_flags'] = selected_flags + cleaned['site_values'] = site_values + # self.selected_flags = selected_flags + # self.site_values = site_values + + return cleaned class SiteConfigurationAdmin(admin.ModelAdmin): """ Admin interface for the SiteConfiguration object. """ + form = SiteConfigurationForm list_display = ('site', 'enabled', 'site_values') search_fields = ('site__domain', 'site_values') @@ -21,6 +85,17 @@ class Meta: """ model = SiteConfiguration + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path( + 'feature-flag-autocomplete/', + FeatureFlagAutocomplete.as_view(), + name='feature-flag-autocomplete' + ), + ] + return custom_urls + urls + admin.site.register(SiteConfiguration, SiteConfigurationAdmin) diff --git a/openedx/core/djangoapps/site_configuration/constants.py b/openedx/core/djangoapps/site_configuration/constants.py new file mode 100644 index 000000000000..ea743121144a --- /dev/null +++ b/openedx/core/djangoapps/site_configuration/constants.py @@ -0,0 +1,6 @@ +# TODO: Dummy Tags to be replaced by real values +FEATURE_FLAGS = { + 'Forum Notifications': {'enable_forum_notifications': True}, + 'Live Chat': {'enable_live_chat': True}, + 'Dark Mode': {'enable_dark_mode': True}, +}