Remove most references to old teams config scheme (#22238)
This is a follow up from MST-16, which was commited
in 3858036a4e.
Changes:
* Enrich course teams_configuration from a plain Dict
to a custom XBlock field that uses the new TeamsConfig
wrapper class.
* Remove teams_conf property from course, as the previous
change made it redundant.
* Update teams_enabled implementation.
* Remove teams_max_size field from course, which is
no longer semantically correct, as max team size
is now defined on a teamset level.
* Remove teams_topics in order to discourage use of raw
teams config dict.
* Add convenience properties teamsets and teamsets_by_id
to course.
* Allow periods and spaces in teamset IDs to avoid breaking
existing course teams.
Some parts of the code still use the old raw config data
(identifiable by searching "cleaned_data_old_format"),
which we expect to be slowly factored away as we build
new teams features. MST-40 has been created to remove any
remaining references if necessary.
MST-18
* fix: bokchoy test
* fix: remove pdb break
This commit is contained in:
@@ -271,6 +271,33 @@ def get_available_providers():
|
||||
return available_providers
|
||||
|
||||
|
||||
class TeamsConfigField(Dict):
|
||||
"""
|
||||
XBlock field for teams configuration, including definitions for teamsets.
|
||||
|
||||
Serializes to JSON dictionary.
|
||||
"""
|
||||
_default = TeamsConfig({})
|
||||
|
||||
def from_json(self, value):
|
||||
"""
|
||||
Return a TeamsConfig instance from a dict.
|
||||
"""
|
||||
return TeamsConfig(value)
|
||||
|
||||
def to_json(self, value):
|
||||
"""
|
||||
Convert a TeamsConfig instance back to a dict.
|
||||
|
||||
If we have the data that was used to build the TeamsConfig instance,
|
||||
return that instead of `value.cleaned_data`, thus preserving the
|
||||
data in the form that the user entered it.
|
||||
"""
|
||||
if value.source_data is not None:
|
||||
return value.source_data
|
||||
return value.cleaned_data
|
||||
|
||||
|
||||
class CourseFields(object):
|
||||
lti_passports = List(
|
||||
display_name=_("LTI Passports"),
|
||||
@@ -797,7 +824,7 @@ class CourseFields(object):
|
||||
scope=Scope.settings
|
||||
)
|
||||
|
||||
teams_configuration = Dict(
|
||||
teams_configuration = TeamsConfigField(
|
||||
display_name=_("Teams Configuration"),
|
||||
# Translators: please don't translate "id".
|
||||
help=_(
|
||||
@@ -808,6 +835,8 @@ class CourseFields(object):
|
||||
'For example, to specify that teams should have a maximum of 5 participants and provide a list of '
|
||||
'2 topics, enter the configuration in this format: {example_format}. '
|
||||
'In "id" values, the only supported special characters are underscore, hyphen, and period.'
|
||||
# Note that we also support space (" "), which may have been an accident, but it's in
|
||||
# our DB now. Let's not advertise the fact, though.
|
||||
),
|
||||
help_format_args=dict(
|
||||
# Put the sample JSON into a format variable so that translators
|
||||
@@ -1471,65 +1500,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
|
||||
"""
|
||||
return course_metadata_utils.clean_course_key(self.location.course_key, padding_char)
|
||||
|
||||
@property
|
||||
def teams_conf(self):
|
||||
"""
|
||||
Returns a TeamsConfig object wrapping the dict `teams_configuration`.
|
||||
|
||||
TODO: In MST-18, `teams_configuration` will be a custom field that
|
||||
parses its input into a TeamsConfig object.
|
||||
All references to this property will become references to
|
||||
`.teams_configuration`.
|
||||
"""
|
||||
# If the `teams_configuration` dict hasn't changed, return a cached
|
||||
# `TeamsConfig` to avoid re-parsing things.
|
||||
# If it has changed, recompute the `TeamsConfig`.
|
||||
try:
|
||||
cached_teams_conf, cached_teams_conf_source = (
|
||||
self._teams_conf, self._teams_conf_source
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if cached_teams_conf_source == self.teams_configuration:
|
||||
return cached_teams_conf
|
||||
self._teams_conf_source = self.teams_configuration
|
||||
self._teams_conf = TeamsConfig(self._teams_conf_source)
|
||||
return self._teams_conf
|
||||
|
||||
@property
|
||||
def teams_enabled(self):
|
||||
"""
|
||||
Returns whether or not teams has been enabled for this course.
|
||||
Alias to `self.teams_configuration.is_enabled`, for convenience.
|
||||
|
||||
Currently, teams are considered enabled when at least one topic has been configured for the course.
|
||||
|
||||
Deprecated; please use `self.teams_conf.is_enabled` instead.
|
||||
Will be removed (TODO MST-18).
|
||||
Returns bool.
|
||||
"""
|
||||
if self.teams_configuration:
|
||||
return len(self.teams_configuration.get('topics', [])) > 0
|
||||
return False
|
||||
return self.teams_configuration.is_enabled # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def teams_max_size(self):
|
||||
def teamsets(self):
|
||||
"""
|
||||
Returns the max size for teams if teams has been configured, else None.
|
||||
Alias to `self.teams_configuration.teamsets`, for convenience.
|
||||
|
||||
Deprecated; please use `self.teams_conf.calc_max_team_size(...)` instead.
|
||||
Will be removed (TODO MST-18).
|
||||
Returns list[TeamsetConfig].
|
||||
"""
|
||||
return self.teams_configuration.get('max_team_size', None)
|
||||
return self.teams_configuration.teamsets # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def teams_topics(self):
|
||||
def teamsets_by_id(self):
|
||||
"""
|
||||
Returns the topics that have been configured for teams for this course, else None.
|
||||
Alias to `self.teams_configuration.teamsets_by_id`, for convenience.
|
||||
|
||||
Deprecated; please use `self.teams_conf.teamsets` instead.
|
||||
Will be removed (TODO MST-18).
|
||||
Returns dict[str: TeamsetConfig].
|
||||
"""
|
||||
return self.teams_configuration.get('topics', None)
|
||||
return self.teams_configuration.teamsets_by_id
|
||||
|
||||
def set_user_partitions_for_scheme(self, partitions, scheme):
|
||||
"""
|
||||
|
||||
@@ -15,6 +15,7 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import utc
|
||||
from xblock.runtime import DictKeyValueStore, KvsFieldData
|
||||
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
import xmodule.course_module
|
||||
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
|
||||
|
||||
@@ -272,25 +273,21 @@ class DiscussionTopicsTestCase(unittest.TestCase):
|
||||
class TeamsConfigurationTestCase(unittest.TestCase):
|
||||
"""
|
||||
Tests for the configuration of teams and the helper methods for accessing them.
|
||||
|
||||
Also tests new configuration wrapper, `.teams_conf`.
|
||||
Eventually the tests for the old dict-based `.teams_configuration` field
|
||||
wil be removed (TODO MST-18).
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TeamsConfigurationTestCase, self).setUp()
|
||||
self.course = get_dummy_course('2012-12-02T12:00')
|
||||
self.course.teams_configuration = dict()
|
||||
self.course.teams_configuration = TeamsConfig(None)
|
||||
self.count = itertools.count()
|
||||
|
||||
def add_team_configuration(self, max_team_size=3, topics=None):
|
||||
""" Add a team configuration to the course. """
|
||||
teams_configuration = {}
|
||||
teams_configuration["topics"] = [] if topics is None else topics
|
||||
teams_config_data = {}
|
||||
teams_config_data["topics"] = [] if topics is None else topics
|
||||
if max_team_size is not None:
|
||||
teams_configuration["max_team_size"] = max_team_size
|
||||
self.course.teams_configuration = teams_configuration
|
||||
teams_config_data["max_team_size"] = max_team_size
|
||||
self.course.teams_configuration = TeamsConfig(teams_config_data)
|
||||
|
||||
def make_topic(self):
|
||||
""" Make a sample topic dictionary. """
|
||||
@@ -303,66 +300,56 @@ class TeamsConfigurationTestCase(unittest.TestCase):
|
||||
def test_teams_enabled_new_course(self):
|
||||
# Make sure we can detect when no teams exist.
|
||||
self.assertFalse(self.course.teams_enabled)
|
||||
self.assertFalse(self.course.teams_conf.is_enabled)
|
||||
|
||||
# add topics
|
||||
self.add_team_configuration(max_team_size=4, topics=[self.make_topic()])
|
||||
self.assertTrue(self.course.teams_enabled)
|
||||
self.assertTrue(self.course.teams_conf.is_enabled)
|
||||
|
||||
# remove them again
|
||||
self.add_team_configuration(max_team_size=4, topics=[])
|
||||
self.assertFalse(self.course.teams_enabled)
|
||||
self.assertFalse(self.course.teams_conf.is_enabled)
|
||||
|
||||
def test_teams_enabled_max_size_only(self):
|
||||
self.add_team_configuration(max_team_size=4)
|
||||
self.assertFalse(self.course.teams_enabled)
|
||||
self.assertFalse(self.course.teams_conf.is_enabled)
|
||||
|
||||
def test_teams_enabled_no_max_size(self):
|
||||
self.add_team_configuration(max_team_size=None, topics=[self.make_topic()])
|
||||
self.assertTrue(self.course.teams_enabled)
|
||||
self.assertTrue(self.course.teams_conf.is_enabled)
|
||||
|
||||
def test_teams_max_size_no_teams_configuration(self):
|
||||
self.assertIsNone(self.course.teams_max_size)
|
||||
self.assertIsNone(self.course.teams_conf.max_team_size)
|
||||
self.assertIsNone(self.course.teams_configuration.default_max_team_size)
|
||||
|
||||
def test_teams_max_size_with_teams_configured(self):
|
||||
size = 4
|
||||
self.add_team_configuration(max_team_size=size, topics=[self.make_topic(), self.make_topic()])
|
||||
self.assertTrue(self.course.teams_enabled)
|
||||
self.assertEqual(size, self.course.teams_max_size)
|
||||
self.assertEqual(size, self.course.teams_conf.max_team_size)
|
||||
self.assertEqual(size, self.course.teams_configuration.default_max_team_size)
|
||||
|
||||
def test_teams_topics_no_teams(self):
|
||||
self.assertIsNone(self.course.teams_topics)
|
||||
self.assertEqual(self.course.teams_conf.teamsets, [])
|
||||
def test_teamsets_no_config(self):
|
||||
self.assertEqual(self.course.teamsets, [])
|
||||
|
||||
def test_teams_topics_no_topics(self):
|
||||
def test_teamsets_empty(self):
|
||||
self.add_team_configuration(max_team_size=4)
|
||||
self.assertEqual(self.course.teams_topics, [])
|
||||
self.assertEqual(self.course.teams_conf.teamsets, [])
|
||||
self.assertEqual(self.course.teamsets, [])
|
||||
|
||||
def test_teams_topics_with_topics(self):
|
||||
def test_teamsets_present(self):
|
||||
topics = [self.make_topic(), self.make_topic()]
|
||||
self.add_team_configuration(max_team_size=4, topics=topics)
|
||||
self.assertTrue(self.course.teams_enabled)
|
||||
self.assertEqual(self.course.teams_topics, topics)
|
||||
expected_teamsets_data = [
|
||||
teamset.cleaned_data_old_format
|
||||
for teamset in self.course.teams_conf.teamsets
|
||||
for teamset in self.course.teamsets
|
||||
]
|
||||
self.assertEqual(expected_teamsets_data, topics)
|
||||
|
||||
def test_teams_conf_caching(self):
|
||||
def test_teams_conf_cached_by_xblock_field(self):
|
||||
self.add_team_configuration(max_team_size=5, topics=[self.make_topic()])
|
||||
cold_cache_conf = self.course.teams_conf
|
||||
warm_cache_conf = self.course.teams_conf
|
||||
cold_cache_conf = self.course.teams_configuration
|
||||
warm_cache_conf = self.course.teams_configuration
|
||||
self.add_team_configuration(max_team_size=5, topics=[self.make_topic(), self.make_topic()])
|
||||
new_cold_cache_conf = self.course.teams_conf
|
||||
new_warm_cache_conf = self.course.teams_conf
|
||||
new_cold_cache_conf = self.course.teams_configuration
|
||||
new_warm_cache_conf = self.course.teams_configuration
|
||||
self.assertIs(cold_cache_conf, warm_cache_conf)
|
||||
self.assertIs(new_cold_cache_conf, new_warm_cache_conf)
|
||||
self.assertIsNot(cold_cache_conf, new_cold_cache_conf)
|
||||
|
||||
Reference in New Issue
Block a user