Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,8 @@ def plugin_settings(settings):
settings.USE_S3_FOR_CUSTOMER_THEMES = True

_add_theme_static_dirs(settings)

settings.TAHOE_DEFAULT_COURSE_NAME = settings.ENV_TOKENS.get('TAHOE_DEFAULT_COURSE_NAME', '')
settings.TAHOE_DEFAULT_COURSE_GITHUB_ORG = settings.ENV_TOKENS.get('TAHOE_DEFAULT_COURSE_GITHUB_ORG', '')
settings.TAHOE_DEFAULT_COURSE_GITHUB_NAME = settings.ENV_TOKENS.get('TAHOE_DEFAULT_COURSE_GITHUB_NAME', '')
settings.TAHOE_DEFAULT_COURSE_VERSION = settings.ENV_TOKENS.get('TAHOE_DEFAULT_COURSE_VERSION', '')
Comment thread
OmarIthawi marked this conversation as resolved.
Outdated
10 changes: 4 additions & 6 deletions openedx/core/djangoapps/appsembler/sites/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from organizations.models import Organization

from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.core.djangoapps.appsembler.sites.tasks import clone_course
from openedx.core.djangoapps.appsembler.sites.tasks import import_course_on_site_creation
from openedx.core.djangoapps.appsembler.sites.models import AlternativeDomain
from openedx.core.djangoapps.appsembler.sites.utils import sass_to_dict, dict_to_sass, bootstrap_site

Expand Down Expand Up @@ -135,11 +135,9 @@ def create(self, validated_data):
site_configuration.save()

# clone course
if settings.CLONE_COURSE_FOR_NEW_SIGNUPS:
source_course_locator = CourseLocator.from_string(settings.COURSE_TO_CLONE)
destination_course_locator = CourseLocator(organization.name, 'My first course', '2017')
clone_course.apply_async(
(unicode(source_course_locator), unicode(destination_course_locator), user.id),
if settings.FEATURES.get("APPSEMBLER_IMPORT_DEFAULT_COURSE_ON_SITE_CREATION", False):
import_course_on_site_creation.apply_async(
organization,
queue="edx.core.cms.high"
Comment thread
melvinsoft marked this conversation as resolved.
Outdated
)
return {
Expand Down
122 changes: 78 additions & 44 deletions openedx/core/djangoapps/appsembler/sites/tasks.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,103 @@
"""
This file contains celery tasks for contentstore views
"""
import os
import logging
import requests
import uuid
import os
import tarfile
import shutil
import datetime

from backports import tempfile

from celery.task import task
from celery.utils.log import get_task_logger

from django.contrib.auth.models import User
from django.conf import settings

from student.roles import CourseAccessRole

from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey

from student.roles import CourseInstructorRole, CourseStaffRole
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import DuplicateCourseError, ItemNotFoundError
from xmodule.modulestore.xml_importer import import_course_from_xml

LOGGER = get_task_logger(__name__)
FULL_COURSE_REINDEX_THRESHOLD = 1


@task()
def clone_course(source_course_key_string, destination_course_key_string, user_id, fields=None):
def import_course_on_site_creation(organization):
"""
Reruns a course in a new celery task.
"""
# import here, at top level this import prevents the celery workers from starting up correctly
from edxval.api import copy_course_videos
from contentstore.utils import initialize_permissions
from contentstore.courseware_index import SearchIndexingError
from contentstore.views.course import reindex_course_and_check_access

try:
# deserialize the payload
source_course_key = CourseKey.from_string(source_course_key_string)
destination_course_key = CourseKey.from_string(destination_course_key_string)

# use the split modulestore as the store for the rerun course,
# as the Mongo modulestore doesn't support multiple runs of the same course.
store = modulestore()
with store.default_store('split'):
store.clone_course(source_course_key, destination_course_key, user_id, fields=fields)

# Send statistics
from appsembler.models import update_course_statistics
update_course_statistics()

# set initial permissions for the user to access the course.
user = User.objects.get(id=user_id)
initialize_permissions(destination_course_key, user)

# add course intructor and staff roles to the new user
CourseInstructorRole(destination_course_key).add_users(user)
CourseStaffRole(destination_course_key).add_users(user)

# call edxval to attach videos to the rerun
copy_course_videos(source_course_key, destination_course_key)

return "succeeded"

except DuplicateCourseError as exc:
# do NOT delete the original course, only update the status
logging.exception(u'Course Clone Error')
return "duplicate course"

except SearchIndexingError as search_err:
logging.exception(u'Course Clone index Error')
return "index error"
course_name = settings.TAHOE_DEFAULT_COURSE_NAME
course_github_org = settings.TAHOE_DEFAULT_COURSE_GITHUB_ORG
course_github_name = settings.TAHOE_DEFAULT_COURSE_GITHUB_NAME
course_version = settings.TAHOE_DEFAULT_COURSE_VERSION
Comment thread
OmarIthawi marked this conversation as resolved.
Outdated

# build the github download URL from settings to download the course
# in tar.gz format from the github releases
course_download_url = 'https://github.com/{}/{}/archive/{}.tar.gz'.format(
course_github_org,
course_github_name,
course_version
)
Comment thread
melvinsoft marked this conversation as resolved.
Outdated

# download the course in tar.gz format from github in /tmp using a
# random file name
with tempfile.TemporaryDirectory() as dir:
r = requests.get(course_download_url)
course_file_name = str(uuid.uuid4()) + ".tar.gz"
file_path = os.path.join("/", "tmp", course_file_name)
with open(file_path, 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)

# untar the downloaded file in the /tmp/random_dir just created
with tarfile.open(file_path) as tar:
extracted_course_folder = tar.getnames()[0]
tar.extractall(path=dir)

# create the course key object with the current created
# organization, the course name from the settings and the current
# year
course_target_id = CourseKey.from_string(
'course-v1:{}+{}+{}'.format(
organization.short_name,
course_name,
str(datetime.datetime.now().year)
)
)

# import the course
mstore = modulestore()
course_items = import_course_from_xml(
mstore,
ModuleStoreEnum.UserID.mgmt_command,
os.path.join(dir, extracted_course_folder),
source_dirs=os.path.join(dir, extracted_course_folder, 'course'),
target_id=course_target_id,
create_if_not_present=True,
)

# remove the course file
os.remove(file_path)

# add the new registered user as admin in the course
access_role, created = CourseAccessRole.objects.get_or_create(
user=organization.users.first(),
role="instructor",
course_id=course_target_id,
org=organization.short_name
)

# catch all exceptions so we can update the state and properly cleanup the course.
except Exception as exc: # pylint: disable=broad-except
Expand All @@ -72,7 +106,7 @@ def clone_course(source_course_key_string, destination_course_key_string, user_i

try:
# cleanup any remnants of the course
modulestore().delete_course(destination_course_key, user_id)
modulestore().delete_course(course_target_id, organization.users.first())
Comment thread
melvinsoft marked this conversation as resolved.
Outdated
except ItemNotFoundError:
# it's possible there was an error even before the course module was created
pass
Expand Down
1 change: 1 addition & 0 deletions requirements/edx/appsembler.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ psycopg2-binary==2.8.3
django-compat==1.0.14
django-hijack==2.1.4
django-hijack-admin==2.1.4
backports.tempfile==1.0