Merge remote-tracking branch 'upstream/master' into proversity/add-recover-password-endpoint [ci skip]

This commit is contained in:
Jose Antonio Gonzalez
2017-11-21 09:15:09 +02:00
22 changed files with 180 additions and 72 deletions

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import re
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('course_modes', '0008_course_key_field_to_foreign_key'),
]
operations = [
migrations.AlterField(
model_name='coursemode',
name='suggested_prices',
field=models.CharField(default=b'', max_length=255, blank=True, validators=[django.core.validators.RegexValidator(re.compile('^[\\d,]+\\Z'), 'Enter only digits separated by commas.', 'invalid')]),
),
]

View File

@@ -8,11 +8,11 @@ import pytz
from config_models.models import ConfigurationModel
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_comma_separated_integer_list
from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from opaque_keys.edx.keys import CourseKey
@@ -103,7 +103,8 @@ class CourseMode(models.Model):
# DEPRECATED: the suggested prices for this mode
# We used to allow users to choose from a set of prices, but we now allow only
# a single price. This field has been deprecated by `min_price`
suggested_prices = models.CommaSeparatedIntegerField(max_length=255, blank=True, default='')
suggested_prices = models.CharField(max_length=255, blank=True, default='',
validators=[validate_comma_separated_integer_list])
# optional description override
# WARNING: will not be localized

View File

@@ -48,21 +48,21 @@
<div class="js-post-body editor" aria-describedby="new-post-editor-description" name="body"></div>
</div>
<div class="post-options">
<label class="field-label label-inline">
<input type="checkbox" name="follow" class="field-input input-checkbox" checked>
<label for= "follow" class="field-label label-inline">
<input id="follow" type="checkbox" name="follow" class="field-input input-checkbox" checked>
<span class="field-input-label">
<span class="icon fa fa-star" aria-hidden="true"></span><%- gettext("follow this post") %>
</span>
</label>
<% if (allow_anonymous) { %>
<label class="field-label label-inline">
<input type="checkbox" name="anonymous" class="field-input input-checkbox">
<label for="anonymous" class="field-label label-inline">
<input id="anonymous" type="checkbox" name="anonymous" class="field-input input-checkbox">
<span class="field-input-label"><%- gettext("post anonymously") %></span>
</label>
<% } %>
<% if (allow_anonymous_to_peers) { %>
<label class="field-label label-inline">
<input type="checkbox" name="anonymous_to_peers" class="field-input input-checkbox">
<label for="anonymous_to_peers" class="field-label label-inline">
<input id="anonymous_to_peers" type="checkbox" name="anonymous_to_peers" class="field-input input-checkbox">
<span class="field-input-label"><%- gettext("post anonymously to classmates") %></span>
</div>
<% } %>

View File

@@ -2,7 +2,7 @@
# This will copy each source language to a new directory at the end of the i18n generate step
# which allows us to migrate to a new locale code without re-creating the Transifex project.
edx-lang-map:
edx_lang_map:
zh_CN: zh_HANS
locales:

View File

@@ -3,7 +3,6 @@ Defines asynchronous celery task for sending email notification (through edx-ace
pertaining to new discussion forum comments.
"""
import logging
from urllib import urlencode
from urlparse import urljoin
from celery import task
@@ -41,8 +40,9 @@ class ResponseNotification(MessageType):
@task(base=LoggedTask, routing_key=ROUTING_KEY)
def send_ace_message(context):
context['course_id'] = CourseKey.from_string(context['course_id'])
context['site'] = Site.objects.get(id=context['site_id'])
if _should_send_message(context):
context['site'] = Site.objects.get(id=context['site_id'])
thread_author = User.objects.get(id=context['thread_author_id'])
middleware_classes = [
CurrentRequestUserMiddleware,

View File

@@ -133,7 +133,7 @@ if Markdown?
.append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
$wmdPanel = $("<div>").addClass("wmd-panel")
.append($("<div>").attr("id", "wmd-button-bar#{_append}"))
.append($("<label>").addClass("sr").attr("for", wmdInputId).text(gettext("Post body")))
.append($("<label>").addClass("sr").attr("for", wmdInputId).text(gettext("Your question or idea (required)")))
.append($("<textarea>").addClass("wmd-input").attr("id", wmdInputId).html(initialText))
.append($wmdPreviewContainer)
$elem.append($wmdPanel)

View File

@@ -142,8 +142,13 @@ define(['backbone',
requests,
'POST',
'/i18n/setlang/',
'language=' + data[fieldData.valueAttribute]
$.param({
language: data[fieldData.valueAttribute],
next: window.location.href
})
);
// Django will actually respond with a 302 redirect, but that would cause a page load during these
// unittests. 204 should work fine for testing.
AjaxHelpers.respondWithNoContent(requests);
FieldViewsSpecHelpers.expectMessageContains(view, 'Your changes have been saved.');
@@ -157,7 +162,10 @@ define(['backbone',
requests,
'POST',
'/i18n/setlang/',
'language=' + data[fieldData.valueAttribute]
$.param({
language: data[fieldData.valueAttribute],
next: window.location.href
})
);
AjaxHelpers.respondWithError(requests, 500);
FieldViewsSpecHelpers.expectMessageContains(

View File

@@ -52,7 +52,8 @@
fieldTemplate: field_dropdown_account_template,
saveSucceeded: function() {
var data = {
language: this.modelValue()
language: this.modelValue(),
next: window.location.href
};
var view = this;

View File

@@ -77,7 +77,7 @@
}
svg {
max-width: 100%;
max-width: 100% !important;
}
}

View File

@@ -39,14 +39,15 @@ ORG_DEADLINE_QUERY = 1 # courseware_orgdynamicupgradedeadlineconfiguration
COURSE_DEADLINE_QUERY = 1 # courseware_coursedynamicupgradedeadlineconfiguration
COMMERCE_CONFIG_QUERY = 1 # commerce_commerceconfiguration
USER_QUERY = 1
USER_QUERY = 1 # auth_user
THEME_PREVIEW_QUERY = 1
THEME_QUERY = 1
SCHEDULE_CONFIG_QUERY = 1
THEME_QUERY = 1 # theming_sitetheme
SCHEDULE_CONFIG_QUERY = 1 # schedules_scheduleconfig
NUM_QUERIES_SITE_SCHEDULES = (
SITE_QUERY +
SITE_CONFIG_QUERY +
THEME_QUERY +
SCHEDULES_QUERY
)

View File

@@ -10,7 +10,6 @@ from django.core.urlresolvers import reverse
from django.db.models import F, Q
from django.utils.formats import dateformat, get_format
from edx_ace.recipient_resolver import RecipientResolver
from edx_ace.recipient import Recipient
@@ -22,7 +21,7 @@ from openedx.core.djangoapps.schedules.models import Schedule, ScheduleExperienc
from openedx.core.djangoapps.schedules.utils import PrefixedDebugLoggerMixin
from openedx.core.djangoapps.schedules.template_context import get_base_template_context
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.features.course_experience import course_home_url_name
LOG = logging.getLogger(__name__)
@@ -244,7 +243,7 @@ class RecurringNudgeResolver(BinnedSchedulesBaseResolver):
first_schedule = user_schedules[0]
context = {
'course_name': first_schedule.enrollment.course.display_name,
'course_url': reverse('course_root', args=[str(first_schedule.enrollment.course_id)]),
'course_url': _get_trackable_course_home_url(first_schedule.enrollment.course_id),
}
# Information for including upsell messaging in template.
@@ -284,7 +283,7 @@ class UpgradeReminderResolver(BinnedSchedulesBaseResolver):
course_id_str = str(schedule.enrollment.course_id)
course_id_strs.append(course_id_str)
course_links.append({
'url': reverse('course_root', args=[course_id_str]),
'url': _get_trackable_course_home_url(schedule.enrollment.course_id),
'name': schedule.enrollment.course.display_name
})
@@ -357,16 +356,33 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
except CourseUpdateDoesNotExist:
continue
course_id_str = str(enrollment.course_id)
template_context.update({
'course_name': schedule.enrollment.course.display_name,
'course_url': reverse('course_root', args=[course_id_str]),
'course_url': _get_trackable_course_home_url(enrollment.course_id),
'week_num': week_num,
'week_highlights': week_highlights,
# This is used by the bulk email optout policy
'course_ids': [course_id_str],
'course_ids': [str(enrollment.course_id)],
})
template_context.update(_get_upsell_information_for_schedule(user, schedule))
yield (user, schedule.enrollment.course.language, template_context)
def _get_trackable_course_home_url(course_id):
"""
Get the home page URL for the course.
NOTE: For us to be able to track clicks in the email, this URL needs to point to a landing page that does not result
in a redirect so that the GA snippet can register the UTM parameters.
Args:
course_id (CourseKey): The course to get the home page URL for.
Returns:
A relative path to the course home page.
"""
course_url_name = course_home_url_name(course_id)
return reverse(course_url_name, args=[str(course_id)])

View File

@@ -103,15 +103,20 @@ class ScheduleMessageBaseTask(Task):
):
msg_type = self.make_message_type(day_offset)
site = Site.objects.select_related('configuration').get(id=site_id)
_annotate_for_monitoring(msg_type, site, bin_num, target_day_str, day_offset)
return self.resolver(
self.async_send_task,
site,
deserialize(target_day_str),
day_offset,
bin_num,
override_recipient_email=override_recipient_email,
).send(msg_type)
middleware_classes = [
CurrentRequestUserMiddleware,
CurrentSiteThemeMiddleware,
]
with emulate_http_request(site=site, middleware_classes=middleware_classes):
_annotate_for_monitoring(msg_type, site, bin_num, target_day_str, day_offset)
return self.resolver(
self.async_send_task,
site,
deserialize(target_day_str),
day_offset,
bin_num,
override_recipient_email=override_recipient_email,
).send(msg_type)
def make_message_type(self, day_offset):
raise NotImplementedError

View File

@@ -24,12 +24,12 @@
<p>
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Many edX learners are completing more problems every week, and
Many {{ platform_name }} learners are completing more problems every week, and
participating in the discussion forums. What do you want to do to keep learning?
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Many edX learners in <strong>{{course_name}}</strong> are completing more problems every week, and
Many {{ platform_name }} learners in <strong>{{course_name}}</strong> are completing more problems every week, and
participating in the discussion forums. What do you want to do to keep learning?
{% endblocktrans %}
{% endif %}

View File

@@ -2,13 +2,13 @@
{% load ace %}
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Many edX learners are completing more problems every week, and
Many {{ platform_name }} learners are completing more problems every week, and
participating in the discussion forums. What do you want to do to keep learning?
{% endblocktrans %}
{% trans "Keep learning" %} <{% with_link_tracking dashboard_url %}>
{% else %}
{% blocktrans trimmed %}
Many edX learners in {{course_name}} are completing more problems every week, and
Many {{ platform_name }} learners in {{course_name}} are completing more problems every week, and
participating in the discussion forums. What do you want to do to keep learning?
{% endblocktrans %}
{% trans "Keep learning" %} <{% with_link_tracking course_url %}>

View File

@@ -4,12 +4,12 @@
{% block preview_text %}
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }}, and other courses on edX.org? We do, and were glad
Remember when you enrolled in {{ course_name }}, and other courses on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }} on edX.org? We do, and were glad
Remember when you enrolled in {{ course_name }} on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% endif %}
@@ -24,12 +24,12 @@
<p>
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Remember when you enrolled in <strong>{{ course_name }}</strong>, and other courses on edX.org? We do, and were glad
Remember when you enrolled in <strong>{{ course_name }}</strong>, and other courses on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Remember when you enrolled in <strong>{{ course_name }}</strong> on edX.org? We do, and were glad
Remember when you enrolled in <strong>{{ course_name }}</strong> on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% endif %}

View File

@@ -2,14 +2,14 @@
{% load ace %}
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }}, and other courses on edX.org? We do, and were glad
Remember when you enrolled in {{ course_name }}, and other courses on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% trans "Start learning now" %} <{% with_link_tracking dashboard_url %}>
{% else %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }} on edX.org? We do, and were glad
Remember when you enrolled in {{ course_name }} on {{ platform_name }}? We do, and were glad
to have you! Come see what everyone is learning.
{% endblocktrans %}

View File

@@ -8,7 +8,7 @@ import unittest
import paver.easy
import paver.tasks
from ddt import ddt, file_data
from ddt import ddt, file_data, data, unpack
from mock import MagicMock, mock_open, patch
from path import Path as path
from paver.easy import BuildFailure
@@ -60,6 +60,34 @@ class TestPaverQualityViolations(unittest.TestCase):
self.assertEqual(num, 2)
@ddt
class TestPaverQualityOptions(unittest.TestCase):
"""
Tests the paver pylint command-line options parsing.
"""
@data(
({'limit': '5500'}, (-1, 5500, False, pavelib.quality.ALL_SYSTEMS.split(','))),
({'limit': '1000:5500'}, (1000, 5500, False, pavelib.quality.ALL_SYSTEMS.split(','))),
({'limit': '1:2:3:4:5'}, (1, 2, False, pavelib.quality.ALL_SYSTEMS.split(','))),
({'system': 'lms,cms'}, (-1, -1, False, ['lms', 'cms'])),
(
{'limit': '2000:5000', 'errors': True, 'system': 'lms,cms,openedx'},
(2000, 5000, True, ['lms', 'cms', 'openedx'])
),
)
@unpack
def test_pylint_parser_other_string(self, options, expected_values):
class PaverOptions(object):
"""
Simple options class to mimick paver's Namespace object.
"""
def __init__(self, d):
self.__dict__ = d
paver_options = PaverOptions(options)
returned_values = pavelib.quality._parse_pylint_options(paver_options) # pylint: disable=protected-access
self.assertEqual(returned_values, expected_values)
class TestPaverReportViolationsCounts(unittest.TestCase):
"""
For testing utility functions for getting counts from reports for

View File

@@ -6,7 +6,6 @@ Check code quality using pep8, pylint, and diff_quality.
import json
import os
import re
from string import join
from paver.easy import BuildFailure, call_task, cmdopts, needs, sh, task
@@ -15,13 +14,7 @@ from openedx.core.djangolib.markup import HTML
from .utils.envs import Env
from .utils.timer import timed
ALL_SYSTEMS = [
'cms',
'common',
'lms',
'openedx',
'pavelib',
]
ALL_SYSTEMS = 'lms,cms,common,openedx,pavelib'
def top_python_dirs(dirname):
@@ -55,7 +48,7 @@ def find_fixme(options):
Run pylint on system code, only looking for fixme items.
"""
num_fixme = 0
systems = getattr(options, 'system', '').split(',') or ALL_SYSTEMS
systems = getattr(options, 'system', ALL_SYSTEMS).split(',')
for system in systems:
# Directory to put the pylint report in.
@@ -92,7 +85,7 @@ def find_fixme(options):
@cmdopts([
("system=", "s", "System to act on"),
("errors", "e", "Check for errors only"),
("limit=", "l", "limit for number of acceptable violations"),
("limit=", "l", "Limits for number of acceptable violations - either <upper> or <lower>:<upper>"),
])
@timed
def run_pylint(options):
@@ -100,14 +93,12 @@ def run_pylint(options):
Run pylint on system code. When violations limit is passed in,
fail the task if too many violations are found.
"""
num_violations = 0
violations_limit = int(getattr(options, 'limit', -1))
errors = getattr(options, 'errors', False)
systems = getattr(options, 'system', '').split(',') or ALL_SYSTEMS
lower_violations_limit, upper_violations_limit, errors, systems = _parse_pylint_options(options)
# Make sure the metrics subdirectory exists
Env.METRICS_DIR.makedirs_p()
num_violations = 0
for system in systems:
# Directory to put the pylint report in.
# This makes the folder if it doesn't already exist.
@@ -147,10 +138,45 @@ def run_pylint(options):
with open(Env.METRICS_DIR / "pylint", "w") as f:
f.write(violations_count_str)
# Fail number of violations is greater than the limit
if num_violations > violations_limit > -1:
raise BuildFailure("Failed. Too many pylint violations. "
"The limit is {violations_limit}.".format(violations_limit=violations_limit))
# Fail when number of violations is less than the lower limit,
# which likely means that pylint did not run successfully.
# If pylint *did* run successfully, then great! Modify the lower limit.
if num_violations < lower_violations_limit > -1:
raise BuildFailure(
"Failed. Too few pylint violations. "
"Expected to see at least {lower_limit} pylint violations. "
"Either pylint is not running correctly -or- "
"the limits should be lowered and/or the lower limit should be removed.".format(
lower_limit=lower_violations_limit
)
)
# Fail when number of violations is greater than the upper limit.
if num_violations > upper_violations_limit > -1:
raise BuildFailure(
"Failed. Too many pylint violations. "
"The limit is {upper_limit}.".format(upper_limit=upper_violations_limit)
)
def _parse_pylint_options(options):
"""
Parse the options passed to run_pylint.
"""
lower_violations_limit = upper_violations_limit = -1
violations_limit = getattr(options, 'limit', '').split(':')
if violations_limit[0]:
# Limit was specified.
if len(violations_limit) == 1:
# Only upper limit was specified.
upper_violations_limit = int(violations_limit[0])
else:
# Upper and lower limits were both specified.
lower_violations_limit = int(violations_limit[0])
upper_violations_limit = int(violations_limit[1])
errors = getattr(options, 'errors', False)
systems = getattr(options, 'system', ALL_SYSTEMS).split(',')
return lower_violations_limit, upper_violations_limit, errors, systems
def _count_pylint_violations(report_file):
@@ -244,7 +270,7 @@ def run_complexity():
Uses radon to examine cyclomatic complexity.
For additional details on radon, see http://radon.readthedocs.org/
"""
system_string = join(ALL_SYSTEMS, '/ ') + '/'
system_string = '/ '.join(ALL_SYSTEMS.split(',')) + '/'
complexity_report_dir = (Env.REPORT_DIR / "complexity")
complexity_report = complexity_report_dir / "python_complexity.log"

View File

@@ -11,7 +11,8 @@ set -e
###############################################################################
# Violations thresholds for failing the build
export PYLINT_THRESHOLD=3600
export LOWER_PYLINT_THRESHOLD=1000
export UPPER_PYLINT_THRESHOLD=5335
export ESLINT_THRESHOLD=9134
export STYLELINT_THRESHOLD=973

View File

@@ -57,7 +57,7 @@ else
echo "Finding pylint violations and storing in report..."
# HACK: we need to print something to the console, otherwise circleci
# fails and aborts the job because nothing is displayed for > 10 minutes.
paver run_pylint -l $PYLINT_THRESHOLD | tee pylint.log || EXIT=1
paver run_pylint -l $LOWER_PYLINT_THRESHOLD:$UPPER_PYLINT_THRESHOLD | tee pylint.log || EXIT=1
mkdir -p reports
PATH=$PATH:node_modules/.bin

View File

@@ -84,7 +84,7 @@ case "$TEST_SUITE" in
echo "Finding pep8 violations and storing report..."
paver run_pep8 > pep8.log || { cat pep8.log; EXIT=1; }
echo "Finding pylint violations and storing in report..."
paver run_pylint -l $PYLINT_THRESHOLD > pylint.log || { echo 'Too many pylint violations. You can view them in pylint.log'; EXIT=1; }
paver run_pylint -l $LOWER_PYLINT_THRESHOLD:$UPPER_PYLINT_THRESHOLD > pylint.log || { echo 'Too many pylint violations. You can view them in pylint.log'; EXIT=1; }
mkdir -p reports

View File

@@ -19,14 +19,14 @@
"mako-unparseable-expression": 0,
"mako-unwanted-html-filter": 0,
"python-close-before-format": 0,
"python-concat-html": 25,
"python-concat-html": 24,
"python-custom-escape": 13,
"python-deprecated-display-name": 41,
"python-interpolate-html": 66,
"python-interpolate-html": 64,
"python-parse-error": 0,
"python-requires-html-or-text": 0,
"python-wrap-html": 235,
"python-wrap-html": 226,
"underscore-not-escaped": 507
},
"total": 1782
"total": 1770
}