Merge branch 'master' of github.com:edx/edx-platform into EDUCATOR-5080
This commit is contained in:
@@ -64,9 +64,10 @@ class BlockCompletionTransformer(BlockStructureTransformer):
|
||||
children = block_structure.get_children(block_key)
|
||||
non_discussion_children = (child_key for child_key in children
|
||||
if block_structure.get_xblock_field(child_key, 'category') != 'discussion')
|
||||
child_complete = (block_structure.get_xblock_field(child_key, self.COMPLETE)
|
||||
for child_key in non_discussion_children)
|
||||
if children and all(child_complete):
|
||||
all_children_complete = all(block_structure.get_xblock_field(child_key, self.COMPLETE)
|
||||
for child_key in non_discussion_children)
|
||||
|
||||
if children and all_children_complete:
|
||||
block_structure.override_xblock_field(block_key, self.COMPLETE, True)
|
||||
|
||||
if any(block_structure.get_xblock_field(child_key, self.RESUME_BLOCK) for child_key in children):
|
||||
|
||||
30
lms/djangoapps/instructor/toggles.py
Normal file
30
lms/djangoapps/instructor/toggles.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Toggles for instructor app
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleFlag
|
||||
|
||||
# Namespace for instructor waffle flags.
|
||||
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='instructor')
|
||||
|
||||
# Waffle flag to use optimised is_small_course.
|
||||
# .. toggle_name: verify_student.optimised_is_small_course
|
||||
# .. toggle_implementation: WaffleFlag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Supports staged rollout to improved is_small_course method.
|
||||
# .. toggle_category: instructor
|
||||
# .. toggle_use_cases: incremental_release, open_edx
|
||||
# .. toggle_creation_date: 2020-07-02
|
||||
# .. toggle_expiration_date: n/a
|
||||
# .. toggle_warnings: n/a
|
||||
# .. toggle_tickets: PROD-1740
|
||||
# .. toggle_status: supported
|
||||
OPTIMISED_IS_SMALL_COURSE = WaffleFlag(
|
||||
waffle_namespace=WAFFLE_FLAG_NAMESPACE,
|
||||
flag_name='optimised_is_small_course',
|
||||
flag_undefined_default=False
|
||||
)
|
||||
|
||||
|
||||
def use_optimised_is_small_course():
|
||||
return OPTIMISED_IS_SMALL_COURSE.is_enabled()
|
||||
@@ -7,6 +7,7 @@ import datetime
|
||||
import logging
|
||||
import uuid
|
||||
from functools import reduce
|
||||
import time
|
||||
|
||||
import pytz
|
||||
import six
|
||||
@@ -32,6 +33,7 @@ from xblock.fields import ScopeIds
|
||||
from bulk_email.api import is_bulk_email_feature_enabled
|
||||
from course_modes.models import CourseMode, CourseModesArchive
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from instructor.toggles import use_optimised_is_small_course
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.models import (
|
||||
CertificateGenerationConfiguration,
|
||||
@@ -132,6 +134,7 @@ def instructor_dashboard_2(request, course_id):
|
||||
reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False)
|
||||
|
||||
sections = []
|
||||
start_time = time.time() # starts time before _section_student_admin (further calls is_small_course)
|
||||
if access['staff']:
|
||||
sections.extend([
|
||||
_section_course_info(course, access),
|
||||
@@ -140,6 +143,8 @@ def instructor_dashboard_2(request, course_id):
|
||||
_section_discussions_management(course, access),
|
||||
_section_student_admin(course, access),
|
||||
])
|
||||
if course_id == 'course-v1:HarvardX+CS50+X':
|
||||
log.info('Investigating log at %s : after _section_student_admin', time.time() - start_time)
|
||||
if access['data_researcher']:
|
||||
sections.append(_section_data_download(course, access))
|
||||
|
||||
@@ -195,7 +200,8 @@ def instructor_dashboard_2(request, course_id):
|
||||
|
||||
if can_see_special_exams:
|
||||
sections.append(_section_special_exams(course, access))
|
||||
|
||||
if course_id == 'course-v1:HarvardX+CS50+X':
|
||||
log.info('Investigating log at %s : section certificate', time.time() - start_time)
|
||||
# Certificates panel
|
||||
# This is used to generate example certificates
|
||||
# and enable self-generated certificates for a course.
|
||||
@@ -214,7 +220,14 @@ def instructor_dashboard_2(request, course_id):
|
||||
if len(openassessment_blocks) > 0 and access['staff']:
|
||||
sections.append(_section_open_response_assessment(request, course, openassessment_blocks, access))
|
||||
|
||||
disable_buttons = not _is_small_course(course_key)
|
||||
if course_id == 'course-v1:HarvardX+CS50+X':
|
||||
log.info('Investigating log at %s : before Disable Button (calling is_small_course)', time.time() - start_time)
|
||||
if use_optimised_is_small_course():
|
||||
disable_buttons = not CourseEnrollment.objects.is_small_course(course_key)
|
||||
else:
|
||||
disable_buttons = not _is_small_course(course_key)
|
||||
if course_id == 'course-v1:HarvardX+CS50+X':
|
||||
log.info('Investigating log at %s : after Disable Button (calling is_small_course)', time.time() - start_time)
|
||||
|
||||
certificate_white_list = CertificateWhitelist.get_certificate_white_list(course_key)
|
||||
generate_certificate_exceptions_url = reverse(
|
||||
@@ -235,6 +248,8 @@ def instructor_dashboard_2(request, course_id):
|
||||
kwargs={'course_id': six.text_type(course_key)}
|
||||
)
|
||||
|
||||
if course_id == 'course-v1:HarvardX+CS50+X':
|
||||
log.info('Investigating log at %s : Before Context', time.time() - start_time)
|
||||
certificate_invalidations = CertificateInvalidation.get_certificate_invalidations(course_key)
|
||||
|
||||
context = {
|
||||
@@ -617,7 +632,10 @@ def _is_small_course(course_key):
|
||||
def _section_student_admin(course, access):
|
||||
""" Provide data for the corresponding dashboard section """
|
||||
course_key = course.id
|
||||
is_small_course = _is_small_course(course_key)
|
||||
if use_optimised_is_small_course():
|
||||
is_small_course = CourseEnrollment.objects.is_small_course(course_key)
|
||||
else:
|
||||
is_small_course = _is_small_course(course_key)
|
||||
|
||||
section_data = {
|
||||
'section_key': 'student_admin',
|
||||
|
||||
@@ -13,7 +13,10 @@ define([
|
||||
});
|
||||
|
||||
mockGetTopic = function(topicId) {
|
||||
return $.Deferred().resolve(TeamSpecHelpers.createMockTopic({id: topicId}));
|
||||
return $.Deferred().resolve(TeamSpecHelpers.createMockTopic({
|
||||
id: topicId,
|
||||
name: 'teamset-name-' + topicId,
|
||||
}));
|
||||
};
|
||||
|
||||
createMyTeamsView = function(myTeams) {
|
||||
@@ -78,5 +81,11 @@ define([
|
||||
);
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
});
|
||||
|
||||
it('sets showTeamsetOnTeamCards on child Teams view', function() {
|
||||
var teams = TeamSpecHelpers.createMockTeams({results: []}),
|
||||
myTeamsView = createMyTeamsView(teams);
|
||||
TeamSpecHelpers.verifyCards(myTeamsView, [], true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,41 +1,54 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'moment',
|
||||
'teams/js/spec_helpers/team_spec_helpers',
|
||||
'teams/js/views/team_card',
|
||||
'teams/js/models/team',
|
||||
'teams/js/models/topic'],
|
||||
function($, _, moment, TeamCardView, Team, Topic) {
|
||||
function($, _, moment, TeamSpecHelpers, TeamCardView, Team, Topic) {
|
||||
'use strict';
|
||||
|
||||
describe('TeamCardView', function() {
|
||||
var createTeamCardView, view;
|
||||
var teamName = 'Test Team',
|
||||
teamID = 'test-team',
|
||||
courseID = TeamSpecHelpers.testCourseID,
|
||||
teamsetID = TeamSpecHelpers.testTopicID,
|
||||
teamsetName = 'Team Set',
|
||||
description = 'A team for testing';
|
||||
|
||||
createTeamCardView = function(topicOptions) {
|
||||
var model = new Team({
|
||||
id: 'test-team',
|
||||
name: 'Test Team',
|
||||
id: teamID,
|
||||
name: teamName,
|
||||
course_id: courseID,
|
||||
topic_id: teamsetID,
|
||||
description: description,
|
||||
is_active: true,
|
||||
course_id: 'test/course/id',
|
||||
topic_id: 'test-topic',
|
||||
description: 'A team for testing',
|
||||
last_activity_at: '2015-08-21T18:53:01.145Z',
|
||||
country: 'us',
|
||||
language: 'en',
|
||||
membership: []
|
||||
}),
|
||||
topic = new Topic(_.extend({id: 'test-topic'}, topicOptions)),
|
||||
topic = new Topic(_.extend({
|
||||
id: teamsetID,
|
||||
name: teamsetName,
|
||||
}, topicOptions)),
|
||||
TeamCardClass = TeamCardView.extend({
|
||||
courseMaxTeamSize: '100',
|
||||
srInfo: {
|
||||
id: 'test-sr-id',
|
||||
text: 'Screenreader text'
|
||||
},
|
||||
showTeamset: true,
|
||||
countries: {us: 'United States of America'},
|
||||
languages: {en: 'English'},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getTopic: function(topicId) { return $.Deferred().resolve(topic); }
|
||||
});
|
||||
return new TeamCardClass({
|
||||
model: model
|
||||
model: model,
|
||||
showTopic: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -47,8 +60,9 @@ define(['jquery',
|
||||
|
||||
it('can render itself', function() {
|
||||
expect(view.$el).toHaveClass('list-card');
|
||||
expect(view.$el.find('.card-title').text()).toContain('Test Team');
|
||||
expect(view.$el.find('.card-description').text()).toContain('A team for testing');
|
||||
expect(view.$el.find('.card-title').text()).toContain(teamName);
|
||||
expect(view.$el.find('.card-description').text()).toContain(description);
|
||||
expect(view.$el.find('.card-type').text()).toContain(teamsetName);
|
||||
expect(view.$el.find('.team-activity abbr').attr('title')).toContain('August 21st 2015');
|
||||
expect(view.$el.find('.team-activity').text()).toContain('Last activity');
|
||||
expect(view.$el.find('.card-meta').text()).toContain('0 / 100 Members');
|
||||
@@ -56,8 +70,14 @@ define(['jquery',
|
||||
expect(view.$el.find('.team-language').text()).toContain('English');
|
||||
});
|
||||
|
||||
it('does not show teamset name is showTeamset is false', function() {
|
||||
view.showTeamset = false;
|
||||
view.render();
|
||||
expect(view.$el.find('.card-type').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('navigates to the associated team page when its action button is clicked', function() {
|
||||
expect(view.$('.action').attr('href')).toEqual('#teams/test-topic/test-team');
|
||||
expect(view.$('.action').attr('href')).toEqual('#teams/' + teamsetID + '/' + teamID);
|
||||
});
|
||||
|
||||
describe('Profile Image Thumbnails', function() {
|
||||
|
||||
@@ -16,13 +16,17 @@ define([
|
||||
var MockTeamsView = TeamsView.extend({
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getTopic: function(topicId) {
|
||||
return $.Deferred().resolve(TeamSpecHelpers.createMockTopic({}));
|
||||
}
|
||||
return $.Deferred().resolve(TeamSpecHelpers.createMockTopic({
|
||||
id: topicId,
|
||||
name: 'teamset-name-' + topicId,
|
||||
}));
|
||||
},
|
||||
});
|
||||
return new MockTeamsView({
|
||||
el: '.teams-container',
|
||||
collection: options.teams || TeamSpecHelpers.createMockTeams(),
|
||||
showActions: true,
|
||||
showTeamset: options.showTeamset,
|
||||
context: TeamSpecHelpers.testContext
|
||||
}).render();
|
||||
};
|
||||
@@ -40,7 +44,18 @@ define([
|
||||
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+2'); // eslint-disable-line no-useless-escape
|
||||
expect(footerEl).not.toHaveClass('hidden');
|
||||
|
||||
TeamSpecHelpers.verifyCards(teamsView, testTeamData);
|
||||
TeamSpecHelpers.verifyCards(teamsView, testTeamData, false);
|
||||
});
|
||||
|
||||
it('forwards the showTeamset option to loaded team cards)', function() {
|
||||
var testTeamData = TeamSpecHelpers.createMockTeamData(1, 5),
|
||||
teamsView = createTeamsView({
|
||||
teams: TeamSpecHelpers.createMockTeams({
|
||||
results: testTeamData
|
||||
}),
|
||||
showTeamset: true,
|
||||
});
|
||||
TeamSpecHelpers.verifyCards(teamsView, testTeamData, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,13 +113,31 @@ define([
|
||||
);
|
||||
};
|
||||
|
||||
var verifyCards = function(view, teams) {
|
||||
|
||||
/**
|
||||
* Verify that the given view shows cards for each of the teams included.
|
||||
* If showTeamset is included (true or false), the test will also verify that the teamset
|
||||
* label/penannt is or is not included in the card accordingly.
|
||||
*
|
||||
* @param {JQ Element} view - jquery DOM element for the card container
|
||||
* @param {TeamModel[]} teams - list of teams to verify.
|
||||
* @param {[bool]} showTeamset - should show teamset string? (if not included, do not check
|
||||
* the teamset string at all)
|
||||
*/
|
||||
var verifyCards = function(view, teams, showTeamset) {
|
||||
var teamCards = view.$('.team-card');
|
||||
_.each(teams, function(team, index) {
|
||||
var currentCard = teamCards.eq(index);
|
||||
var teamsetString = 'teamset-name-' + team.topic_id;
|
||||
expect(currentCard.text()).toMatch(team.name);
|
||||
expect(currentCard.text()).toMatch(_.object(testLanguages)[team.language]);
|
||||
expect(currentCard.text()).toMatch(_.object(testCountries)[team.country]);
|
||||
if (showTeamset === false) {
|
||||
expect(currentCard.text()).not.toMatch(teamsetString);
|
||||
}
|
||||
if (showTeamset === true) {
|
||||
expect(currentCard.text()).toMatch(teamsetString);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
|
||||
initialize: function(options) {
|
||||
this.getTopic = options.getTopic;
|
||||
TeamsView.prototype.initialize.call(this, options);
|
||||
TeamsView.prototype.initialize.call(
|
||||
this,
|
||||
_.extend(
|
||||
{ showTeamset: true },
|
||||
options
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
@@ -138,11 +138,17 @@
|
||||
this.model.on('change:membership', function() {
|
||||
this.detailViews[0].memberships = this.model.get('membership');
|
||||
}, this);
|
||||
|
||||
this.teamsetName = null;
|
||||
this.getTopic(this.model.get('topic_id')).done(_.bind(function(teamset) {
|
||||
this.teamsetName = teamset.get('name');
|
||||
}, this));
|
||||
},
|
||||
|
||||
configuration: 'list_card',
|
||||
cardClass: 'team-card',
|
||||
title: function() { return this.model.get('name'); },
|
||||
pennant: function() { return this.showTeamset ? this.teamsetName : undefined; },
|
||||
description: function() { return this.model.get('description'); },
|
||||
details: function() { return this.detailViews; },
|
||||
actionClass: 'action-view',
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
router: options.router,
|
||||
courseMaxTeamSize: this.context.courseMaxTeamSize,
|
||||
srInfo: this.srInfo,
|
||||
showTeamset: options.showTeamset,
|
||||
countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries),
|
||||
languages: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.languages),
|
||||
getTopic: function(topicId) { return view.getTopic(topicId); }
|
||||
|
||||
@@ -1155,7 +1155,7 @@ def results_callback(request):
|
||||
)
|
||||
|
||||
if use_new_templates_for_id_verification_emails():
|
||||
context = {'user_id': user, 'expiry_date': expiry_date.strftime("%m/%d/%Y")}
|
||||
context = {'user': user, 'expiry_date': expiry_date.strftime("%m/%d/%Y")}
|
||||
send_verification_approved_email(context=context)
|
||||
else:
|
||||
verification_status_email_vars['expiry_date'] = expiry_date.strftime("%m/%d/%Y")
|
||||
|
||||
@@ -362,15 +362,14 @@ CREDENTIALS_SERVICE_USERNAME = 'credentials_worker'
|
||||
COURSE_CATALOG_URL_ROOT = 'http://edx.devstack.discovery:18381'
|
||||
COURSE_CATALOG_API_URL = '{}/api/v1'.format(COURSE_CATALOG_URL_ROOT)
|
||||
|
||||
# Uncomment the lines below if you'd like to see SQL statements in your devstack LMS log.
|
||||
# LOGGING['handlers']['console']['level'] = 'DEBUG'
|
||||
# LOGGING['loggers']['django.db.backends'] = {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}
|
||||
|
||||
# Enable enterprise integration so that we can include enterprise System-wide
|
||||
# role logic without having to manipulate private settings.
|
||||
FEATURES['ENABLE_ENTERPRISE_INTEGRATION'] = True
|
||||
SYSTEM_WIDE_ROLE_CLASSES = os.environ.get("SYSTEM_WIDE_ROLE_CLASSES", SYSTEM_WIDE_ROLE_CLASSES)
|
||||
SYSTEM_WIDE_ROLE_CLASSES.extend(['system_wide_roles.SystemWideRoleAssignment'])
|
||||
|
||||
if FEATURES['ENABLE_ENTERPRISE_INTEGRATION']:
|
||||
SYSTEM_WIDE_ROLE_CLASSES.extend(['enterprise.SystemWideEnterpriseUserRoleAssignment'])
|
||||
SYSTEM_WIDE_ROLE_CLASSES.extend([
|
||||
'system_wide_roles.SystemWideRoleAssignment',
|
||||
'enterprise.SystemWideEnterpriseUserRoleAssignment',
|
||||
])
|
||||
|
||||
# List of enterprise customer uuids to exclude from transition to use of enterprise-catalog
|
||||
ENTERPRISE_CUSTOMERS_EXCLUDED_FROM_CATALOG = ()
|
||||
@@ -398,3 +397,7 @@ if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')):
|
||||
# ]
|
||||
# TEMPLATES[1]["DIRS"] = _make_mako_template_dirs
|
||||
# derive_settings(__name__)
|
||||
|
||||
# Uncomment the lines below if you'd like to see SQL statements in your devstack LMS log.
|
||||
# LOGGING['handlers']['console']['level'] = 'DEBUG'
|
||||
# LOGGING['loggers']['django.db.backends'] = {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}
|
||||
|
||||
Reference in New Issue
Block a user