feat: add setting for config private fields in profile information report (#36688)
This commit is contained in:
committed by
GitHub
parent
845086e358
commit
addeae209f
@@ -22,6 +22,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.test.client import MULTIPART_CONTENT
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse as django_reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
@@ -96,6 +97,7 @@ from openedx.core.djangoapps.oauth_dispatch.adapters import DOTAdapter
|
||||
from openedx.core.djangoapps.oauth_dispatch.tests.factories import AccessTokenFactory, ApplicationFactory
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
|
||||
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
|
||||
from openedx.core.djangoapps.user_api.preferences.api import delete_user_preference
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
from openedx.core.lib.xblock_utils import grade_histogram
|
||||
@@ -2739,6 +2741,63 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
assert student_json['city'] == student.profile.city
|
||||
assert student_json['country'] == ''
|
||||
|
||||
def test_get_students_features_private_fields(self):
|
||||
"""
|
||||
Test that the get_students_features does not return the private fields
|
||||
if they are by default in the `PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS` setting.
|
||||
"""
|
||||
url = reverse("get_students_features", kwargs={"course_id": str(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
res_json = json.loads(response.content.decode("utf-8"))
|
||||
|
||||
assert "students" in res_json
|
||||
for student in res_json["students"]:
|
||||
for field in settings.PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS:
|
||||
assert field not in student
|
||||
|
||||
def test_get_students_features_private_fields_with_custom_config(self):
|
||||
"""
|
||||
Test that the get_students_features does not return the private custom
|
||||
fields set in the `PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS` setting.
|
||||
"""
|
||||
private_fields = ["email", "location", "gender"]
|
||||
|
||||
with override_settings(PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS=private_fields):
|
||||
url = reverse("get_students_features", kwargs={"course_id": str(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
res_json = json.loads(response.content.decode("utf-8"))
|
||||
|
||||
assert "students" in res_json
|
||||
for student in res_json["students"]:
|
||||
for field in private_fields:
|
||||
assert field not in student
|
||||
|
||||
@override_settings(PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS=[])
|
||||
def test_get_students_features_private_fields_empty(self):
|
||||
"""
|
||||
Test that the get_students_features returns all the fields if the
|
||||
`PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS` setting is empty.
|
||||
"""
|
||||
custom_config = {
|
||||
"student_profile_download_fields": [
|
||||
"id",
|
||||
"username",
|
||||
"email",
|
||||
"language",
|
||||
"year_of_birth",
|
||||
]
|
||||
}
|
||||
|
||||
with with_site_configuration_context(configuration=custom_config):
|
||||
url = reverse("get_students_features", kwargs={"course_id": str(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
res_json = json.loads(response.content.decode("utf-8"))
|
||||
|
||||
assert "students" in res_json
|
||||
for student in res_json["students"]:
|
||||
for field in custom_config["student_profile_download_fields"]:
|
||||
assert field in student
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_get_students_features_cohorted(self, is_cohorted):
|
||||
"""
|
||||
@@ -2800,6 +2859,16 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
else:
|
||||
assert student_json['external_user_key'] == ''
|
||||
|
||||
def test_get_students_features_without_permissions(self):
|
||||
""" Test that get_students_features returns 403 without credentials. """
|
||||
|
||||
# removed both roles from courses for instructor
|
||||
CourseDataResearcherRole(self.course.id).remove_users(self.instructor)
|
||||
CourseInstructorRole(self.course.id).remove_users(self.instructor)
|
||||
url = reverse('get_students_features', kwargs={'course_id': str(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_students_who_may_enroll(self):
|
||||
"""
|
||||
Test whether get_students_who_may_enroll returns an appropriate
|
||||
|
||||
@@ -1497,7 +1497,6 @@ class GetStudentsFeatures(DeveloperErrorViewMixin, APIView):
|
||||
'year_of_birth', 'gender', 'level_of_education', 'mailing_address',
|
||||
'goals', 'enrollment_mode', 'last_login', 'date_joined', 'external_user_key'
|
||||
]
|
||||
keep_field_private(query_features, 'year_of_birth') # protected information
|
||||
|
||||
# Provide human-friendly and translatable names for these features. These names
|
||||
# will be displayed in the table generated in data_download.js. It is not (yet)
|
||||
@@ -1509,8 +1508,7 @@ class GetStudentsFeatures(DeveloperErrorViewMixin, APIView):
|
||||
'email': _('Email'),
|
||||
'language': _('Language'),
|
||||
'location': _('Location'),
|
||||
# 'year_of_birth': _('Birth Year'), treated as privileged information as of TNL-10683,
|
||||
# not to go in reports
|
||||
'year_of_birth': _('Birth Year'),
|
||||
'gender': _('Gender'),
|
||||
'level_of_education': _('Level of Education'),
|
||||
'mailing_address': _('Mailing Address'),
|
||||
@@ -1521,6 +1519,10 @@ class GetStudentsFeatures(DeveloperErrorViewMixin, APIView):
|
||||
'external_user_key': _('External User Key'),
|
||||
}
|
||||
|
||||
for field in settings.PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS:
|
||||
keep_field_private(query_features, field)
|
||||
query_features_names.pop(field, None)
|
||||
|
||||
if is_course_cohorted(course.id):
|
||||
# Translators: 'Cohort' refers to a group of students within a course.
|
||||
query_features.append('cohort')
|
||||
|
||||
@@ -4257,6 +4257,14 @@ ACCOUNT_VISIBILITY_CONFIGURATION = {
|
||||
],
|
||||
}
|
||||
|
||||
# .. setting_name: PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS
|
||||
# .. setting_default: ["year_of_birth"]
|
||||
# .. setting_description: List of private fields that will be hidden from the profile information report.
|
||||
# .. setting_use_cases: open_edx
|
||||
# .. setting_creation_date: 2025-07-07
|
||||
# .. setting_tickets: https://github.com/openedx/edx-platform/pull/36688
|
||||
PROFILE_INFORMATION_REPORT_PRIVATE_FIELDS = ["year_of_birth"]
|
||||
|
||||
# The list of all fields that are shared with other users using the bulk 'all_users' privacy setting
|
||||
ACCOUNT_VISIBILITY_CONFIGURATION["bulk_shareable_fields"] = (
|
||||
ACCOUNT_VISIBILITY_CONFIGURATION["public_fields"] + [
|
||||
|
||||
Reference in New Issue
Block a user