diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 39fd943eff..98e9bf8b2d 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -13,6 +13,7 @@ file and check it in at the same time as your model changes. To do that, import hashlib import json import logging +import six import uuid from collections import OrderedDict, defaultdict, namedtuple from datetime import datetime, timedelta @@ -1529,7 +1530,7 @@ class CourseEnrollment(models.Model): if not status_hash: enrollments = cls.enrollments_for_user(user).values_list('course_id', 'mode') - enrollments = [(e[0].lower(), e[1].lower()) for e in enrollments] + enrollments = [(six.text_type(e[0]).lower(), e[1].lower()) for e in enrollments] enrollments = sorted(enrollments, key=lambda e: e[0]) hash_elements = [user.username] hash_elements += ['{course_id}={mode}'.format(course_id=e[0], mode=e[1]) for e in enrollments] diff --git a/common/djangoapps/util/models.py b/common/djangoapps/util/models.py index 65a10e2a6d..2e8e1a1409 100644 --- a/common/djangoapps/util/models.py +++ b/common/djangoapps/util/models.py @@ -7,6 +7,8 @@ from config_models.models import ConfigurationModel from django.db import models from django.utils.text import compress_string +from openedx.core.djangoapps.util.model_utils import CreatorMixin + logger = logging.getLogger(__name__) # pylint: disable=invalid-name @@ -40,12 +42,10 @@ def decompress_string(value): return ret -class CompressedTextField(models.TextField): +class CompressedTextField(CreatorMixin, models.TextField): """ TextField that transparently compresses data when saving to the database, and decompresses the data when retrieving it from the database. """ - __metaclass__ = models.SubfieldBase - def get_prep_value(self, value): """ Compress the text data. """ if value is not None: diff --git a/lms/djangoapps/class_dashboard/dashboard_data.py b/lms/djangoapps/class_dashboard/dashboard_data.py index 77944a2206..cd2a1b29fb 100644 --- a/lms/djangoapps/class_dashboard/dashboard_data.py +++ b/lms/djangoapps/class_dashboard/dashboard_data.py @@ -45,7 +45,7 @@ def get_problem_grade_distribution(course_id): # Loop through resultset building data for each problem for row in db_query: - curr_problem = UsageKey.from_string(row['module_state_key']).map_into_course(course_id) + curr_problem = row['module_state_key'].map_into_course(course_id) # Build set of grade distributions for each problem that has student responses if curr_problem in prob_grade_distrib: @@ -85,7 +85,7 @@ def get_sequential_open_distrib(course_id): # Build set of "opened" data for each subsection that has "opened" data sequential_open_distrib = {} for row in db_query: - row_loc = UsageKey.from_string(row['module_state_key']).map_into_course(course_id) + row_loc = row['module_state_key'].map_into_course(course_id) sequential_open_distrib[row_loc] = row['count_sequential'] return sequential_open_distrib @@ -122,7 +122,7 @@ def get_problem_set_grade_distrib(course_id, problem_set): # Loop through resultset building data for each problem for row in db_query: - row_loc = UsageKey.from_string(row['module_state_key']).map_into_course(course_id) + row_loc = row['module_state_key'].map_into_course(course_id) if row_loc not in prob_grade_distrib: prob_grade_distrib[row_loc] = { 'max_grade': 0, diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index ae780a519a..3ebd483941 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -960,7 +960,7 @@ class ScoresClient(object): # attached to them (since old mongo identifiers don't include runs). # So we have to add that info back in before we put it into our lookup. self._locations_to_scores.update({ - UsageKey.from_string(location).map_into_course(self.course_key): self.Score(correct, total, created) + location.map_into_course(self.course_key): self.Score(correct, total, created) for location, correct, total, created in scores_qset.values_list('module_state_key', 'grade', 'max_grade', 'created') }) diff --git a/lms/djangoapps/instructor_analytics/basic.py b/lms/djangoapps/instructor_analytics/basic.py index fb54cc9416..af444321a9 100644 --- a/lms/djangoapps/instructor_analytics/basic.py +++ b/lms/djangoapps/instructor_analytics/basic.py @@ -199,6 +199,7 @@ def issued_certificates(course_key, features): # Report run date for data in generated_certificates: data['report_run_date'] = report_run_date + data['course_id'] = str(data['course_id']) return generated_certificates diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index 619bf405f6..a098ae985d 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -1055,7 +1055,7 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView): CourseAccessRole.objects.filter(user=request.user, role='staff').values_list('course_id', flat=True) ) accessible_course_ids = [item for sublist in (enrolled_courses, staff_courses) for item in sublist] - if requested_course_id is not None and requested_course_id not in accessible_course_ids: + if requested_course_id is not None and requested_course_key not in accessible_course_ids: return Response(status=status.HTTP_400_BAD_REQUEST) if not specified_username_or_team: @@ -1068,7 +1068,7 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView): if requested_course_key is not None: course_keys = [requested_course_key] elif accessible_course_ids is not None: - course_keys = [CourseKey.from_string(course_string) for course_string in accessible_course_ids] + course_keys = accessible_course_ids queryset = CourseTeamMembership.get_memberships(username, course_keys, team_id) page = self.paginate_queryset(queryset) diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index 0284dea763..171c44cfac 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -551,10 +551,7 @@ class CourseOverview(TimeStampedModel): """ Returns all course keys from course overviews. """ - return [ - CourseKey.from_string(course_overview['id']) - for course_overview in CourseOverview.objects.values('id') - ] + return CourseOverview.objects.values_list('id', flat=True) def is_discussion_tab_enabled(self): """ diff --git a/openedx/core/djangoapps/util/model_utils.py b/openedx/core/djangoapps/util/model_utils.py new file mode 100644 index 0000000000..ec1714f60a --- /dev/null +++ b/openedx/core/djangoapps/util/model_utils.py @@ -0,0 +1,27 @@ +class Creator(object): + """ + A placeholder class that provides a way to set the attribute on the model. + """ + def __init__(self, field): + self.field = field + + def __get__(self, obj, type=None): + if obj is None: + return self + return obj.__dict__[self.field.name] + + def __set__(self, obj, value): + obj.__dict__[self.field.name] = self.field.to_python(value) + + +class CreatorMixin(object): + """ + Mixin class to provide SubfieldBase functionality to django fields. + See: https://docs.djangoproject.com/en/1.11/releases/1.8/#subfieldbase + """ + def contribute_to_class(self, cls, name, *args, **kwargs): + super(CreatorMixin, self).contribute_to_class(cls, name, *args, **kwargs) + setattr(cls, name, Creator(self)) + + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) diff --git a/openedx/core/djangoapps/xmodule_django/models.py b/openedx/core/djangoapps/xmodule_django/models.py index af4cc61dcd..548c57321f 100644 --- a/openedx/core/djangoapps/xmodule_django/models.py +++ b/openedx/core/djangoapps/xmodule_django/models.py @@ -7,7 +7,7 @@ import warnings from django.core.exceptions import ValidationError from django.db import models from opaque_keys.edx.keys import BlockTypeKey, CourseKey, UsageKey - +from openedx.core.djangoapps.util.model_utils import CreatorMixin from xmodule.modulestore.django import modulestore log = logging.getLogger(__name__) @@ -68,7 +68,7 @@ def _strip_value(value, lookup='exact'): return stripped_value -class OpaqueKeyField(models.CharField): +class OpaqueKeyField(CreatorMixin, models.CharField): """ A django field for storing OpaqueKeys. @@ -81,8 +81,6 @@ class OpaqueKeyField(models.CharField): """ description = "An OpaqueKey object, saved to the DB in the form of a string." - __metaclass__ = models.SubfieldBase - Empty = object() KEY_CLASS = None