diff --git a/lms/djangoapps/courseware/admin.py b/lms/djangoapps/courseware/admin.py index c13d4f0ffb..57fa242a69 100644 --- a/lms/djangoapps/courseware/admin.py +++ b/lms/djangoapps/courseware/admin.py @@ -1,6 +1,10 @@ -from django.contrib import admin +""" +Admin view for courseware. +""" +from __future__ import absolute_import from config_models.admin import ConfigurationModelAdmin, KeyedConfigurationModelAdmin +from django.contrib import admin from courseware import models diff --git a/lms/djangoapps/courseware/context_processor.py b/lms/djangoapps/courseware/context_processor.py index 5b9c7c460c..cf1a11bd8f 100644 --- a/lms/djangoapps/courseware/context_processor.py +++ b/lms/djangoapps/courseware/context_processor.py @@ -5,6 +5,10 @@ This is meant to simplify the process of sending user preferences (espec. time_z to the templates without having to append every view file. """ +from __future__ import absolute_import + +import six + from openedx.core.djangoapps.user_api.errors import UserAPIInternalError, UserNotFound from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences from openedx.core.lib.cache_utils import get_cache @@ -39,7 +43,7 @@ def user_timezone_locale_prefs(request): else: user_prefs = { key: user_preferences.get(pref_name, None) - for key, pref_name in RETRIEVABLE_PREFERENCES.iteritems() + for key, pref_name in six.iteritems(RETRIEVABLE_PREFERENCES) } cached_value.update(user_prefs) diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py index 8493cbd7d3..9d7d7fd290 100644 --- a/lms/djangoapps/courseware/course_tools.py +++ b/lms/djangoapps/courseware/course_tools.py @@ -2,15 +2,18 @@ Platform plugins to support a verified upgrade tool. """ +from __future__ import absolute_import + import datetime + import pytz from crum import get_current_request from django.utils.translation import ugettext as _ from course_modes.models import CourseMode +from courseware.date_summary import verified_upgrade_deadline_link from openedx.features.course_experience.course_tools import CourseTool from student.models import CourseEnrollment -from courseware.date_summary import verified_upgrade_deadline_link class VerifiedUpgradeTool(CourseTool): diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 4c154d454e..1538e80b94 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -2,45 +2,49 @@ Functions for accessing and displaying courses within the courseware. """ +from __future__ import absolute_import + import logging from collections import defaultdict from datetime import datetime -import branding import pytz +import six from crum import get_current_request -from openedx.features.course_duration_limits.access import AuditExpiredError +from django.conf import settings +from django.db.models import Prefetch +from django.http import Http404, QueryDict +from django.urls import reverse +from fs.errors import ResourceNotFound +from opaque_keys.edx.keys import UsageKey +from path import Path as path +from six import text_type + +import branding +from course_modes.models import CourseMode from courseware.access import has_access -from courseware.access_response import StartDateError, MilestoneAccessError +from courseware.access_response import MilestoneAccessError, StartDateError from courseware.date_summary import ( + CertificateAvailableDate, CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, - VerifiedUpgradeDeadlineDate, - CertificateAvailableDate + VerifiedUpgradeDeadlineDate ) from courseware.masquerade import check_content_start_date_for_masquerade_user from courseware.model_data import FieldDataCache from courseware.module_render import get_module -from course_modes.models import CourseMode -from django.conf import settings -from django.db.models import Prefetch -from django.urls import reverse -from django.http import Http404, QueryDict from edxmako.shortcuts import render_to_string -from fs.errors import ResourceNotFound from lms.djangoapps.certificates import api as certs_api from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException from lms.djangoapps.courseware.exceptions import CourseAccessRedirect -from opaque_keys.edx.keys import UsageKey from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.enrollments.api import get_course_enrollment_details +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.api.view_utils import LazySequence +from openedx.features.course_duration_limits.access import AuditExpiredError from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG -from path import Path as path -from six import text_type from static_replace import replace_static_urls from student.models import CourseEnrollment from survey.utils import is_survey_required_and_unanswered @@ -81,7 +85,7 @@ def get_course_by_id(course_key, depth=0): if course: return course else: - raise Http404(u"Course not found: {}.".format(unicode(course_key))) + raise Http404(u"Course not found: {}.".format(six.text_type(course_key))) def get_course_with_access(user, action, course_key, depth=0, check_if_enrolled=False, check_survey_complete=True): @@ -175,12 +179,12 @@ def check_course_access(course, user, action, check_if_enrolled=False, check_sur if check_if_enrolled: # If the user is not enrolled, redirect them to the about page if not CourseEnrollment.is_enrolled(user, course.id): - raise CourseAccessRedirect(reverse('about_course', args=[unicode(course.id)])) + raise CourseAccessRedirect(reverse('about_course', args=[six.text_type(course.id)])) # Redirect if the user must answer a survey before entering the course. if check_survey_complete and action == 'load': if is_survey_required_and_unanswered(user, course): - raise CourseAccessRedirect(reverse('course_survey', args=[unicode(course.id)])) + raise CourseAccessRedirect(reverse('course_survey', args=[six.text_type(course.id)])) def can_self_enroll_in_course(course_key): @@ -205,7 +209,7 @@ def course_open_for_self_enrollment(course_key): return False # Check the enrollment start and end dates. - course_details = get_course_enrollment_details(unicode(course_key)) + course_details = get_course_enrollment_details(six.text_type(course_key)) now = datetime.now().replace(tzinfo=pytz.UTC) start = course_details['enrollment_start'] end = course_details['enrollment_end'] @@ -379,7 +383,7 @@ def get_course_info_section(request, user, course, section_key): html = render_to_string('courseware/error-message.html', None) log.exception( u"Error rendering course_id=%s, section_key=%s", - unicode(course.id), section_key + six.text_type(course.id), section_key ) return html @@ -524,7 +528,7 @@ def get_cms_course_link(course, page='course'): """ # This is fragile, but unfortunately the problem is that within the LMS we # can't use the reverse calls from the CMS - return u"//{}/{}/{}".format(settings.CMS_BASE, page, unicode(course.id)) + return u"//{}/{}/{}".format(settings.CMS_BASE, page, six.text_type(course.id)) def get_cms_block_link(block, page): @@ -571,7 +575,7 @@ def get_problems_in_section(section): for vertical in subsection.get_children(): for component in vertical.get_children(): if component.location.block_type == 'problem' and getattr(component, 'has_score', False): - problem_descriptors[unicode(component.location)] = component + problem_descriptors[six.text_type(component.location)] = component return problem_descriptors @@ -643,7 +647,7 @@ def get_course_chapter_ids(course_key): except Exception: # pylint: disable=broad-except log.exception('Failed to retrieve course from modulestore.') return [] - return [unicode(chapter_key) for chapter_key in chapter_keys if chapter_key.block_type == 'chapter'] + return [six.text_type(chapter_key) for chapter_key in chapter_keys if chapter_key.block_type == 'chapter'] def allow_public_access(course, visibilities): diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index c4155e5c41..3c4077b946 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -3,17 +3,19 @@ This module provides date summary blocks for the Course Info page. Each block gives information about a particular course-run-specific date which will be displayed to the user. """ -import crum +from __future__ import absolute_import + import datetime +import crum from babel.dates import format_timedelta - from django.conf import settings from django.urls import reverse from django.utils.formats import date_format from django.utils.functional import cached_property -from django.utils.translation import get_language, to_locale, ugettext_lazy +from django.utils.translation import get_language, to_locale from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy from lazy import lazy from pytz import utc @@ -23,7 +25,7 @@ from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field from openedx.core.djangolib.markup import HTML, Text -from openedx.features.course_experience import CourseHomeMessages, UPGRADE_DEADLINE_MESSAGE +from openedx.features.course_experience import UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages from student.models import CourseEnrollment from .context_processor import user_timezone_locale_prefs diff --git a/lms/djangoapps/courseware/management/commands/import.py b/lms/djangoapps/courseware/management/commands/import.py deleted file mode 120000 index 36b7e3c6fc..0000000000 --- a/lms/djangoapps/courseware/management/commands/import.py +++ /dev/null @@ -1 +0,0 @@ -../../../../../cms/djangoapps/contentstore/management/commands/import.py \ No newline at end of file diff --git a/lms/djangoapps/courseware/management/commands/import.py b/lms/djangoapps/courseware/management/commands/import.py new file mode 100644 index 0000000000..62de3d68c7 --- /dev/null +++ b/lms/djangoapps/courseware/management/commands/import.py @@ -0,0 +1,78 @@ +""" +Script for importing courseware from XML format +""" +from __future__ import absolute_import + +from django.core.management.base import BaseCommand + +from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded, seed_permissions_roles +from xmodule.contentstore.django import contentstore +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.xml_importer import import_course_from_xml +from xmodule.util.sandboxing import DEFAULT_PYTHON_LIB_FILENAME + + +class Command(BaseCommand): + """ + Import the specified data directory into the default ModuleStore + """ + help = 'Import the specified data directory into the default ModuleStore.' + + def add_arguments(self, parser): + parser.add_argument('data_directory') + parser.add_argument('course_dirs', + nargs='*', + metavar='course_dir') + parser.add_argument('--nostatic', + action='store_true', + help='Skip import of static content') + parser.add_argument('--nopythonlib', + action='store_true', + help=( + 'Skip import of course python library if it exists ' + '(NOTE: If the static content import is not skipped, the python library ' + 'will be imported and this flag will be ignored)' + )) + parser.add_argument('--python-lib-filename', + default=DEFAULT_PYTHON_LIB_FILENAME, + help='Filename of the course code library (if it exists)') + + def handle(self, *args, **options): + data_dir = options['data_directory'] + source_dirs = options['course_dirs'] + if not source_dirs: + source_dirs = None + do_import_static = not options.get('nostatic', False) + # If the static content is not skipped, the python lib should be imported regardless + # of the 'nopythonlib' flag. + do_import_python_lib = do_import_static or not options.get('nopythonlib', False) + python_lib_filename = options.get('python_lib_filename') + + output = ( + u"Importing...\n" + u" data_dir={data}, source_dirs={courses}\n" + u" Importing static content? {import_static}\n" + u" Importing python lib? {import_python_lib}" + ).format( + data=data_dir, + courses=source_dirs, + import_static=do_import_static, + import_python_lib=do_import_python_lib + ) + self.stdout.write(output) + mstore = modulestore() + + course_items = import_course_from_xml( + mstore, ModuleStoreEnum.UserID.mgmt_command, data_dir, source_dirs, load_error_modules=False, + static_content_store=contentstore(), verbose=True, + do_import_static=do_import_static, do_import_python_lib=do_import_python_lib, + create_if_not_present=True, + python_lib_filename=python_lib_filename, + ) + + for course in course_items: + course_id = course.id + if not are_permissions_roles_seeded(course_id): + self.stdout.write(u'Seeding forum roles for course {0}\n'.format(course_id)) + seed_permissions_roles(course_id) diff --git a/lms/djangoapps/courseware/middleware.py b/lms/djangoapps/courseware/middleware.py index 81d053327e..4742d311bb 100644 --- a/lms/djangoapps/courseware/middleware.py +++ b/lms/djangoapps/courseware/middleware.py @@ -2,6 +2,8 @@ Middleware for the courseware app """ +from __future__ import absolute_import + from django.shortcuts import redirect from lms.djangoapps.courseware.exceptions import Redirect diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index 30cf517e33..1b7edb43a6 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -12,9 +12,12 @@ file and check it in at the same time as your model changes. To do that, ASSUMPTIONS: modules have unique IDs, even across different module_types """ +from __future__ import absolute_import + import itertools import logging +import six from config_models.models import ConfigurationModel from django.conf import settings from django.contrib.auth.models import User @@ -22,11 +25,11 @@ from django.db import models from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _ from model_utils.models import TimeStampedModel +from opaque_keys.edx.django.models import BlockTypeKeyField, CourseKeyField, UsageKeyField from six import text_type +from six.moves import range import coursewarehistoryextended -from opaque_keys.edx.django.models import BlockTypeKeyField, CourseKeyField, UsageKeyField - from openedx.core.djangolib.markup import HTML log = logging.getLogger("edx.courseware") @@ -37,7 +40,7 @@ def chunks(items, chunk_size): Yields the values from items in chunks of size chunk_size """ items = list(items) - return (items[i:i + chunk_size] for i in xrange(0, len(items), chunk_size)) + return (items[i:i + chunk_size] for i in range(0, len(items), chunk_size)) class ChunkingManager(models.Manager): @@ -67,7 +70,7 @@ class ChunkingManager(models.Manager): """ chunk_size = kwargs.pop('chunk_size', 500) res = itertools.chain.from_iterable( - self.filter(**dict([(chunk_field, chunk)] + kwargs.items())) + self.filter(**dict([(chunk_field, chunk)] + list(kwargs.items()))) for chunk in chunks(items, chunk_size) ) return res @@ -151,7 +154,7 @@ class StudentModule(models.Model): },) def __unicode__(self): - return unicode(repr(self)) + return six.text_type(repr(self)) @classmethod def get_state_by_params(cls, course_id, module_state_keys, student_id=None): @@ -245,7 +248,7 @@ class StudentModuleHistory(BaseStudentModuleHistory): student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.CASCADE) def __unicode__(self): - return unicode(repr(self)) + return six.text_type(repr(self)) def save_history(sender, instance, **kwargs): # pylint: disable=no-self-argument, unused-argument """