Merge pull request #3600 from edx/rc/2014-05-06

Release Candidate cut May 6th, 2014, finally made it to release branch on the following Monday (May 12th)
This commit is contained in:
marcotuts
2014-05-12 12:32:10 -04:00
436 changed files with 150743 additions and 10781 deletions

View File

@@ -142,3 +142,4 @@ Marco Re <mrc.re@tiscali.it>
Jonas Jelten <jelten@in.tum.de>
Christine Lytwynec <clytwynec@edx.org>
John Cox <johncox@google.com>
Ben Weeks <benweeks@mit.edu>

View File

@@ -2,26 +2,57 @@
Contributing to edx-platform
############################
Contributions to edx-platform are very welcome, and strongly encouraged! The
easiest way is to fork the repo and then make a pull request from your fork.
Check out our `process documentation`_, or read on for details on how to
become a contributor, edx-platform code quality, testing, making a pull
request, and more.
Contributions to edx-platform are very welcome, and strongly encouraged! We've
put together `some documentation that describes our contribution process`_,
but here's a step-by-step guide that should help you get started.
.. _process documentation: https://github.com/edx/edx-platform/blob/master/docs/en_us/developers/source/process/index.rst
.. _some documentation that describes our contribution process: http://edx.readthedocs.org/projects/userdocs/en/latest/process/overview.html
Becoming a Contributor
======================
Step 0: Join the Conversation
=============================
Before your first pull request is merged, you'll need to sign the `individual
contributor agreement`_ and send it in. This confirms you have the authority to
contribute the code in the pull request and ensures we can relicense it.
Got an idea for how to improve the codebase? Fantastic, we'd love to hear about
it! Before you dive in and spend a lot of time and effort making a pull request,
it's a good idea to discuss your idea with other interested developers. You may
get some valuable feedback that changes how you think about your idea, or you
may find other developers who have the same idea and want to work together.
For real-time conversation, we use `IRC`_: we all hang out in the
`#edx-code channel on Freenode`_. Come join us! The channel tends to be most
active Monday through Friday between 13:00 and 21:00 UTC
(9am to 5pm US Eastern time), but interesting conversations can happen
at any time.
.. _IRC: http://www.irchelp.org/
.. _#edx-code channel on Freenode: http://webchat.freenode.net/?channels=edx-code
For asynchronous conversation, we have several mailing lists on Google Groups:
* `openedx-ops`_: everything related to *running* Open edX. This includes
installation issues, server management, cost analysis, and so on.
* `openedx-translation`_: everything related to *translating* Open edX into
other languages. This includes volunteer translators, our internationalization
infrastructure, issues related to Transifex, and so on.
* `edx-code`_: everything related to the *code* in Open edX. This includes
feature requests, idea proposals, refactorings, and so on.
.. _openedx-ops: https://groups.google.com/forum/#!forum/openedx-ops
.. _openedx-translation: https://groups.google.com/forum/#!forum/openedx-translation
.. _edx-code: https://groups.google.com/forum/#!forum/edx-code
Step 1: Sign a Contribution Agreement
=====================================
Before edX can accept any code contributions from you, you'll need to sign
the `individual contributor agreement`_ and send it in. This confirms
that you have the authority to contribute the code in the pull request and
ensures that edX can relicense it.
You should print out the agreement and sign it. Then scan (or photograph) the
signed agreement and email it to the email address indicated on the agreement.
Alternatively, you're also free to physically mail the agreement to the street
address on the agreement. Once we have your agreement in hand, we can begin
merging your work.
reviewing and merging your work.
You'll also need to add yourself to the `AUTHORS` file when you submit your
first pull request. You should add your full name as well as the email address
@@ -31,156 +62,69 @@ request to contain multiple commits, including a commit to `AUTHORS`).
Alternatively, you can open up a separate PR just to have your name added to
the `AUTHORS` file, and link that PR to the PR with your changes.
Step 2: Fork, Commit, and Pull Request
======================================
Github has some great documentation on `how to fork a git repository`_. Once
you've done that, make your changes and `send us a pull request`_! Be sure to
include a detailed description for your pull request, so that a community
manager can understand *what* change you're making, *why* you're making it, *how*
it should work now, and how you can *test* that it's working correctly.
Code Quality Guidelines
=======================
.. _how to fork a git repository: https://help.github.com/articles/fork-a-repo
.. _send us a pull request: https://help.github.com/articles/creating-a-pull-request
Comments
--------
Step 3: Meet PR Requirements
============================
We expect you to contribute code that is self-documenting as much as possible.
This means submitting code with well-formed variable, function, class, and
method names; good docstrings; lots of comments. Use your discretion - not
every line needs to be commented. However, code that is obtuse is hard to
maintain and hard for others to build upon. So please do your best to provide
code that is easy to read and well-commented.
Our `contributor documentation`_ includes a long list of requirements that pull
requests must meet in order to be reviewed by a core committer. These requirements
include things like documentation and passing tests: see the
`contributor documentation`_ page for the full list.
Python/Javascript Styling
-------------------------
.. _contributor documentation: http://edx.readthedocs.org/projects/userdocs/en/latest/process/contributor.html
Before you submit your first pull request, please review the edx-platform code
quality and style guidelines:
Step 4: Approval by Community Manager and Product Owner
=======================================================
* `Python Guidelines <https://github.com/edx/edx-platform/wiki/Python-Guidelines>`_
* `Javascript Guidelines <https://github.com/edx/edx-platform/wiki/Javascript-Guidelines>`_
A community manager will read the description of your pull request. If the
description is understandable, the community manager will send the pull request
to a product owner. The product owner will evaluate if the pull request is a
good idea for Open edX, and if not, your pull request will be rejected. This
is another good reason why you should discuss your ideas with other members
of the community before working on a pull request!
Coding conventions should be followed. Your submission should not introduce any
new pep8 or pylint errors (and ideally, should fix up other errors you
encounter in the files you edit). From the edx-platform main directory, you can
run the command::
Step 5: Code Review by Core Committer(s)
========================================
$ rake quality
If your pull request meets the requirements listed in the
`contributor documentation`_, and it hasn't been rejected by a product owner,
then it will be scheduled for code review by one or more core committers. This
process sometimes takes awhile: currently, all core committers on the project
are employees of edX, and they have to balance their time between code review
and new development.
to print the "Diff Quality" report, a report of the quality violations your
branch has made.
Once the code review process has started, please be responsive to comments on
the pull request, so we can keep the review process moving forward.
If you are unable to respond for a few days, that's fine, but
please add a comment informing us of that -- otherwise, it looks like you're
abandoning your work!
Although we try to be vigilant and resolve all quality violations, some Pylint
violations are just too challenging to resolve, so we opt to ignore them via
use of a pragma. A pragma tells Pylint to ignore the violation in the given
line. An example is::
Step 6: Merge!
==============
self.assertEquals(msg, form._errors['course_id'][0]) # pylint: disable=protected-access
The pragma starts with a ``#`` two spaces after the end of the line. We prefer
that you use the full name of the error (``pylint: disable=unused-argument`` as
opposed to ``pylint: disable=W0613``), so it's more clear what you're disabling
in the line.
If you have any questions, don't hesitate to reach out to us on email or IRC;
see the section on **Contacting Us**, below, for more.
Once the core committers are satisfied that your pull request is ready to go,
one of them will merge it for you. Your code will end up on the edX production
servers in the next release, which usually which happens every week. Congrats!
Testing Coverage Guidelines
===========================
Before you submit a pull request, please refer to the `edx-platform testing
documentation`_.
Code you commit should *increase* test coverage, not decrease it. For more
involved contributions, you may want to discuss your intentions on the mailing
list *before* you start coding.
Running the command ::
$ rake test
in the edx-platform directory will run all the unit tests on edx-platform (to
run specific tests, refer to the testing documentation). Once you've run this
command, you can run ::
$ rake coverage
to generate the "Diff Coverage" report. This report tells you how much of the
Python and JavaScript code you've changed is covered by unit tests. We aim for
a coverage report score of 95% or higher. We also encourage you to write
acceptance tests as your changes require. For more in-depth help on various
types of tests, please refer to the `edx-platform testing documentation`_.
Opening A Pull Request
======================
When you open a pull request (PR), please follow these guidelines:
* In the PR description, please be as clear as possible explaining what the
change is. This helps us so much in contextualizing your PR and providing
appropriate reviewers for you. Take a look at `pull request 1322`_ for an
example of a verbose PR description for a new feature.
* As far as code goes, a first pass is to make sure that your code is of high
quality. This means ensuring plenty of comments, as well as a 100% pass rate
when you run ``rake quality`` locally. See the section **Code Quality
Guidelines**.
* Testing coverage should be as complete as possible. 95% or greater on
JavaScript and Python coverage (you can check this by running ``rake test;
rake coverage`` locally). Percentage coverage is only calculated from unit
tests, however. If you're adding new visual features, we love seeing
acceptance tests as applicable. See the section **Testing Coverage
Guidelines**.
* Be sure that your commit history is *clean* - that is, you don't have a ton
of tiny commits with throwaway commit messages such as "Fix", "Arugh",
"asdfjkl;", "Merge branch Master into fork", etc. Commit messages should be
concise and explain what work was done. The first line should be fewer than
50 characters; you may add additional lines to your commit messages for
further explaination.
* To clean up your commit history you'll need to perform an *interactive
rebase* where you squash your commits together. More about interactive
rebase can be found in the `github help documents`_ or by Googling.
* The reasoning behind a clean commit history is that we want the log of all
commits in edx-platform to be readable and self-documenting. This way,
developers can take a look at all recent commits in the past few days or
weeks and have a good understanding of all the code changes that were made.
* The `CHANGELOG` is a list of changes to the platform, distinct from the git
log because the audience is not developers but rather users of our platform
(specifically, course authors). Please make an entry in `CHANGELOG`
describing your change if it is something that you think platform users would
be interested in - eg a major bugfix, new feature, or update to existing
functionality. Be sure to also indicate what system (LMS, CMS, etc) your
change affects. If in doubt if your change is "big enough", we encourage you
to make a `CHANGELOG` entry!
* Make sure that your branch is freshly rebased on master when you go to open
your pull request. If you don't have repo permissions, you won't be able to
see if your branch is able to be cleanly merged or not. We'll tell you if
it's not; however, rebasing before you open your PR will help decrease the
frequency of conflicts.
* If you need help with rebasing, please see the following resources:
1. `Git Book <http://git-scm.com/book/en/Git-Branching-Rebasing>`_
2. `Git Docs <http://git-scm.com/docs/git-rebase>`_
3. `Interactive Git tutorial <http://pcottle.github.io/learnGitBranching/>`_ -- totally awesome!!
4. `Git Ready <http://gitready.com/intermediate/2009/01/31/intro-to-rebase.html>`_
Finally, **Please Do Not** close a pull request and open a new one to respond
to review comments. Keep the same pull request open, so it's clear how your
code has been worked upon and what reviewers have been involved in the
conversation. Rebase as needed to get updated code from master into your
branch.
Expectations We Have of You
---------------------------
===========================
By opening up a pull request, we expect the following things:
1. You've read and understand the instructions in this contributing file.
1. You've read and understand the instructions in this contributing file and
the contribution process documentation.
2. You are ready to engage with the edX community. Engaging means you will be
prompt in following up with review comments and critiques. Do not open up a
@@ -193,124 +137,21 @@ By opening up a pull request, we expect the following things:
4. If you do not respond to comments on your pull request within 7 days, we
will close it. You are welcome to re-open it when you are ready to engage.
=========================
Expections You Have of Us
-------------------------
=========================
1. Within a week of opening up a pull request, one of our open source community
managers will triage it, either tagging other reviewers for the PR or asking
follow up questions (Please give us a little extra time if you open the PR
on a weekend or around a US holiday! We may take a little longer getting to
it.).
1. Within a week of opening up a pull request, one of our community managers
will triage it, starting the documented contribution process. (Please
give us a little extra time if you open the PR on a weekend or
around a US holiday! We may take a little longer getting to it.)
2. We promise to engage in an active dialogue with you from the time we begin
reviewing until either the PR is merged (by an edX staff member), or we
reviewing until either the PR is merged (by a core committer), or we
decide that, for whatever reason, it should be closed.
3. Once we have determined through visual review that your code is not
malicious, we will run a Jenkins build on your branch.
Using Jenkins Builds
--------------------
When you open up a pull request, an edX staff member can decide to run a
Jenkins build on your branch. We will do this once we have determined that your
code is not malicious.
When a Jenkins job is run, all unit, Javascript, and acceptance tests are run.
**If the build fails...**
Click on the build to be brought to the build page. You'll see a matrix of blue
and red dots; the red dots indicate what section failing tests were present in.
You can click on the test name to be brought to an error trace that explains
why the tests fail. Please address the failing tests before requesting a new
build on your branch. If the failures appear to not have anything to do with
your code, it may be the case that the master branch is failing. You can ask
your reviewers for advice in this scenario.
If the build says "Unstable" but passes all tests, you have introduced too many
pep8 and pylint violations. Please refer to the **Code Quality Guidelines**
section and clean up the code.
**If the build passes...**
If all the tests pass, the "Diff Coverage" and "Diff Quality" reports are
generated. Click on the "View Reports" link on your pull request to be brought
to the Jenkins report page. In a column on the left side of the page are a few
links, including "Diff Coverage Report" and "Diff Quality Report". View each of
these reports (making note that the Diff Quality report has two tabs - one for
pep8, and one for Pylint).
Make sure your quality coverage is 100% and your test coverage is at least 95%.
Adjust your code appropriately if these metrics are not high enough. Be sure to
ask your reviewers for advice if you need it.
Contacting Us
=============
Mailing list
------------
If you have any questions, please ask on the `mailing list`_. It's always a
good idea to first search through the archives, to see if any of your questions
have already been asked and answered.
The edx platform team is based in the US, so we're best able to respond to
questions posted in English. You're most likely to get an answer if you ask
questions related to edx-platform code or conventions. Questions only
tangentially related to edx-platform may be better answered on different forums
or mailing lists (for example, asking for help on how to set up Git is better
posted on a Git related message list or forum).
Questions about translations, creating courses, or using Studio are not
appropriate for the edx-code mailing list. We have a few other mailing lists
you may be interested in:
* `openedx-translation <https://groups.google.com/forum/#!forum/openedx-translation>`_
* `openedx-studio <https://groups.google.com/forum/#!forum/openedx-studio>`_
IRC
---
Many edX employees and community members hang out in the #edx-code `IRC
channel`_ on Freenode. We're always happy to see more people hanging out with
us there!
**Tips on Using IRC**
For clients, the `webchat <http://webchat.freenode.net>`_ is easiest, because you
don't need to install anything and it's cross-platform. `ChatZilla
<http://chatzilla.hacksrus.com/>`_ is almost as easy -- it's a Firefox
extension, and works anywhere Firefox does. For an installed application,
`Pidgin <http://pidgin.im>`_ works decently (or `Adium <https://adium.im>`_ on
Mac), and has a familiar instant-messenger-style interface. For something truly
dedicated to IRC, there's `mIRC <http://www.mirc.com>`_ for Windows (free),
`LimeChat <http://limechat.net/mac/>`_ for Mac (free), or `Textual
<http://www.codeux.com/textual/>`_ for Mac (paid). There are also many other
clients out there, but those are some good recommendations for people
relatively new to IRC.
Pull requests/issues
--------------------
We do not make much use of Github issues, so opening an issue on edx-platform
is not the best way to reach us. However, when you've opened up a pull request,
please please don't be shy about adding comments and having a robust
conversation with your pull request reviewers.
Your pull request is a good place to ask pointed questions about the code
you've written, and we're very happy to have interaction with you through code,
commits, and comments.
.. _individual contributor agreement: http://code.edx.org/individual-contributor-agreement.pdf
.. _edx-platform testing documentation: https://github.com/edx/edx-platform/blob/master/docs/en_us/internal/testing.md
.. _mailing list: https://groups.google.com/forum/#!forum/edx-code
.. _IRC channel: http://www.irchelp.org/irchelp/new2irc.html
.. _pull request 1322: https://github.com/edx/edx-platform/pull/1322
.. _github help documents: https://help.github.com/articles/interactive-rebase

View File

@@ -26,10 +26,10 @@ for details.
Documentation
------------
High-level documentation of the code is located in the `docs` subdirectory.
Most (although not all) of our documentation is built using
Documentation for developers, researchers, and course staff is located in the
`docs` subdirectory. Documentation is built using
[Sphinx](http://sphinx-doc.org/): you can [view the built documentation on
ReadTheDocs](http://edx.readthedocs.org/).
ReadTheDocs](http://docs.edx.org/).
How to Contribute
-----------------

View File

@@ -251,6 +251,7 @@ BROKER_URL = "{0}://{1}:{2}@{3}/{4}".format(CELERY_BROKER_TRANSPORT,
# Event tracking
TRACKING_BACKENDS.update(AUTH_TOKENS.get("TRACKING_BACKENDS", {}))
EVENT_TRACKING_BACKENDS.update(AUTH_TOKENS.get("EVENT_TRACKING_BACKENDS", {}))
SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {})
VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', [])

View File

@@ -546,7 +546,7 @@ COURSES_WITH_UNSAFE_CODE = []
############################## EVENT TRACKING #################################
TRACK_MAX_EVENT = 10000
TRACK_MAX_EVENT = 50000
TRACKING_BACKENDS = {
'logger': {
@@ -557,6 +557,26 @@ TRACKING_BACKENDS = {
}
}
# We're already logging events, and we don't want to capture user
# names/passwords. Heartbeat events are likely not interesting.
TRACKING_IGNORE_URL_PATTERNS = [r'^/event', r'^/login', r'^/heartbeat']
EVENT_TRACKING_ENABLED = True
EVENT_TRACKING_BACKENDS = {
'logger': {
'ENGINE': 'eventtracking.backends.logger.LoggerBackend',
'OPTIONS': {
'name': 'tracking',
'max_event_size': TRACK_MAX_EVENT,
}
}
}
EVENT_TRACKING_PROCESSORS = [
{
'ENGINE': 'track.shim.LegacyFieldMappingProcessor'
}
]
#### PASSWORD POLICY SETTINGS #####
PASSWORD_MIN_LENGTH = None
@@ -565,11 +585,6 @@ PASSWORD_COMPLEXITY = {}
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD = None
PASSWORD_DICTIONARY = []
# We're already logging events, and we don't want to capture user
# names/passwords. Heartbeat events are likely not interesting.
TRACKING_IGNORE_URL_PATTERNS = [r'^/event', r'^/login', r'^/heartbeat']
TRACKING_ENABLED = True
##### ACCOUNT LOCKOUT DEFAULT PARAMETERS #####
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = 5
MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = 15 * 60

View File

@@ -1,8 +1,9 @@
define ["jquery", "jquery.ui", "gettext", "backbone",
"js/views/feedback_notification", "js/views/feedback_prompt",
"coffee/src/views/module_edit", "js/models/module_info"],
($, ui, gettext, Backbone, NotificationView, PromptView, ModuleEditView, ModuleModel) ->
class UnitEditView extends Backbone.View
"coffee/src/views/module_edit", "js/models/module_info",
"js/views/baseview"],
($, ui, gettext, Backbone, NotificationView, PromptView, ModuleEditView, ModuleModel, BaseView) ->
class UnitEditView extends BaseView
events:
'click .new-component .new-component-type a.multiple-templates': 'showComponentTemplates'
'click .new-component .new-component-type a.single-template': 'saveNewComponent'
@@ -212,30 +213,35 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
)
createDraft: (event) ->
@wait(true)
self = this
@disableElementWhileRunning($(event.target), ->
self.wait(true)
$.postJSON(self.model.url(), {
publish: 'create_draft'
}, =>
analytics.track "Created Draft",
course: course_location_analytics
unit_id: unit_location_analytics
$.postJSON(@model.url(), {
publish: 'create_draft'
}, =>
analytics.track "Created Draft",
course: course_location_analytics
unit_id: unit_location_analytics
@model.set('state', 'draft')
self.model.set('state', 'draft')
)
)
publishDraft: (event) ->
@wait(true)
@saveDraft()
self = this
@disableElementWhileRunning($(event.target), ->
self.wait(true)
self.saveDraft()
$.postJSON(@model.url(), {
publish: 'make_public'
}, =>
analytics.track "Published Draft",
course: course_location_analytics
unit_id: unit_location_analytics
$.postJSON(self.model.url(), {
publish: 'make_public'
}, =>
analytics.track "Published Draft",
course: course_location_analytics
unit_id: unit_location_analytics
@model.set('state', 'public')
self.model.set('state', 'public')
)
)
setVisibility: (event) ->
@@ -259,7 +265,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@model.set('state', @$('.visibility-select').val())
)
class UnitEditView.NameEdit extends Backbone.View
class UnitEditView.NameEdit extends BaseView
events:
'change .unit-display-name-input': 'saveName'
@@ -293,14 +299,14 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
display_name: metadata.display_name
class UnitEditView.LocationState extends Backbone.View
class UnitEditView.LocationState extends BaseView
initialize: =>
@model.on('change:state', @render)
render: =>
@$el.toggleClass("#{@model.previous('state')}-item #{@model.get('state')}-item")
class UnitEditView.Visibility extends Backbone.View
class UnitEditView.Visibility extends BaseView
initialize: =>
@model.on('change:state', @render)
@render()

View File

@@ -1,6 +0,0 @@
define(["backbone", "js/models/course_relative"], function(Backbone, CourseRelativeModel) {
var CourseRelativeCollection = Backbone.Collection.extend({
model: CourseRelativeModel
});
return CourseRelativeCollection;
});

View File

@@ -1,9 +0,0 @@
define(["backbone"], function(Backbone) {
var CourseRelative = Backbone.Model.extend({
defaults: {
course_location : null, // must never be null, but here to doc the field
idx : null // the index making it unique in the containing collection (no implied sort)
}
});
return CourseRelative;
});

View File

@@ -76,5 +76,24 @@ define(["jquery", "underscore", "js/views/baseview", "js/utils/handle_iframe_bin
expect(view.$('.is-collapsible')).not.toHaveClass('collapsed');
});
});
describe("disabled element while running", function() {
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
var viewWithLink,
link,
deferred = new $.Deferred(),
promise = deferred.promise(),
view = new BaseView();
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
link = $("#link");
expect(link).not.toHaveClass("is-disabled");
view.disableElementWhileRunning(link, function(){return promise});
expect(link).toHaveClass("is-disabled");
deferred.resolve();
expect(link).not.toHaveClass("is-disabled");
});
});
});
});

View File

@@ -162,5 +162,79 @@ define(["coffee/src/views/unit", "js/models/module_info", "js/spec_helpers/creat
verifyComponents(unit, ['loc_1', 'loc_2']);
});
});
describe("Disabled edit/publish links during ajax call", function() {
var unit,
link,
draft_states = [
{
state: "draft",
selector: ".publish-draft"
},
{
state: "public",
selector: ".create-draft"
}
],
editLinkFixture =
'<div class="main-wrapper edit-state-draft" data-locator="unit_locator"> \
<div class="unit-settings window"> \
<h4 class="header">Unit Settings</h4> \
<div class="window-contents"> \
<div class="row published-alert"> \
<p class="edit-draft-message"> \
<a href="#" class="create-draft">edit a draft</a> \
</p> \
<p class="publish-draft-message"> \
<a href="#" class="publish-draft">replace it with this draft</a> \
</p> \
</div> \
</div> \
</div> \
</div>';
function test_link_disabled_during_ajax_call(draft_state) {
beforeEach(function () {
setFixtures(editLinkFixture);
unit = new UnitEditView({
el: $('.main-wrapper'),
model: new ModuleModel({
id: 'unit_locator',
state: draft_state['state']
})
});
// needed to stub out the ajax
window.analytics = jasmine.createSpyObj('analytics', ['track']);
window.course_location_analytics = jasmine.createSpy('course_location_analytics');
window.unit_location_analytics = jasmine.createSpy('unit_location_analytics');
});
it("reenables the " + draft_state['selector'] + " link once the ajax call returns", function() {
runs(function(){
spyOn($, "ajax").andCallThrough();
spyOn($.fn, 'addClass').andCallThrough();
spyOn($.fn, 'removeClass').andCallThrough();
link = $(draft_state['selector']);
link.click();
});
waitsFor(function(){
// wait for "is-disabled" to be removed as a class
return !($(draft_state['selector']).hasClass("is-disabled"));
}, 500);
runs(function(){
// check that the `is-disabled` class was added and removed
expect($.fn.addClass).toHaveBeenCalledWith("is-disabled");
expect($.fn.removeClass).toHaveBeenCalledWith("is-disabled");
// make sure the link finishes without the `is-disabled` class
expect(link).not.toHaveClass("is-disabled");
// affirm that ajax was called
expect($.ajax).toHaveBeenCalled();
});
});
};
for (var i = 0; i < draft_states.length; i++) {
test_link_disabled_during_ajax_call(draft_states[i]);
};
});
}
);

View File

@@ -1,13 +1,13 @@
define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"],
function ($, _, Backbone, IframeUtils) {
/*
This view is extended from backbone to provide useful functionality for all Studio views.
This functionality includes:
- automatic expand and collapse of elements with the 'ui-toggle-expansion' class specified
- additional control of rendering by overriding 'beforeRender' or 'afterRender'
This view is extended from backbone to provide useful functionality for all Studio views.
This functionality includes:
- automatic expand and collapse of elements with the 'ui-toggle-expansion' class specified
- additional control of rendering by overriding 'beforeRender' or 'afterRender'
Note: the default 'afterRender' function calls a utility function 'iframeBinding' which modifies
iframe src urls on a page so that they are rendered as part of the DOM.
Note: the default 'afterRender' function calls a utility function 'iframeBinding' which modifies
iframe src urls on a page so that they are rendered as part of the DOM.
*/
var BaseView = Backbone.View.extend({
@@ -60,6 +60,20 @@ define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"],
$('.ui-loading').hide();
},
/**
* Disables a given element when a given operation is running.
* @param {jQuery} element: the element to be disabled.
* @param operation: the operation during whose duration the
* element should be disabled. The operation should return
* a jquery promise.
*/
disableElementWhileRunning: function(element, operation) {
element.addClass("is-disabled");
operation().always(function() {
element.removeClass("is-disabled");
});
},
/**
* Loads the named template from the page, or logs an error if it fails.
* @param name The name of the template.

View File

@@ -136,11 +136,9 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
mode = parent.data('mode');
event.preventDefault();
var $cheatsheet = $('.simple-editor-cheatsheet');
if ($cheatsheet.hasClass("shown")) {
$(".CodeMirror").removeAttr("style");
$(".modal-content").removeAttr("style");
$cheatsheet.removeClass('shown');
}
$(".CodeMirror").css({"overflow": "none"});
$(".modal-content").removeAttr("style");
$cheatsheet.removeClass('shown');
this.selectMode(mode);
},

View File

@@ -195,6 +195,11 @@
.modal-window .editor-with-buttons {
margin-bottom: ($baseline*3);
// temporary fix until xblock structure is set
&.wrapper-comp-settings .list-input.settings-list {
height: 375px;
}
// TODO: need to sync up (alongside general editing mode) with xblocks.scss UI
.xblock-actions {
background-color: $gray-l4;
@@ -202,9 +207,6 @@
width: 100%;
bottom: 0;
}
}

View File

@@ -191,7 +191,7 @@ $('#fileupload').fileupload({
window.onbeforeunload = null;
if (xhr.status != 200) {
if (!result.responseText) {
alert(gettext("Your browser has timed out, but the server is still processing your import. Please wait 5 min and verify that the new content has appeared."));
alert(gettext("Your browser has timed out, but the server is still processing your import. Please wait 5 minutes and verify that the new content has appeared."));
return;
}
var serverMsg = $.parseJSON(result.responseText);

View File

@@ -140,3 +140,9 @@ if settings.DEBUG:
# pylint: disable=C0103
handler404 = 'contentstore.views.render_404'
handler500 = 'contentstore.views.render_500'
# display error page templates, for testing purposes
urlpatterns += (
url(r'404', handler404),
url(r'500', handler500),
)

View File

@@ -34,7 +34,7 @@ class EmbargoedStateAdmin(ConfigurationModelAdmin):
form = EmbargoedStateForm
fieldsets = (
(None, {
'fields': ('embargoed_countries',),
'fields': ('enabled', 'embargoed_countries',),
'description': textwrap.dedent("""Enter the two-letter ISO-3166-1 Alpha-2
code of the country or countries to embargo in the following box. For help,
see <a href="http://en.wikipedia.org/wiki/ISO_3166-1#Officially_assigned_code_elements">
@@ -51,7 +51,7 @@ class IPFilterAdmin(ConfigurationModelAdmin):
form = IPFilterForm
fieldsets = (
(None, {
'fields': ('whitelist', 'blacklist'),
'fields': ('enabled', 'whitelist', 'blacklist'),
'description': textwrap.dedent("""Enter specific IP addresses to explicitly
whitelist (not block) or blacklist (block) in the appropriate box below.
Separate IP addresses with a comma. Do not surround with quotes.

View File

@@ -10,7 +10,6 @@ file and check it in at the same time as your model changes. To do that,
2. ./manage.py lms schemamigration student --auto description_of_your_change
3. Add the migration file created in edx-platform/common/djangoapps/student/migrations/
"""
import crum
from datetime import datetime, timedelta
import hashlib
import json
@@ -34,7 +33,6 @@ from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext_noop
from django_countries import CountryField
from track import contexts
from track.views import server_track
from eventtracking import tracker
from importlib import import_module
@@ -718,7 +716,7 @@ class CourseEnrollment(models.Model):
}
with tracker.get_tracker().context(event_name, context):
server_track(crum.get_current_request(), event_name, data)
tracker.emit(event_name, data)
except: # pylint: disable=bare-except
if event_name and self.course_id:
log.exception('Unable to emit event %s for user %s and course %s', event_name, self.user.username, self.course_id)

View File

@@ -12,16 +12,17 @@ import pytz
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from django.test.client import RequestFactory
from django.test.client import RequestFactory, Client
from django.contrib.auth.models import User, AnonymousUser
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import HttpResponse
from unittest.case import SkipTest
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from courseware.tests.tests import TEST_DATA_MIXED_MODULESTORE
from mock import Mock, patch, sentinel
from mock import Mock, patch
from student.models import anonymous_id_for_user, user_by_anonymous_id, CourseEnrollment, unique_id_for_user
from student.views import (process_survey_link, _cert_info,
@@ -146,12 +147,58 @@ class DashboardTest(TestCase):
def setUp(self):
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
self.assertIsNotNone(self.course)
self.user = UserFactory.create(username="jack", email="jack@fake.edx.org")
self.user = UserFactory.create(username="jack", email="jack@fake.edx.org", password='test')
CourseModeFactory.create(
course_id=self.course.id,
mode_slug='honor',
mode_display_name='Honor Code',
)
self.client = Client()
def check_verification_status_on(self, mode, value):
"""
Check that the css class and the status message are in the dashboard html.
"""
CourseEnrollment.enroll(self.user, self.course.location.course_id, mode=mode)
try:
response = self.client.get(reverse('dashboard'))
except NoReverseMatch:
raise SkipTest("Skip this test if url cannot be found (ie running from CMS tests)")
self.assertContains(response, "class=\"course {0}\"".format(mode))
self.assertContains(response, value)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_VERIFIED_CERTIFICATES': True})
def test_verification_status_visible(self):
"""
Test that the certificate verification status for courses is visible on the dashboard.
"""
self.client.login(username="jack", password="test")
self.check_verification_status_on('verified', 'You\'re enrolled as a verified student')
self.check_verification_status_on('honor', 'You\'re enrolled as an honor code student')
self.check_verification_status_on('audit', 'You\'re auditing this course')
def check_verification_status_off(self, mode, value):
"""
Check that the css class and the status message are not in the dashboard html.
"""
CourseEnrollment.enroll(self.user, self.course.location.course_id, mode=mode)
try:
response = self.client.get(reverse('dashboard'))
except NoReverseMatch:
raise SkipTest("Skip this test if url cannot be found (ie running from CMS tests)")
self.assertNotContains(response, "class=\"course {0}\"".format(mode))
self.assertNotContains(response, value)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_VERIFIED_CERTIFICATES': False})
def test_verification_status_invisible(self):
"""
Test that the certificate verification status for courses is not visible on the dashboard
if the verified certificates setting is off.
"""
self.client.login(username="jack", password="test")
self.check_verification_status_off('verified', 'You\'re enrolled as a verified student')
self.check_verification_status_off('honor', 'You\'re enrolled as an honor code student')
self.check_verification_status_off('audit', 'You\'re auditing this course')
def test_course_mode_info(self):
verified_mode = CourseModeFactory.create(
@@ -192,15 +239,10 @@ class EnrollInCourseTest(TestCase):
"""Tests enrolling and unenrolling in courses."""
def setUp(self):
patcher = patch('student.models.server_track')
self.mock_server_track = patcher.start()
patcher = patch('student.models.tracker')
self.mock_tracker = patcher.start()
self.addCleanup(patcher.stop)
crum_patcher = patch('student.models.crum.get_current_request')
self.mock_get_current_request = crum_patcher.start()
self.addCleanup(crum_patcher.stop)
self.mock_get_current_request.return_value = sentinel.request
def test_enrollment(self):
user = User.objects.create_user("joe", "joe@joe.com", "password")
course_id = "edX/Test101/2013"
@@ -254,13 +296,12 @@ class EnrollInCourseTest(TestCase):
def assert_no_events_were_emitted(self):
"""Ensures no events were emitted since the last event related assertion"""
self.assertFalse(self.mock_server_track.called)
self.mock_server_track.reset_mock()
self.assertFalse(self.mock_tracker.emit.called) # pylint: disable=maybe-no-member
self.mock_tracker.reset_mock()
def assert_enrollment_event_was_emitted(self, user, course_id):
"""Ensures an enrollment event was emitted since the last event related assertion"""
self.mock_server_track.assert_called_once_with(
sentinel.request,
self.mock_tracker.emit.assert_called_once_with( # pylint: disable=maybe-no-member
'edx.course.enrollment.activated',
{
'course_id': course_id,
@@ -268,12 +309,11 @@ class EnrollInCourseTest(TestCase):
'mode': 'honor'
}
)
self.mock_server_track.reset_mock()
self.mock_tracker.reset_mock()
def assert_unenrollment_event_was_emitted(self, user, course_id):
"""Ensures an unenrollment event was emitted since the last event related assertion"""
self.mock_server_track.assert_called_once_with(
sentinel.request,
self.mock_tracker.emit.assert_called_once_with( # pylint: disable=maybe-no-member
'edx.course.enrollment.deactivated',
{
'course_id': course_id,
@@ -281,7 +321,7 @@ class EnrollInCourseTest(TestCase):
'mode': 'honor'
}
)
self.mock_server_track.reset_mock()
self.mock_tracker.reset_mock()
def test_enrollment_non_existent_user(self):
# Testing enrollment of newly unsaved user (i.e. no database entry)
@@ -445,8 +485,8 @@ class AnonymousLookupTable(TestCase):
mode_slug='honor',
mode_display_name='Honor Code',
)
patcher = patch('student.models.server_track')
self.mock_server_track = patcher.start()
patcher = patch('student.models.tracker')
patcher.start()
self.addCleanup(patcher.stop)
def test_for_unregistered_user(self): # same path as for logged out user

View File

@@ -200,7 +200,7 @@ def cert_info(user, course):
'survey_url': url, only if show_survey_button is True
'grade': if status is not 'processing'
"""
if not course.has_ended():
if not course.may_certify():
return {}
return _cert_info(user, course, certificate_status_for_student(user, course.id))
@@ -291,6 +291,15 @@ def _cert_info(user, course, cert_status):
"""
Implements the logic for cert_info -- split out for testing.
"""
# simplify the status for the template using this lookup table
template_state = {
CertificateStatuses.generating: 'generating',
CertificateStatuses.regenerating: 'generating',
CertificateStatuses.downloadable: 'ready',
CertificateStatuses.notpassing: 'notpassing',
CertificateStatuses.restricted: 'restricted',
}
default_status = 'processing'
default_info = {'status': default_status,
@@ -302,15 +311,6 @@ def _cert_info(user, course, cert_status):
if cert_status is None:
return default_info
# simplify the status for the template using this lookup table
template_state = {
CertificateStatuses.generating: 'generating',
CertificateStatuses.regenerating: 'generating',
CertificateStatuses.downloadable: 'ready',
CertificateStatuses.notpassing: 'notpassing',
CertificateStatuses.restricted: 'restricted',
}
status = template_state.get(cert_status['status'], default_status)
d = {'status': status,

View File

@@ -13,7 +13,7 @@ from uuid import uuid4
import textwrap
import urllib
import re
from oauthlib.oauth1.rfc5849 import signature
from oauthlib.oauth1.rfc5849 import signature, parameters
import oauthlib.oauth1
import hashlib
import base64
@@ -46,7 +46,16 @@ class StubLtiHandler(StubHttpRequestHandler):
status_message = 'LTI consumer (edX) responded with XML content:<br>' + self.server.grade_data['TC answer']
content = self._create_content(status_message)
self.send_response(200, content)
elif 'lti2_outcome' in self.path and self._send_lti2_outcome().status_code == 200:
status_message = 'LTI consumer (edX) responded with HTTP {}<br>'.format(
self.server.grade_data['status_code'])
content = self._create_content(status_message)
self.send_response(200, content)
elif 'lti2_delete' in self.path and self._send_lti2_delete().status_code == 200:
status_message = 'LTI consumer (edX) responded with HTTP {}<br>'.format(
self.server.grade_data['status_code'])
content = self._create_content(status_message)
self.send_response(200, content)
# Respond to request with correct lti endpoint
elif self._is_correct_lti_request():
params = {k: v for k, v in self.post_dict.items() if k != 'oauth_signature'}
@@ -57,7 +66,7 @@ class StubLtiHandler(StubHttpRequestHandler):
# Set data for grades what need to be stored as server data
if 'lis_outcome_service_url' in self.post_dict:
self.server.grade_data = {
'callback_url': self.post_dict.get('lis_outcome_service_url'),
'callback_url': self.post_dict.get('lis_outcome_service_url').replace('https', 'http'),
'sourcedId': self.post_dict.get('lis_result_sourcedid')
}
@@ -122,16 +131,75 @@ class StubLtiHandler(StubHttpRequestHandler):
self.server.grade_data['TC answer'] = response.content
return response
def _send_lti2_outcome(self):
"""
Send a grade back to consumer
"""
payload = textwrap.dedent("""
{{
"@context" : "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type" : "Result",
"resultScore" : {score},
"comment" : "This is awesome."
}}
""")
data = payload.format(score=0.8)
return self._send_lti2(data)
def _send_lti2_delete(self):
"""
Send a delete back to consumer
"""
payload = textwrap.dedent("""
{
"@context" : "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type" : "Result"
}
""")
return self._send_lti2(payload)
def _send_lti2(self, payload):
"""
Send lti2 json result service request.
"""
### We compute the LTI V2.0 service endpoint from the callback_url (which is set by the launch call)
url = self.server.grade_data['callback_url']
url_parts = url.split('/')
url_parts[-1] = "lti_2_0_result_rest_handler"
anon_id = self.server.grade_data['sourcedId'].split(":")[-1]
url_parts.extend(["user", anon_id])
new_url = '/'.join(url_parts)
content_type = 'application/vnd.ims.lis.v2.result+json'
headers = {
'Content-Type': content_type,
'Authorization': self._oauth_sign(new_url, payload,
method='PUT',
content_type=content_type)
}
# Send request ignoring verifirecation of SSL certificate
response = requests.put(new_url, data=payload, headers=headers, verify=False)
self.server.grade_data['status_code'] = response.status_code
self.server.grade_data['TC answer'] = response.content
return response
def _create_content(self, response_text, submit_url=None):
"""
Return content (str) either for launch, send grade or get result from TC.
"""
if submit_url:
submit_form = textwrap.dedent("""
<form action="{}/grade" method="post">
<form action="{submit_url}/grade" method="post">
<input type="submit" name="submit-button" value="Submit">
</form>
""").format(submit_url)
<form action="{submit_url}/lti2_outcome" method="post">
<input type="submit" name="submit-lti2-button" value="Submit">
</form>
<form action="{submit_url}/lti2_delete" method="post">
<input type="submit" name="submit-lti2-delete-button" value="Submit">
</form>
""").format(submit_url=submit_url)
else:
submit_form = ''
@@ -169,9 +237,9 @@ class StubLtiHandler(StubHttpRequestHandler):
lti_endpoint = self.server.config.get('lti_endpoint', self.DEFAULT_LTI_ENDPOINT)
return lti_endpoint in self.path
def _oauth_sign(self, url, body):
def _oauth_sign(self, url, body, content_type=u'application/x-www-form-urlencoded', method=u'POST'):
"""
Signs request and returns signed body and headers.
Signs request and returns signed Authorization header.
"""
client_key = self.server.config.get('client_key', self.DEFAULT_CLIENT_KEY)
client_secret = self.server.config.get('client_secret', self.DEFAULT_CLIENT_SECRET)
@@ -181,21 +249,27 @@ class StubLtiHandler(StubHttpRequestHandler):
)
headers = {
# This is needed for body encoding:
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Type': content_type,
}
# Calculate and encode body hash. See http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
sha1 = hashlib.sha1()
sha1.update(body)
oauth_body_hash = base64.b64encode(sha1.digest())
__, headers, __ = client.sign(
unicode(url.strip()),
http_method=u'POST',
body={u'oauth_body_hash': oauth_body_hash},
headers=headers
oauth_body_hash = unicode(base64.b64encode(sha1.digest())) # pylint: disable=too-many-function-args
params = client.get_oauth_params()
params.append((u'oauth_body_hash', oauth_body_hash))
mock_request = mock.Mock(
uri=unicode(urllib.unquote(url)),
headers=headers,
body=u"",
decoded_body=u"",
oauth_params=params,
http_method=unicode(method),
)
headers = headers['Authorization'] + ', oauth_body_hash="{}"'.format(oauth_body_hash)
return headers
sig = client.get_oauth_signature(mock_request)
mock_request.oauth_params.append((u'oauth_signature', sig))
new_headers = parameters.prepare_headers(mock_request.oauth_params, headers, realm=None)
return new_headers['Authorization']
def _check_oauth_signature(self, params, client_signature):
"""

View File

@@ -62,7 +62,7 @@ class StubLtiServiceTest(unittest.TestCase):
self.assertIn('This is LTI tool. Success.', response.content)
@patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True)
def test_send_graded_result(self, verify_hmac):
def test_send_graded_result(self, verify_hmac): # pylint: disable=unused-argument
response = requests.post(self.launch_uri, data=self.payload)
self.assertIn('This is LTI tool. Success.', response.content)
grade_uri = self.uri + 'grade'
@@ -70,3 +70,23 @@ class StubLtiServiceTest(unittest.TestCase):
mocked_post.return_value = Mock(content='Test response', status_code=200)
response = urllib2.urlopen(grade_uri, data='')
self.assertIn('Test response', response.read())
@patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True)
def test_lti20_outcomes_put(self, verify_hmac): # pylint: disable=unused-argument
response = requests.post(self.launch_uri, data=self.payload)
self.assertIn('This is LTI tool. Success.', response.content)
grade_uri = self.uri + 'lti2_outcome'
with patch('terrain.stubs.lti.requests.put') as mocked_put:
mocked_put.return_value = Mock(status_code=200)
response = urllib2.urlopen(grade_uri, data='')
self.assertIn('LTI consumer (edX) responded with HTTP 200', response.read())
@patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True)
def test_lti20_outcomes_put_like_delete(self, verify_hmac): # pylint: disable=unused-argument
response = requests.post(self.launch_uri, data=self.payload)
self.assertIn('This is LTI tool. Success.', response.content)
grade_uri = self.uri + 'lti2_delete'
with patch('terrain.stubs.lti.requests.put') as mocked_put:
mocked_put.return_value = Mock(status_code=200)
response = urllib2.urlopen(grade_uri, data='')
self.assertIn('LTI consumer (edX) responded with HTTP 200', response.read())

View File

@@ -350,7 +350,7 @@ def redirect_to_supplementary_form(strategy, details, response, uid, is_dashboar
user_inactive = user and not user.is_active
user_unset = user is None
dispatch_to_login = (is_login and user_unset) or user_inactive
dispatch_to_login = is_login and (user_unset or user_inactive)
if is_dashboard:
return

View File

@@ -640,21 +640,17 @@ class IntegrationTest(testutil.TestCase, test.TestCase):
created_user = self.get_user_by_email(strategy, email)
self.assert_password_overridden_by_pipeline(overridden_password, created_user.username)
# The user's account isn't created yet, so an attempt to complete the
# pipeline will error out on /login:
self.assert_redirect_to_login_looks_correct(
actions.do_complete(strategy, social_views._do_login, user=created_user))
# So we activate the account in order to verify the redirect to /dashboard:
created_user.is_active = True
created_user.save()
# At this point the user object exists, but there is no associated
# social auth.
self.assert_social_auth_does_not_exist_for_user(created_user, strategy)
# Last step in the pipeline: we re-invoke the pipeline and expect to
# end up on /dashboard, with the correct social auth object now in the
# backend and the correct user's data on display.
# Pick the pipeline back up. This will create the account association
# and send the user to the dashboard, where the association will be
# displayed.
self.assert_redirect_to_dashboard_looks_correct(
actions.do_complete(strategy, social_views._do_login, user=created_user))
self.assert_social_auth_exists_for_user(created_user, strategy)
self.assert_dashboard_response_looks_correct(student_views.dashboard(request), created_user)
self.assert_dashboard_response_looks_correct(student_views.dashboard(request), created_user, linked=True)
def test_new_account_registration_assigns_distinct_username_on_collision(self):
original_username = self.get_username()

View File

@@ -12,6 +12,12 @@ from eventtracking import tracker
log = logging.getLogger(__name__)
CONTEXT_NAME = 'edx.request'
META_KEY_TO_CONTEXT_KEY = {
'REMOTE_ADDR': 'ip',
'SERVER_NAME': 'host',
'HTTP_USER_AGENT': 'agent',
'PATH_INFO': 'path'
}
class TrackMiddleware(object):
@@ -78,26 +84,58 @@ class TrackMiddleware(object):
"""
Extract information from the request and add it to the tracking
context.
The following fields are injected in to the context:
* session - The Django session key that identifies the user's session.
* user_id - The numeric ID for the logged in user.
* username - The username of the logged in user.
* ip - The IP address of the client.
* host - The "SERVER_NAME" header, which should be the name of the server running this code.
* agent - The client browser identification string.
* path - The path part of the requested URL.
"""
context = {}
context = {
'session': self.get_session_key(request),
'user_id': self.get_user_primary_key(request),
'username': self.get_username(request),
}
for header_name, context_key in META_KEY_TO_CONTEXT_KEY.iteritems():
context[context_key] = request.META.get(header_name, '')
context.update(contexts.course_context_from_url(request.build_absolute_uri()))
try:
context['user_id'] = request.user.pk
except AttributeError:
context['user_id'] = ''
if settings.DEBUG:
log.error('Cannot determine primary key of logged in user.')
tracker.get_tracker().enter_context(
CONTEXT_NAME,
context
)
def process_response(self, request, response): # pylint: disable=unused-argument
def get_session_key(self, request):
"""Gets the Django session key from the request or an empty string if it isn't found"""
try:
return request.session.session_key
except AttributeError:
return ''
def get_user_primary_key(self, request):
"""Gets the primary key of the logged in Django user"""
try:
return request.user.pk
except AttributeError:
return ''
def get_username(self, request):
"""Gets the username of the logged in Django user"""
try:
return request.user.username
except AttributeError:
return ''
def process_response(self, _request, response):
"""Exit the context if it exists."""
try:
tracker.get_tracker().exit_context(CONTEXT_NAME)
except: # pylint: disable=bare-except
except Exception: # pylint: disable=broad-except
pass
return response

View File

@@ -0,0 +1,42 @@
"""Map new event context values to old top-level field values. Ensures events can be parsed by legacy parsers."""
CONTEXT_FIELDS_TO_INCLUDE = [
'username',
'session',
'ip',
'agent',
'host'
]
class LegacyFieldMappingProcessor(object):
"""Ensures all required fields are included in emitted events"""
def __call__(self, event):
if 'context' in event:
context = event['context']
for field in CONTEXT_FIELDS_TO_INCLUDE:
if field in context:
event[field] = context[field]
del context[field]
else:
event[field] = ''
if 'event_type' in event.get('context', {}):
event['event_type'] = event['context']['event_type']
del event['context']['event_type']
else:
event['event_type'] = event.get('name', '')
if 'data' in event:
event['event'] = event['data']
del event['data']
else:
event['event'] = {}
if 'timestamp' in event:
event['time'] = event['timestamp']
del event['timestamp']
event['event_source'] = 'server'
event['page'] = None

View File

@@ -1,8 +1,10 @@
import re
from mock import patch
from mock import sentinel
from django.contrib.auth.models import User
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
@@ -50,35 +52,86 @@ class TrackMiddlewareTestCase(TestCase):
self.track_middleware.process_request(request)
self.assertFalse(self.mock_server_track.called)
def test_request_in_course_context(self):
request = self.request_factory.get('/courses/test_org/test_course/test_run/foo')
self.track_middleware.process_request(request)
captured_context = tracker.get_tracker().resolve_context()
self.track_middleware.process_response(request, None)
def test_default_request_context(self):
context = self.get_context_for_path('/courses/')
self.assertEquals(context, {
'user_id': '',
'session': '',
'username': '',
'ip': '127.0.0.1',
'host': 'testserver',
'agent': '',
'path': '/courses/',
'org_id': '',
'course_id': '',
})
def get_context_for_path(self, path):
"""Extract the generated event tracking context for a given request for the given path."""
request = self.request_factory.get(path)
return self.get_context_for_request(request)
def get_context_for_request(self, request):
"""Extract the generated event tracking context for the given request."""
self.track_middleware.process_request(request)
try:
captured_context = tracker.get_tracker().resolve_context()
finally:
self.track_middleware.process_response(request, None)
self.assertEquals(
captured_context,
{
'course_id': 'test_org/test_course/test_run',
'org_id': 'test_org',
'user_id': ''
}
)
self.assertEquals(
tracker.get_tracker().resolve_context(),
{}
)
return captured_context
def test_request_in_course_context(self):
captured_context = self.get_context_for_path('/courses/test_org/test_course/test_run/foo')
expected_context_subset = {
'course_id': 'test_org/test_course/test_run',
'org_id': 'test_org',
}
self.assert_dict_subset(captured_context, expected_context_subset)
def assert_dict_subset(self, superset, subset):
"""Assert that the superset dict contains all of the key-value pairs found in the subset dict."""
for key, expected_value in subset.iteritems():
self.assertEquals(superset[key], expected_value)
def test_request_with_user(self):
user_id = 1
username = sentinel.username
request = self.request_factory.get('/courses/')
request.user = User(pk=1)
self.track_middleware.process_request(request)
self.addCleanup(self.track_middleware.process_response, request, None)
self.assertEquals(
tracker.get_tracker().resolve_context(),
{
'course_id': '',
'org_id': '',
'user_id': 1
}
)
request.user = User(pk=user_id, username=username)
context = self.get_context_for_request(request)
self.assert_dict_subset(context, {
'user_id': user_id,
'username': username,
})
def test_request_with_session(self):
request = self.request_factory.get('/courses/')
SessionMiddleware().process_request(request)
request.session.save()
session_key = request.session.session_key
context = self.get_context_for_request(request)
self.assert_dict_subset(context, {
'session': session_key,
})
def test_request_headers(self):
ip_address = '10.0.0.0'
user_agent = 'UnitTest/1.0'
factory = RequestFactory(REMOTE_ADDR=ip_address, HTTP_USER_AGENT=user_agent)
request = factory.get('/some-path')
context = self.get_context_for_request(request)
self.assert_dict_subset(context, {
'ip': ip_address,
'agent': user_agent,
})

View File

@@ -0,0 +1,121 @@
"""Ensure emitted events contain the fields legacy processors expect to find."""
from datetime import datetime
from freezegun import freeze_time
from mock import sentinel
from django.test import TestCase
from django.test.utils import override_settings
from pytz import UTC
from eventtracking.django import DjangoTracker
IN_MEMORY_BACKEND = {
'mem': {
'ENGINE': 'track.tests.test_shim.InMemoryBackend'
}
}
LEGACY_SHIM_PROCESSOR = [
{
'ENGINE': 'track.shim.LegacyFieldMappingProcessor'
}
]
FROZEN_TIME = datetime(2013, 10, 3, 8, 24, 55, tzinfo=UTC)
@freeze_time(FROZEN_TIME)
class LegacyFieldMappingProcessorTestCase(TestCase):
"""Ensure emitted events contain the fields legacy processors expect to find."""
@override_settings(
EVENT_TRACKING_BACKENDS=IN_MEMORY_BACKEND,
EVENT_TRACKING_PROCESSORS=LEGACY_SHIM_PROCESSOR,
)
def test_event_field_mapping(self):
django_tracker = DjangoTracker()
data = {sentinel.key: sentinel.value}
context = {
'username': sentinel.username,
'session': sentinel.session,
'ip': sentinel.ip,
'host': sentinel.host,
'agent': sentinel.agent,
'path': sentinel.path,
'user_id': sentinel.user_id,
'course_id': sentinel.course_id,
'org_id': sentinel.org_id,
'event_type': sentinel.event_type,
}
with django_tracker.context('test', context):
django_tracker.emit(sentinel.name, data)
emitted_event = django_tracker.backends['mem'].get_event()
expected_event = {
'event_type': sentinel.event_type,
'name': sentinel.name,
'context': {
'user_id': sentinel.user_id,
'course_id': sentinel.course_id,
'org_id': sentinel.org_id,
'path': sentinel.path,
},
'event': data,
'username': sentinel.username,
'event_source': 'server',
'time': FROZEN_TIME,
'agent': sentinel.agent,
'host': sentinel.host,
'ip': sentinel.ip,
'page': None,
'session': sentinel.session,
}
self.assertEqual(expected_event, emitted_event)
@override_settings(
EVENT_TRACKING_BACKENDS=IN_MEMORY_BACKEND,
EVENT_TRACKING_PROCESSORS=LEGACY_SHIM_PROCESSOR,
)
def test_missing_fields(self):
django_tracker = DjangoTracker()
django_tracker.emit(sentinel.name)
emitted_event = django_tracker.backends['mem'].get_event()
expected_event = {
'event_type': sentinel.name,
'name': sentinel.name,
'context': {},
'event': {},
'username': '',
'event_source': 'server',
'time': FROZEN_TIME,
'agent': '',
'host': '',
'ip': '',
'page': None,
'session': '',
}
self.assertEqual(expected_event, emitted_event)
class InMemoryBackend(object):
"""A backend that simply stores all events in memory"""
def __init__(self):
super(InMemoryBackend, self).__init__()
self.events = []
def send(self, event):
"""Store the event in a list"""
self.events.append(event)
def get_event(self):
"""Return the first event that was emitted."""
return self.events[0]

View File

@@ -165,7 +165,7 @@ def add_staff_markup(user, block, view, frag, context): # pylint: disable=unuse
Does nothing if module is a SequenceModule.
"""
# TODO: make this more general, eg use an XModule attribute instead
if isinstance(block, VerticalModule):
if isinstance(block, VerticalModule) and (not context or not context.get('child_of_vertical', False)):
# check that the course is a mongo backed Studio course before doing work
is_mongo_course = modulestore().get_modulestore_type(block.course_id) == MONGO_MODULESTORE_TYPE
is_studio_course = block.course_edit_method == "Studio"

View File

@@ -62,7 +62,47 @@ log = logging.getLogger(__name__)
#########################################################################
registry = TagRegistry()
registry = TagRegistry() # pylint: disable=C0103
class Status(object):
"""
Problem status
attributes: classname, display_name
"""
css_classes = {
# status: css class
'unsubmitted': 'unanswered',
'incomplete': 'incorrect',
'queued': 'processing',
}
__slots__ = ('classname', '_status', 'display_name')
def __init__(self, status, gettext_func=unicode):
self.classname = self.css_classes.get(status, status)
_ = gettext_func
names = {
'correct': _('correct'),
'incorrect': _('incorrect'),
'incomplete': _('incomplete'),
'unanswered': _('unanswered'),
'unsubmitted': _('unanswered'),
'queued': _('processing'),
}
self.display_name = names.get(status, unicode(status))
self._status = status or ''
def __str__(self):
return self._status
def __unicode__(self):
return self._status.decode('utf8')
def __repr__(self):
return 'Status(%r)' % self._status
def __eq__(self, other):
return self._status == str(other)
class Attribute(object):
@@ -261,9 +301,7 @@ class InputTypeBase(object):
context = {
'id': self.input_id,
'value': self.value,
'status': self.status,
'status_class': self.status_class,
'status_display': self.status_display,
'status': Status(self.status, self.capa_system.i18n.ugettext),
'msg': self.msg,
'STATIC_URL': self.capa_system.STATIC_URL,
}
@@ -273,34 +311,6 @@ class InputTypeBase(object):
context.update(self._extra_context())
return context
@property
def status_class(self):
"""
Return the CSS class for the associated status.
"""
statuses = {
'unsubmitted': 'unanswered',
'incomplete': 'incorrect',
'queued': 'processing',
}
return statuses.get(self.status, self.status)
@property
def status_display(self):
"""
Return the human-readable and translated word for the associated status.
"""
_ = self.capa_system.i18n.ugettext
statuses = {
'correct': _('correct'),
'incorrect': _('incorrect'),
'incomplete': _('incomplete'),
'unanswered': _('unanswered'),
'unsubmitted': _('unanswered'),
'queued': _('queued'),
}
return statuses.get(self.status, self.status)
def _extra_context(self):
"""
Subclasses can override this to return extra context that should be passed to their templates for rendering.

View File

@@ -52,13 +52,7 @@
<input type="hidden" class="value" name="input_${id}" id="input_${id}" value="${value|h}" />
% endif
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: Unanswered</span></span>
% elif status == 'incomplete':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: Incorrect</span></span>
% elif status == 'incorrect' and not has_options_value:
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: Incorrect</span></span>
% endif
<span class="status ${status.classname}" id="status_${id}" aria-describedby="input_${id}"><span class="sr">${status.display_name}</span></span>
<p id="answer_${id}" class="answer answer-annotation"></p>
</div>

View File

@@ -1,7 +1,7 @@
<div id="chemicalequationinput_${id}" class="chemicalequationinput">
<div class="script_placeholder" data-src="${previewer}"/>
<div class="${status_class}" id="status_${id}">
<div class="${status.classname}" id="status_${id}">
<input type="text" name="input_${id}" id="input_${id}" aria-label="${label}" aria-describedby="answer_${id}" data-input-id="${id}" value="${value|h}"
% if size:
@@ -11,7 +11,7 @@
<p class="status" aria-describedby="input_${id}">
${value|h} -
${status_display}
${status.display_name}
</p>
<div id="input_${id}_preview" class="equation"></div>

View File

@@ -1,7 +1,7 @@
<form class="choicegroup capa_inputtype" id="inputtype_${id}">
<div class="indicator_container">
% if input_type == 'checkbox' or not value:
<span class="status ${status_class if show_correctness != 'never' else 'unanswered'}"
<span class="status ${status.classname if show_correctness != 'never' else 'unanswered'}"
id="status_${id}"
aria-describedby="inputtype_${id}">
<span class="sr">
@@ -11,7 +11,7 @@
%endif
%endfor
-
${status_display}
${status.display_name}
</span>
</span>
% endif
@@ -51,7 +51,7 @@
% if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ):
% if status in ('correct', 'incorrect') and not show_correctness=='never':
<span class="sr status">${choice_description|h} - ${status_display}</span>
<span class="sr status">${choice_description|h} - ${status.display_name}</span>
% endif
% endif
</label>

View File

@@ -10,7 +10,7 @@
<div class="script_placeholder" data-src="/static/js/capa/choicetextinput.js"/>
<div class="indicator_container">
% if input_type == 'checkbox' or not element_checked:
<span class="status ${status_class}" id="status_${id}"></span>
<span class="status ${status.classname}" id="status_${id}"></span>
% endif
</div>

View File

@@ -17,10 +17,10 @@
<div class="grader-status" tabindex="-1">
<span id="status_${id}"
class="${status_class}"
class="${status.classname}"
aria-describedby="input_${id}"
>
<span class="status sr">${status_display}</span>
<span class="status sr">${status.display_name}</span>
</span>
% if status == 'queued':
<span style="display:none;" class="xqueue" id="${id}">${queue_len}</span>
@@ -30,7 +30,7 @@
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<p class="debug">${status_display}</p>
<p class="debug">${status.display_name}</p>
</div>
<span id="answer_${id}"></span>

View File

@@ -9,29 +9,14 @@
<div class="script_placeholder" data-src="/static/js/sylvester.js"></div>
<div class="script_placeholder" data-src="/static/js/crystallography.js"></div>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="status ${status.classname}" id="status_${id}">
<input type="text" name="input_${id}" aria-describedby="answer_${id}" id="input_${id}" value="${value|h}" style="display:none;"/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<p id="answer_${id}" class="answer"></p>

View File

@@ -2,14 +2,8 @@
<div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js?raw"/>
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incomplete" id="status_${id}">
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
<div id="protex_container"></div>
@@ -17,15 +11,7 @@
<input type="hidden" name="input_${id}" id="input_${id}" aria-describedby="answer_${id}" value="${value|h}"/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<p id="answer_${id}" class="answer"></p>

View File

@@ -8,14 +8,8 @@
<div class="script_placeholder" data-src="${STATIC_URL}js/capa/drag_and_drop.js"></div>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
@@ -23,15 +17,7 @@
style="display:none;"/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<p id="answer_${id}" class="answer"></p>

View File

@@ -2,14 +2,8 @@
<div class="script_placeholder" data-src="/static/js/capa/genex/genex.nocache.js?raw"/>
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incomplete" id="status_${id}">
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
<div id="genex_container"></div>
@@ -18,15 +12,7 @@
<input type="hidden" name="input_${id}" aria-describedby="answer_${id}" id="input_${id}" value="${value|h}"/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<p id="answer_${id}" class="answer"></p>

View File

@@ -1,15 +1,9 @@
<section id="editamoleculeinput_${id}" class="editamoleculeinput">
<div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
<div id="applet_${id}" class="applet" data-molfile-src="${file}" style="display:block;width:500px;height:400px">
</div>
@@ -23,15 +17,7 @@
<p id="answer_${id}" class="answer"></p>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<br/> <br/>

View File

@@ -1,14 +1,9 @@
<section id="filesubmission_${id}" class="filesubmission">
<div class="grader-status file">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif status == 'correct':
<span class="correct" id="status_${id}">Correct</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}">Incorrect</span>
% elif status == 'queued':
<span class="processing" id="status_${id}">Queued</span>
<span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
<span class="${status.classname}" id="status_${id}">${status.display_name}</span>
% if status == 'queued':
<span style="display:none;" class="xqueue" id="${id}">${queue_len}</span>
% endif
<p class="debug">${status}</p>

View File

@@ -1,6 +1,6 @@
<% doinline = 'style="display:inline-block;vertical-align:top"' if inline else "" %>
<section id="formulaequationinput_${id}" class="inputtype formulaequationinput" ${doinline}>
<div class="${status_class}" id="status_${id}">
<div class="${status.classname}" id="status_${id}">
<input type="text" name="input_${id}" id="input_${id}"
data-input-id="${id}" value="${value|h}"
aria-label="${label}"
@@ -20,7 +20,7 @@
% else:
${label}
%endif
</span> - ${status_display}
</span> - ${status.display_name}
</p>
<div id="input_${id}_preview" class="equation">

View File

@@ -39,38 +39,11 @@
(new ImageInput('${id}'));
</script>
% if status == 'unsubmitted':
<span
class="unanswered"
style="display: inline-block;"
class="status ${status.classname}"
id="status_${id}"
aria-describedby="input_${id}"
>
<span class="sr">Status: unanswered</span>
<span class="sr">${status.display_name}</span>
</span>
% elif status == 'correct':
<span
class="correct"
id="status_${id}"
aria-describedby="input_${id}"
>
<span class="sr">Status: correct</span>
</span>
% elif status == 'incorrect':
<span
class="incorrect"
id="status_${id}"
aria-describedby="input_${id}"
>
<span class="sr">Status: incorrect</span>
</span>
% elif status == 'incomplete':
<span
class="incorrect"
id="status_${id}"
aria-describedby="input_${id}"
>
<span class="sr">Status: incorrect</span>
</span>
% endif
</div>

View File

@@ -17,14 +17,8 @@
<div class="script_placeholder" data-src="${jschannel_loader}"/>
<div class="script_placeholder" data-src="${jsinput_loader}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
<iframe name="iframe_${id}"
@@ -44,15 +38,7 @@
<p id="answer_${id}" class="answer"></p>
<p class="status">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<br/> <br/>

View File

@@ -18,23 +18,9 @@
<textarea style="display:none" id="input_${id}_fromjs" name="input_${id}_fromjs"></textarea>
% endif
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: unanswered</span>
<span class="status ${status.classname}" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">${status.display_name</span>
</span>
% elif status == 'correct':
<span class="correct" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: correct</span>
</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incorrect</span>
</span>
% elif status == 'incomplete':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incorrect</span>
</span>
% endif
% if msg:
<br/>
<span class="debug">${msg|n}</span>

View File

@@ -18,10 +18,10 @@
<div class="grader-status" tabindex="-1">
<span id="status_${id}"
class="${status_class}"
class="${status.classname}"
aria-describedby="input_${id}"
>
<span class="status sr">${status_display}</span>
<span class="status sr">${status.display_name}</span>
</span>
% if status == 'queued':
<span style="display:none;" class="xqueue" id="${id}">${queue_len}</span>
@@ -31,7 +31,7 @@
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<p class="debug">${status_display}</p>
<p class="debug">${status.display_name}</p>
</div>
<span id="answer_${id}"></span>

View File

@@ -13,10 +13,10 @@
</select>
<span id="answer_${id}"></span>
<span class="status ${status_class}"
<span class="status ${status.classname}"
id="status_${id}"
aria-describedby="input_${id}">
<span class="sr">${value|h} - ${status_display}</span>
<span class="sr">${value|h} - ${status.display_name}</span>
</span>
% if msg:

View File

@@ -1,4 +1,4 @@
<span>
<div>
<div class="script_placeholder" data-src="${setup_script}"/>
<input type="hidden"
class="schematic"
@@ -16,23 +16,7 @@
/>
<span id="answer_${id}"></span>
% if status == 'unsubmitted':
<span class="ui-icon ui-icon-bullet" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: unsubmitted</span>
<span class="status ${status.classname}" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">${status.display_name}</span>
</span>
% elif status == 'correct':
<span class="ui-icon ui-icon-check" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: correct</span>
</span>
% elif status == 'incorrect':
<span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incorrect</span>
</span>
% elif status == 'incomplete':
<span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incomplete</span>
</span>
% endif
</span>
</div>

View File

@@ -8,7 +8,7 @@
% endif
% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'):
<div class="${status_class} ${doinline}" id="status_${id}">
<div class="${status.classname} ${doinline}" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
@@ -38,7 +38,7 @@
${label}
%endif
-
${status_display}
${status.display_name}
</p>
<p id="answer_${id}" class="answer" aria-hidden="true"></p>

View File

@@ -11,14 +11,8 @@
<div class="script_placeholder" data-src="/static/js/vsepr/vsepr.js"></div>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="${status.classname}" id="status_${id}">
% endif
<input type="text" name="input_${id}" id="input_${id}" aria-describedby="answer_${id}" value="${value|h}"
@@ -26,15 +20,7 @@
/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${status.display_name}
</p>
<p id="answer_${id}" class="answer"></p>

View File

@@ -7,6 +7,7 @@ import os.path
import fs.osfs
from capa.capa_problem import LoncapaProblem, LoncapaSystem
from capa.inputtypes import Status
from mock import Mock, MagicMock
import xml.sax.saxutils as saxutils
@@ -47,6 +48,7 @@ def test_capa_system():
render_template=tst_render_template,
seed=0,
STATIC_URL='/dummy-static/',
STATUS_CLASS=Status,
xqueue={'interface': xqueue_interface, 'construct_callback': calledback_url, 'default_queuename': 'testqueue', 'waittime': 10},
)
return the_system

View File

@@ -153,9 +153,7 @@ class CapaHtmlRenderTest(unittest.TestCase):
# the solution
expected_textline_context = {
'STATIC_URL': '/dummy-static/',
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': the_system.STATUS_CLASS('unsubmitted'),
'label': '',
'value': '',
'preprocessor': None,

View File

@@ -9,7 +9,7 @@ import json
from lxml import etree
from mako.template import Template as MakoTemplate
from mako import exceptions
from capa.inputtypes import Status
class TemplateError(Exception):
"""
@@ -123,9 +123,7 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
choices = [('1', 'choice 1'), ('2', 'choice 2'), ('3', 'choice 3')]
self.context = {'id': '1',
'choices': choices,
'status': 'correct',
'status_class': 'correct',
'status_display': u'correct',
'status': Status('correct'),
'label': 'test',
'input_type': 'checkbox',
'name_array_suffix': '1',
@@ -138,7 +136,7 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
(not a particular option) is marked correct.
"""
self.context['status'] = self.context['status_class'] = self.context['status_display'] = 'correct'
self.context['status'] = Status('correct')
self.context['input_type'] = 'checkbox'
self.context['value'] = ['1', '2']
@@ -160,14 +158,14 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
(not a particular option) is marked incorrect.
"""
conditions = [
{'status': 'incorrect', 'input_type': 'radio', 'value': '', 'status_class': 'incorrect'},
{'status': 'incorrect', 'input_type': 'checkbox', 'value': [], 'status_class': 'incorrect'},
{'status': 'incorrect', 'input_type': 'checkbox', 'value': ['2'], 'status_class': 'incorrect'},
{'status': 'incorrect', 'input_type': 'checkbox', 'value': ['2', '3'], 'status_class': 'incorrect'},
{'status': 'incomplete', 'input_type': 'radio', 'value': '', 'status_class': 'incorrect'},
{'status': 'incomplete', 'input_type': 'checkbox', 'value': [], 'status_class': 'incorrect'},
{'status': 'incomplete', 'input_type': 'checkbox', 'value': ['2'], 'status_class': 'incorrect'},
{'status': 'incomplete', 'input_type': 'checkbox', 'value': ['2', '3'], 'status_class': 'incorrect'}]
{'status': Status('incorrect'), 'input_type': 'radio', 'value': ''},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': []},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': ['2']},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': ['2', '3']},
{'status': Status('incomplete'), 'input_type': 'radio', 'value': ''},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': []},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': ['2']},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': ['2', '3']}]
for test_conditions in conditions:
self.context.update(test_conditions)
@@ -190,16 +188,16 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
(not a particular option) is marked unanswered.
"""
conditions = [
{'status': 'unsubmitted', 'input_type': 'radio', 'value': '', 'status_class': 'unanswered'},
{'status': 'unsubmitted', 'input_type': 'radio', 'value': [], 'status_class': 'unanswered'},
{'status': 'unsubmitted', 'input_type': 'checkbox', 'value': [], 'status_class': 'unanswered'},
{'status': Status('unsubmitted'), 'input_type': 'radio', 'value': ''},
{'status': Status('unsubmitted'), 'input_type': 'radio', 'value': []},
{'status': Status('unsubmitted'), 'input_type': 'checkbox', 'value': []},
{'input_type': 'radio', 'value': ''},
{'input_type': 'radio', 'value': []},
{'input_type': 'checkbox', 'value': []},
{'input_type': 'checkbox', 'value': ['1']},
{'input_type': 'checkbox', 'value': ['1', '2']}]
self.context['status'] = self.context['status_class'] = 'unanswered'
self.context['status'] = Status('unanswered')
for test_conditions in conditions:
self.context.update(test_conditions)
@@ -225,7 +223,7 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
{'input_type': 'radio', 'value': '2'},
{'input_type': 'radio', 'value': ['2']}]
self.context['status'] = 'correct'
self.context['status'] = Status('correct')
for test_conditions in conditions:
self.context.update(test_conditions)
@@ -246,7 +244,7 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
{'input_type': 'radio', 'value': '2'},
{'input_type': 'radio', 'value': ['2']}]
self.context['status'] = self.context['status_class'] = 'incorrect'
self.context['status'] = Status('incorrect')
for test_conditions in conditions:
self.context.update(test_conditions)
@@ -270,16 +268,16 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
"""
conditions = [
{'input_type': 'radio', 'status': 'correct', 'value': '', 'status_class': 'correct'},
{'input_type': 'radio', 'status': 'correct', 'value': '2', 'status_class': 'correct'},
{'input_type': 'radio', 'status': 'correct', 'value': ['2'], 'status_class': 'correct'},
{'input_type': 'radio', 'status': 'incorrect', 'value': '2', 'status_class': 'incorrect'},
{'input_type': 'radio', 'status': 'incorrect', 'value': [], 'status_class': 'incorrect'},
{'input_type': 'radio', 'status': 'incorrect', 'value': ['2'], 'status_class': 'incorrect'},
{'input_type': 'checkbox', 'status': 'correct', 'value': [], 'status_class': 'correct'},
{'input_type': 'checkbox', 'status': 'correct', 'value': ['2'], 'status_class': 'correct'},
{'input_type': 'checkbox', 'status': 'incorrect', 'value': [], 'status_class': 'incorrect'},
{'input_type': 'checkbox', 'status': 'incorrect', 'value': ['2'], 'status_class': 'incorrect'}]
{'input_type': 'radio', 'status': Status('correct'), 'value': ''},
{'input_type': 'radio', 'status': Status('correct'), 'value': '2'},
{'input_type': 'radio', 'status': Status('correct'), 'value': ['2']},
{'input_type': 'radio', 'status': Status('incorrect'), 'value': '2'},
{'input_type': 'radio', 'status': Status('incorrect'), 'value': []},
{'input_type': 'radio', 'status': Status('incorrect'), 'value': ['2']},
{'input_type': 'checkbox', 'status': Status('correct'), 'value': []},
{'input_type': 'checkbox', 'status': Status('correct'), 'value': ['2']},
{'input_type': 'checkbox', 'status': Status('incorrect'), 'value': []},
{'input_type': 'checkbox', 'status': Status('incorrect'), 'value': ['2']}]
self.context['show_correctness'] = 'never'
self.context['submitted_message'] = 'Test message'
@@ -315,9 +313,9 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
"""
conditions = [
{'input_type': 'radio', 'status': 'unsubmitted', 'value': ''},
{'input_type': 'radio', 'status': 'unsubmitted', 'value': []},
{'input_type': 'checkbox', 'status': 'unsubmitted', 'value': []},
{'input_type': 'radio', 'status': Status('unsubmitted'), 'value': ''},
{'input_type': 'radio', 'status': Status('unsubmitted'), 'value': []},
{'input_type': 'checkbox', 'status': Status('unsubmitted'), 'value': []},
# These tests expose bug #365
# When the bug is fixed, uncomment these cases.
@@ -354,9 +352,7 @@ class TextlineTemplateTest(TemplateTestCase):
def setUp(self):
self.context = {'id': '1',
'status': 'correct',
'status_class': 'correct',
'status_display': u'correct',
'status': Status('correct'),
'label': 'test',
'value': '3',
'preprocessor': None,
@@ -383,9 +379,7 @@ class TextlineTemplateTest(TemplateTestCase):
('incomplete', 'incorrect', 'incomplete')]
for (context_status, div_class, status_mark) in cases:
self.context['status'] = context_status
self.context['status_class'] = div_class
self.context['status_display'] = status_mark
self.context['status'] = Status(context_status)
xml = self.render_to_xml(self.context)
# Expect that we get a <div> with correct class
@@ -461,8 +455,7 @@ class TextlineTemplateTest(TemplateTestCase):
self.context['inline'] = True
for (context_status, div_class) in cases:
self.context['status'] = context_status
self.context['status_class'] = div_class
self.context['status'] = Status(context_status)
xml = self.render_to_xml(self.context)
# Expect that we get a <div> with correct class
@@ -487,9 +480,7 @@ class FormulaEquationInputTemplateTest(TemplateTestCase):
self.context = {
'id': 2,
'value': 'PREFILLED_VALUE',
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unsubmitted',
'status': Status('unsubmitted'),
'label': 'test',
'previewer': 'file.js',
'reported_status': 'REPORTED_STATUS',
@@ -525,9 +516,7 @@ class AnnotationInputTemplateTest(TemplateTestCase):
'options': [],
'has_options_value': False,
'debug': False,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unsubmitted',
'status': Status('unsubmitted'),
'return_to_annotation': False,
'msg': '<p>This is a test message</p>', }
super(AnnotationInputTemplateTest, self).setUp()
@@ -587,16 +576,16 @@ class AnnotationInputTemplateTest(TemplateTestCase):
('incorrect', 'incorrect')]
for (input_status, expected_css_class) in test_cases:
self.context['status'] = input_status
self.context['status'] = Status(input_status)
xml = self.render_to_xml(self.context)
xpath = "//span[@class='{0}']".format(expected_css_class)
xpath = "//span[@class='status {0}']".format(expected_css_class)
self.assert_has_xpath(xml, xpath, self.context)
# If individual options are being marked, then expect
# just the option to be marked incorrect, not the whole problem
self.context['has_options_value'] = True
self.context['status'] = 'incorrect'
self.context['status'] = Status('incorrect')
xpath = "//span[@class='incorrect']"
xml = self.render_to_xml(self.context)
self.assert_no_xpath(xml, xpath, self.context)
@@ -687,9 +676,7 @@ class OptionInputTemplateTest(TemplateTestCase):
self.context = {
'id': 2,
'options': [],
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': Status('unsubmitted'),
'label': 'test',
'value': 0
}
@@ -729,8 +716,7 @@ class OptionInputTemplateTest(TemplateTestCase):
('incomplete', 'status incorrect')]
for (input_status, expected_css_class) in test_cases:
self.context['status'] = input_status
self.context['status_class'] = expected_css_class.split(' ')[1]
self.context['status'] = Status(input_status)
xml = self.render_to_xml(self.context)
xpath = "//span[@class='{0}']".format(expected_css_class)
@@ -753,7 +739,7 @@ class DragAndDropTemplateTest(TemplateTestCase):
self.context = {'id': 2,
'drag_and_drop_json': '',
'value': 0,
'status': 'unsubmitted',
'status': Status('unsubmitted'),
'msg': ''}
super(DragAndDropTemplateTest, self).setUp()
@@ -767,7 +753,7 @@ class DragAndDropTemplateTest(TemplateTestCase):
('incomplete', 'incorrect', 'incomplete')]
for (input_status, expected_css_class, expected_text) in test_cases:
self.context['status'] = input_status
self.context['status'] = Status(input_status)
xml = self.render_to_xml(self.context)
# Expect a <div> with the status
@@ -814,9 +800,7 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
{'tail_text': '', 'type': 'textinput', 'value': '', 'contents': 'choiceinput_1_textinput_0'}])]
self.context = {'id': '1',
'choices': choices,
'status': 'correct',
'status_class': 'correct',
'status_display': u'correct',
'status': Status('correct'),
'input_type': 'radio',
'label': 'choicetext label',
'value': self.VALUE_DICT}
@@ -829,7 +813,7 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
Section is used for checkbox, so inputting text does not deselect
"""
input_tags = ('radio', 'checkbox')
self.context['status'] = 'correct'
self.context['status'] = Status('correct')
xpath = "//section[@id='forinput1_choiceinput_0bc']"
self.context['value'] = {}
@@ -842,7 +826,7 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
"""Test conditions under which the entire problem
(not a particular option) is marked correct"""
self.context['status'] = 'correct'
self.context['status'] = Status('correct')
self.context['input_type'] = 'checkbox'
self.context['value'] = self.VALUE_DICT
@@ -863,14 +847,14 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
(not a particular option) is marked incorrect"""
grouping_tags = {'radio': 'label', 'checkbox': 'section'}
conditions = [
{'status': 'incorrect', 'status_class': 'incorrect', 'input_type': 'radio', 'value': {}},
{'status': 'incorrect', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.WRONG_CHOICE_CHECKBOX},
{'status': 'incorrect', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX},
{'status': 'incorrect', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.VALUE_DICT},
{'status': 'incomplete', 'status_class': 'incorrect', 'input_type': 'radio', 'value': {}},
{'status': 'incomplete', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.WRONG_CHOICE_CHECKBOX},
{'status': 'incomplete', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX},
{'status': 'incomplete', 'status_class': 'incorrect', 'input_type': 'checkbox', 'value': self.VALUE_DICT}]
{'status': Status('incorrect'), 'input_type': 'radio', 'value': {}},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': self.WRONG_CHOICE_CHECKBOX},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX},
{'status': Status('incorrect'), 'input_type': 'checkbox', 'value': self.VALUE_DICT},
{'status': Status('incomplete'), 'input_type': 'radio', 'value': {}},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': self.WRONG_CHOICE_CHECKBOX},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX},
{'status': Status('incomplete'), 'input_type': 'checkbox', 'value': self.VALUE_DICT}]
for test_conditions in conditions:
self.context.update(test_conditions)
@@ -894,15 +878,14 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
grouping_tags = {'radio': 'label', 'checkbox': 'section'}
conditions = [
{'status': 'unsubmitted', 'input_type': 'radio', 'value': {}},
{'status': 'unsubmitted', 'input_type': 'radio', 'value': self.EMPTY_DICT},
{'status': 'unsubmitted', 'input_type': 'checkbox', 'value': {}},
{'status': 'unsubmitted', 'input_type': 'checkbox', 'value': self.EMPTY_DICT},
{'status': 'unsubmitted', 'input_type': 'checkbox', 'value': self.VALUE_DICT},
{'status': 'unsubmitted', 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX}]
{'status': Status('unsubmitted'), 'input_type': 'radio', 'value': {}},
{'status': Status('unsubmitted'), 'input_type': 'radio', 'value': self.EMPTY_DICT},
{'status': Status('unsubmitted'), 'input_type': 'checkbox', 'value': {}},
{'status': Status('unsubmitted'), 'input_type': 'checkbox', 'value': self.EMPTY_DICT},
{'status': Status('unsubmitted'), 'input_type': 'checkbox', 'value': self.VALUE_DICT},
{'status': Status('unsubmitted'), 'input_type': 'checkbox', 'value': self.BOTH_CHOICE_CHECKBOX}]
self.context['status'] = 'unanswered'
self.context['status_class'] = 'unanswered'
self.context['status'] = Status('unanswered')
for test_conditions in conditions:
self.context.update(test_conditions)

View File

@@ -58,9 +58,7 @@ class OptionInputTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'value': 'Down',
'options': [('Up', 'Up'), ('Down', 'Down'), ('Don\'t know', 'Don\'t know')],
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
'status': inputtypes.Status('answered'),
'label': '',
'msg': '',
'inline': False,
@@ -120,9 +118,7 @@ class ChoiceGroupTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'sky_input',
'value': 'foil3',
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
'status': inputtypes.Status('answered'),
'label': '',
'msg': '',
'input_type': expected_input_type,
@@ -160,10 +156,10 @@ class JavascriptInputTest(unittest.TestCase):
display_file = "my_files/hi.js"
xml_str = """<javascriptinput id="prob_1_2" params="{params}" problem_state="{ps}"
display_class="{dc}" display_file="{df}"/>""".format(
params=params,
ps=quote_attr(problem_state),
dc=display_class, df=display_file)
display_class="{dc}" display_file="{df}"/>""".format(
params=params,
ps=quote_attr(problem_state),
dc=display_class, df=display_file)
element = etree.fromstring(xml_str)
@@ -175,9 +171,7 @@ class JavascriptInputTest(unittest.TestCase):
expected = {
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unanswered'),
# 'label': '',
'msg': '',
'value': '3',
@@ -210,9 +204,7 @@ class TextLineTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unanswered'),
'label': 'testing 123',
'size': size,
'msg': '',
@@ -244,9 +236,7 @@ class TextLineTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unanswered'),
'label': '',
'size': size,
'msg': '',
@@ -290,9 +280,7 @@ class TextLineTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unanswered'),
'label': '',
'size': size,
'msg': '',
@@ -333,9 +321,7 @@ class FileSubmissionTest(unittest.TestCase):
expected = {
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'status': inputtypes.Status('queued'),
'label': '',
'msg': the_input.submitted_msg,
'value': 'BumbleBee.py',
@@ -385,9 +371,7 @@ class CodeInputTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'status': inputtypes.Status('queued'),
# 'label': '',
'msg': the_input.submitted_msg,
'mode': mode,
@@ -441,9 +425,7 @@ class MatlabTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'status': inputtypes.Status('queued'),
# 'label': '',
'msg': self.the_input.submitted_msg,
'mode': self.mode,
@@ -474,9 +456,7 @@ class MatlabTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'status': inputtypes.Status('queued'),
# 'label': '',
'msg': the_input.submitted_msg,
'mode': self.mode,
@@ -507,9 +487,7 @@ class MatlabTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': status,
'status_class': status,
'status_display': unicode(status),
'status': inputtypes.Status(status),
# 'label': '',
'msg': '',
'mode': self.mode,
@@ -540,9 +518,7 @@ class MatlabTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'status': inputtypes.Status('queued'),
# 'label': '',
'msg': the_input.submitted_msg,
'mode': self.mode,
@@ -657,7 +633,7 @@ class MatlabTest(unittest.TestCase):
output = self.the_input.get_html()
self.assertEqual(
etree.tostring(output),
"""<div>{\'status\': \'queued\', \'button_enabled\': True, \'linenumbers\': \'true\', \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'cols\': \'80\', \'value\': \'print "good evening"\', \'status_class\': \'processing\', \'queue_msg\': \'\', \'STATIC_URL\': \'/dummy-static/\', \'msg\': u\'Submitted. As soon as a response is returned, this message will be replaced by that feedback.\', \'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/octave.js\', \'hidden\': \'\', \'status_display\': u\'queued\', \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
"""<div>{\'status\': Status(\'queued\'), \'button_enabled\': True, \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\', \'queue_msg\': \'\', \'value\': \'print "good evening"\', \'msg\': u\'Submitted. As soon as a response is returned, this message will be replaced by that feedback.\', \'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/octave.js\', \'hidden\': \'\', \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
)
# test html, that is correct HTML5 html, but is not parsable by XML parser.
@@ -775,9 +751,7 @@ class SchematicTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unsubmitted'),
'label': '',
'msg': '',
'initial_value': initial_value,
@@ -821,9 +795,7 @@ class ImageInputTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unsubmitted'),
'label': '',
'width': width,
'height': height,
@@ -878,9 +850,7 @@ class CrystallographyTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unsubmitted'),
# 'label': '',
'msg': '',
'width': width,
@@ -922,9 +892,7 @@ class VseprTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unsubmitted'),
'msg': '',
'width': width,
'height': height,
@@ -956,9 +924,7 @@ class ChemicalEquationTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'H2OYeah',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': 'unanswered',
'status': inputtypes.Status('unanswered'),
'label': '',
'msg': '',
'size': self.size,
@@ -1046,9 +1012,7 @@ class FormulaEquationTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': 'x^2+1/2',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unanswered'),
'label': '',
'msg': '',
'size': self.size,
@@ -1155,20 +1119,20 @@ class DragAndDropTest(unittest.TestCase):
"target_outline": "false",
"base_image": "/dummy-static/images/about_1.png",
"draggables": [
{"can_reuse": "", "label": "Label 1", "id": "1", "icon": "", "target_fields": []},
{"can_reuse": "", "label": "cc", "id": "name_with_icon", "icon": "/dummy-static/images/cc.jpg", "target_fields": []},
{"can_reuse": "", "label": "arrow-left", "id": "with_icon", "icon": "/dummy-static/images/arrow-left.png", "can_reuse": "", "target_fields": []},
{"can_reuse": "", "label": "Label2", "id": "5", "icon": "", "can_reuse": "", "target_fields": []},
{"can_reuse": "", "label": "Mute", "id": "2", "icon": "/dummy-static/images/mute.png", "can_reuse": "", "target_fields": []},
{"can_reuse": "", "label": "spinner", "id": "name_label_icon3", "icon": "/dummy-static/images/spinner.gif", "can_reuse": "", "target_fields": []},
{"can_reuse": "", "label": "Star", "id": "name4", "icon": "/dummy-static/images/volume.png", "can_reuse": "", "target_fields": []},
{"can_reuse": "", "label": "Label3", "id": "7", "icon": "", "can_reuse": "", "target_fields": []}],
{"can_reuse": "", "label": "Label 1", "id": "1", "icon": "", "target_fields": []},
{"can_reuse": "", "label": "cc", "id": "name_with_icon", "icon": "/dummy-static/images/cc.jpg", "target_fields": []},
{"can_reuse": "", "label": "arrow-left", "id": "with_icon", "icon": "/dummy-static/images/arrow-left.png", "target_fields": []},
{"can_reuse": "", "label": "Label2", "id": "5", "icon": "", "target_fields": []},
{"can_reuse": "", "label": "Mute", "id": "2", "icon": "/dummy-static/images/mute.png", "target_fields": []},
{"can_reuse": "", "label": "spinner", "id": "name_label_icon3", "icon": "/dummy-static/images/spinner.gif", "target_fields": []},
{"can_reuse": "", "label": "Star", "id": "name4", "icon": "/dummy-static/images/volume.png", "target_fields": []},
{"can_reuse": "", "label": "Label3", "id": "7", "icon": "", "target_fields": []}],
"one_per_target": "True",
"targets": [
{"y": "90", "x": "210", "id": "t1", "w": "90", "h": "90"},
{"y": "160", "x": "370", "id": "t2", "w": "90", "h": "90"}
]
}
{"y": "90", "x": "210", "id": "t1", "w": "90", "h": "90"},
{"y": "160", "x": "370", "id": "t2", "w": "90", "h": "90"}
]
}
the_input = lookup_tag('drag_and_drop_input')(test_capa_system(), element, state)
@@ -1177,9 +1141,7 @@ class DragAndDropTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'status': inputtypes.Status('unsubmitted'),
# 'label': '',
'msg': '',
'drag_and_drop_json': json.dumps(user_input)
@@ -1231,10 +1193,7 @@ class AnnotationInputTest(unittest.TestCase):
expected = {
'STATIC_URL': '/dummy-static/',
'id': 'annotation_input',
'value': value,
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
'status': inputtypes.Status('answered'),
# 'label': '',
'msg': '',
'title': 'foo',
@@ -1294,9 +1253,7 @@ class TestChoiceText(unittest.TestCase):
state = {
'value': '{}',
'id': 'choicetext_input',
'status': 'answered',
'status_class': 'answered',
'status_display': u'answered',
'status': inputtypes.Status('answered'),
}
first_input = self.build_choice_element('numtolerance_input', 'choiceinput_0_textinput_0', 'false', '')
@@ -1352,3 +1309,63 @@ class TestChoiceText(unittest.TestCase):
"""
with self.assertRaisesRegexp(Exception, "Error in xml"):
self.check_group('checkboxtextgroup', 'invalid', 'checkbox')
class TestStatus(unittest.TestCase):
"""
Tests for Status class
"""
def test_str(self):
"""
Test stringifing Status objects
"""
statobj = inputtypes.Status('test')
self.assertEqual(str(statobj), 'test')
self.assertEqual(unicode(statobj), u'test')
def test_classes(self):
"""
Test that css classnames are correct
"""
css_classes = [
('unsubmitted', 'unanswered'),
('incomplete', 'incorrect'),
('queued', 'processing'),
('correct', 'correct'),
('test', 'test'),
]
for status, classname in css_classes:
statobj = inputtypes.Status(status)
self.assertEqual(statobj.classname, classname)
def test_display_names(self):
"""
Test that display names are correct
"""
names = [
('correct', u'correct'),
('incorrect', u'incorrect'),
('incomplete', u'incomplete'),
('unanswered', u'unanswered'),
('unsubmitted', u'unanswered'),
('queued', u'processing'),
('dave', u'dave'),
]
for status, display_name in names:
statobj = inputtypes.Status(status)
self.assertEqual(statobj.display_name, display_name)
def test_translated_names(self):
"""
Test that display names are "translated"
"""
func = lambda t: t.upper()
# status is in the mapping
statobj = inputtypes.Status('queued', func)
self.assertEqual(statobj.display_name, u'PROCESSING')
# status is not in the mapping
statobj = inputtypes.Status('test', func)
self.assertEqual(statobj.display_name, u'test')
self.assertEqual(str(statobj), 'test')
self.assertEqual(statobj.classname, 'test')

View File

@@ -369,6 +369,9 @@ class CourseFields(object):
)
enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
scope=Scope.settings)
certificates_show_before_end = Boolean(help="True if students may download certificates before course end",
scope=Scope.settings,
default=False)
course_image = String(
help="Filename of the course image",
scope=Scope.settings,
@@ -592,6 +595,12 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
return datetime.now(UTC()) > self.end
def may_certify(self):
"""
Return True if it is acceptable to show the student a certificate download link
"""
return self.certificates_show_before_end or self.has_ended()
def has_started(self):
return datetime.now(UTC()) > self.start

View File

@@ -266,8 +266,8 @@ th {
.image-content .image-wrapper {
top: 0 !important;
left: 0 !important;
width: auto !important;
height: auto !important;
width: 100% !important;
height: 100% !important;
img {
top: 0 !important;

View File

@@ -1,3 +1,16 @@
h2.problem-header {
display: inline-block;
}
div.problem-progress {
display: inline-block;
padding-left: 5px;
color: #666;
font-weight: 100;
font-size: em(16);
}
div.lti {
// align center
margin: 0 auto;
@@ -31,4 +44,16 @@ div.lti {
display: block;
border: 0px;
}
h4.problem-feedback-label {
font-weight: 100;
font-size: em(16);
font-family: "Source Sans", "Open Sans", Verdana, Geneva, sans-serif, sans-serif;
}
div.problem-feedback {
margin-top: 5px;
margin-bottom: 5px;
}
}

View File

@@ -81,7 +81,7 @@ describe 'CombinedOpenEnded', ->
expect(window.setTimeout).toHaveBeenCalledWith(@combined.poll, 10000)
expect(window.queuePollerID).toBe(5)
it 'polling stops properly', =>
xit 'polling stops properly', =>
fakeResponseDone = state: "done"
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(fakeResponseDone)
@combined.poll()

View File

@@ -538,7 +538,7 @@ describe 'MarkdownEditingDescriptor', ->
<p>What is the capital of Germany?</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choicegroup label="What is the capital of Germany?" type="MultipleChoice">
<choice correct="false">Bonn</choice>
<choice correct="false">Hamburg</choice>
<choice correct="true">Berlin</choice>

View File

@@ -177,59 +177,6 @@
});
});
describe('YouTube video in FireFox will cue first', function () {
var oldUserAgent;
beforeEach(function () {
oldUserAgent = window.navigator.userAgent;
window.navigator.userAgent = 'firefox';
state = jasmine.initializePlayer('video.html', {
start: 10,
end: 30
});
});
afterEach(function () {
window.navigator.userAgent = oldUserAgent;
});
it('cue is called, skipOnEndedStartEndReset is set', function () {
state.videoPlayer.updatePlayTime(10);
expect(state.videoPlayer.player.cueVideoById).toHaveBeenCalledWith('cogebirgzzM', 10);
expect(state.videoPlayer.skipOnEndedStartEndReset).toBe(true);
});
it('when position is not 0: cue is called with stored position value', function () {
state.config.savedVideoPosition = 15;
state.videoPlayer.updatePlayTime(10);
expect(state.videoPlayer.player.cueVideoById).toHaveBeenCalledWith('cogebirgzzM', 15);
});
it('Handling cue state', function () {
spyOn(state.videoPlayer, 'play');
state.videoPlayer.seekToTimeOnCued = 10;
state.videoPlayer.onStateChange({data: 5});
expect(state.videoPlayer.player.seekTo).toHaveBeenCalledWith(10, true);
expect(state.videoPlayer.play).toHaveBeenCalled();
});
it('even when cued, onEnded does not resets start and end time', function () {
state.videoPlayer.skipOnEndedStartEndReset = true;
state.videoPlayer.onEnded();
expect(state.videoPlayer.startTime).toBe(10);
expect(state.videoPlayer.endTime).toBe(30);
state.videoPlayer.skipOnEndedStartEndReset = undefined;
state.videoPlayer.onEnded();
expect(state.videoPlayer.startTime).toBe(10);
expect(state.videoPlayer.endTime).toBe(30);
});
});
describe('checking start and end times', function () {
var miniTestSuite = [
{

View File

@@ -13,6 +13,7 @@
afterEach(function () {
$('source').remove();
state.storage.clear();
window.Video.previousState = null;
window.onTouchBasedDevice = oldOTBD;
});
@@ -37,7 +38,7 @@
});
it('add ARIA attributes to time control', function () {
var timeControl = $('div.slider>a');
var timeControl = $('div.slider > a');
expect(timeControl).toHaveAttrs({
'role': 'slider',
@@ -135,8 +136,6 @@
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(10);
state.storage.clear();
});
});
@@ -389,7 +388,7 @@
runs(function () {
state = jasmine.initializePlayer({
end: 20,
savedVideoPosition: 'a'
savedVideoPosition: 'a'
});
sliderEl = state.videoProgressSlider.slider;
spyOn(state.videoPlayer, 'duration').andReturn(60);

View File

@@ -17,6 +17,7 @@ function (VideoPlayer) {
afterEach(function () {
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
window.Video.previousState = null;
if (state.storage) {
state.storage.clear();
}
@@ -179,6 +180,11 @@ function (VideoPlayer) {
it('autoplay the first video', function () {
expect(state.videoPlayer.play).not.toHaveBeenCalled();
});
it('invalid endTime is reset to null', function () {
expect(state.videoPlayer.endTime).toBe(null);
});
});
describe('onReady YouTube', function () {
@@ -752,17 +758,6 @@ function (VideoPlayer) {
isFlashMode: jasmine.createSpy().andReturn(false)
};
});
it('invalid endTime is reset to null', function () {
VideoPlayer.prototype.updatePlayTime.call(state, 0);
expect(state.videoPlayer.figureOutStartingTime).toHaveBeenCalled();
VideoPlayer.prototype.figureOutStartEndTime.call(state, 60);
VideoPlayer.prototype.figureOutStartingTime.call(state, 60);
expect(state.videoPlayer.endTime).toBe(null);
});
});
describe('toggleFullScreen', function () {
@@ -1087,9 +1082,12 @@ function (VideoPlayer) {
isHtml5Mode: jasmine.createSpy().andReturn(true),
isYoutubeType: jasmine.createSpy().andReturn(true),
setPlayerMode: jasmine.createSpy(),
trigger: jasmine.createSpy(),
videoPlayer: {
currentTime: 60,
isPlaying: jasmine.createSpy(),
seekTo: jasmine.createSpy(),
duration: jasmine.createSpy().andReturn(60),
updatePlayTime: jasmine.createSpy(),
setPlaybackRate: jasmine.createSpy(),
player: jasmine.createSpyObj('player', [
@@ -1115,6 +1113,12 @@ function (VideoPlayer) {
state.videoPlayer.isPlaying.andReturn(false);
VideoPlayer.prototype.setPlaybackRate.call(state, '0.75');
expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(60);
expect(state.videoPlayer.seekTo).toHaveBeenCalledWith(60);
expect(state.trigger).toHaveBeenCalledWith(
'videoProgressSlider.updateStartEndTimeRegion',
{
duration: 60
});
expect(state.videoPlayer.player.cueVideoById)
.toHaveBeenCalledWith('videoId', 60);
});

View File

@@ -4,7 +4,8 @@ class @HTMLModule
@el = $(@element)
JavascriptLoader.executeModuleScripts(@el)
Collapsible.setCollapsibles(@el)
MathJax.Hub.Queue ["Typeset", MathJax.Hub, @el[0]]
if MathJax?
MathJax.Hub.Queue ["Typeset", MathJax.Hub, @el[0]]
$: (selector) ->
$(selector, @el)

View File

@@ -109,7 +109,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
$(".CodeMirror").css({"overflow": "visible"})
$(".modal-content").css({"overflow-y": "visible", "overflow-x": "visible"})
else
$(".CodeMirror").removeAttr("style")
$(".CodeMirror").css({"overflow": "none"})
$(".modal-content").removeAttr("style")
###
@@ -360,7 +360,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
// looks for >>arbitrary text<< and inserts it into the label attribute of the input type directly below the text.
var split = xml.split('\n');
var new_xml = [];
var line, i, curlabel = '';
var line, i, curlabel, prevlabel = '';
var didinput = false;
for (i = 0; i < split.length; i++) {
line = split[i];
@@ -371,13 +371,14 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
line = line.replace(/>>|<</g, '');
} else if (line.match(/<\w+response/) && didinput) {
} else if (line.match(/<\w+response/) && didinput && curlabel == prevlabel) {
// reset label to prevent gobbling up previous one (if multiple questions)
curlabel = '';
didinput = false;
} else if (line.match(/<(textline|optioninput|formulaequationinput|choicegroup|checkboxgroup)/) && curlabel != '') {
} else if (line.match(/<(textline|optioninput|formulaequationinput|choicegroup|checkboxgroup)/) && curlabel != '' && curlabel != undefined) {
line = line.replace(/<(textline|optioninput|formulaequationinput|choicegroup|checkboxgroup)/, '<$1 label="' + curlabel + '"');
didinput = true;
prevlabel = curlabel;
}
new_xml.push(line);
}

View File

@@ -44,6 +44,7 @@ function (HTML5Video, Resizer) {
onVolumeChange: onVolumeChange,
pause: pause,
play: play,
seekTo: seekTo,
setPlaybackRate: setPlaybackRate,
update: update,
figureOutStartEndTime: figureOutStartEndTime,
@@ -94,7 +95,7 @@ function (HTML5Video, Resizer) {
state.videoPlayer.ready = _.once(function () {
$(window).on('unload', state.saveState);
if (!state.isFlashMode()) {
if (!state.isFlashMode() && state.speed != '1.0') {
state.videoPlayer.setPlaybackRate(state.speed);
}
state.videoPlayer.player.setVolume(state.currentVolume);
@@ -352,7 +353,8 @@ function (HTML5Video, Resizer) {
}
function setPlaybackRate(newSpeed) {
var time = this.videoPlayer.currentTime,
var duration = this.videoPlayer.duration(),
time = this.videoPlayer.currentTime,
methodName, youtubeId;
if (
@@ -378,7 +380,22 @@ function (HTML5Video, Resizer) {
}
this.videoPlayer.player[methodName](youtubeId, time);
// We need to call play() explicitly because after the call
// to functions cueVideoById() followed by seekTo() the video
// is in a PAUSED state.
//
// Why? This is how the YouTube API is implemented.
this.videoPlayer.updatePlayTime(time);
if (time > 0 && this.isFlashMode()) {
this.videoPlayer.seekTo(time);
this.trigger(
'videoProgressSlider.updateStartEndTimeRegion',
{
duration: duration
}
);
}
}
}
@@ -414,59 +431,62 @@ function (HTML5Video, Resizer) {
// It is created on a onPlay event. Cleared on a onPause event.
// Reinitialized on a onSeek event.
function onSeek(params) {
var duration = this.videoPlayer.duration(),
newTime = params.time;
if (
(typeof newTime !== 'number') ||
(newTime > duration) ||
(newTime < 0)
) {
return;
}
this.el.off('play.seek');
this.videoPlayer.log(
'seek_video',
{
old_time: this.videoPlayer.currentTime,
new_time: newTime,
type: params.type
}
);
var time = params.time,
type = params.type;
// After the user seeks, the video will start playing from
// the sought point, and stop playing at the end.
this.videoPlayer.goToStartTime = false;
if (newTime > this.videoPlayer.endTime || this.videoPlayer.endTime === null) {
if (time > this.videoPlayer.endTime || this.videoPlayer.endTime === null) {
this.videoPlayer.stopAtEndTime = false;
}
this.videoPlayer.seekTo(time);
this.videoPlayer.log(
'seek_video',
{
old_time: this.videoPlayer.currentTime,
new_time: time,
type: type
}
);
}
function seekTo(time) {
var duration = this.videoPlayer.duration();
if ((typeof time !== 'number') || (time > duration) || (time < 0)) {
return false;
}
this.el.off('play.seek');
if (this.videoPlayer.isPlaying()) {
this.videoPlayer.stopTimer();
} else {
this.videoPlayer.currentTime = newTime;
this.videoPlayer.currentTime = time;
}
var isUnplayed = this.videoPlayer.isUnstarted() ||
this.videoPlayer.isCued();
// Use `cueVideoById` method for youtube video that is not played before.
if (isUnplayed && this.isYoutubeType()) {
this.videoPlayer.player.cueVideoById(this.youtubeId(), newTime);
this.videoPlayer.player.cueVideoById(this.youtubeId(), time);
} else {
// Youtube video cannot be rewinded during bufferization, so wait to
// finish bufferization and then rewind the video.
if (this.isYoutubeType() && this.videoPlayer.isBuffering()) {
this.el.on('play.seek', function () {
this.videoPlayer.player.seekTo(newTime, true);
this.videoPlayer.player.seekTo(time, true);
}.bind(this));
} else {
// Otherwise, just seek the video
this.videoPlayer.player.seekTo(newTime, true);
this.videoPlayer.player.seekTo(time, true);
}
}
this.videoPlayer.updatePlayTime(newTime, true);
this.videoPlayer.updatePlayTime(time, true);
this.el.trigger('seek', arguments);
}
@@ -609,6 +629,7 @@ function (HTML5Video, Resizer) {
// have 1 speed available, we fall back to Flash.
_restartUsingFlash(this);
return false;
} else if (availablePlaybackRates.length > 1) {
this.setPlayerMode('html5');
@@ -646,16 +667,15 @@ function (HTML5Video, Resizer) {
this.videoPlayer.player.setPlaybackRate(this.speed);
}
this.el.trigger('ready', arguments);
/* The following has been commented out to make sure autoplay is
disabled for students.
if (
!this.isTouch &&
$('.video:first').data('autoplay') === 'True'
) {
this.videoPlayer.play();
var duration = this.videoPlayer.duration(),
time = this.videoPlayer.figureOutStartingTime(duration);
if (time > 0 && this.videoPlayer.goToStartTime) {
this.videoPlayer.seekTo(time);
}
*/
this.el.trigger('ready', arguments);
}
function onStateChange(event) {
@@ -687,13 +707,9 @@ function (HTML5Video, Resizer) {
break;
case this.videoPlayer.PlayerState.CUED:
this.el.addClass('is-cued');
this.videoPlayer.player.seekTo(this.videoPlayer.seekToTimeOnCued, true);
// We need to call play() explicitly because after the call
// to functions cueVideoById() followed by seekTo() the video
// is in a PAUSED state.
//
// Why? This is how the YouTube API is implemented.
this.videoPlayer.play();
if (this.isFlashMode()) {
this.videoPlayer.play();
}
break;
}
}
@@ -769,57 +785,6 @@ function (HTML5Video, Resizer) {
duration = this.videoPlayer.duration(),
youTubeId;
if (duration > 0 && videoPlayer.goToStartTime && !skip_seek) {
videoPlayer.goToStartTime = false;
// The duration might have changed. Update the start-end time region to
// reflect this fact.
this.trigger(
'videoProgressSlider.updateStartEndTimeRegion',
{
duration: duration
}
);
time = videoPlayer.figureOutStartingTime(duration);
// When the video finishes playing, we will start from the
// start-time, or from the beginning (rather than from the remembered
// position).
this.config.savedVideoPosition = 0;
if (time > 0) {
// After a bug came up (BLD-708: "In Firefox YouTube video with
// start-time plays from 00:00:00") the video refused to play
// from start-time, and only played from the beginning.
//
// It turned out that for some reason if Firefox you couldn't
// seek beyond some amount of time before the video loaded.
// Very strange, but in Chrome there is no such bug.
//
// HTML5 video sources play fine from start-time in both Chrome
// and Firefox.
if (this.browserIsFirefox && this.isYoutubeType()) {
youTubeId = this.youtubeId();
// When we will call cueVideoById() for some strange reason
// an ENDED event will be fired. It really does no damage
// except for the fact that the end-time is reset to null.
// We do not want this.
//
// The flag `skipOnEndedStartEndReset` will notify the
// onEnded() callback for the ENDED event that there
// is no need in resetting the start-time and end-time.
videoPlayer.skipOnEndedStartEndReset = true;
videoPlayer.seekToTimeOnCued = time;
videoPlayer.player.cueVideoById(youTubeId, time);
} else {
videoPlayer.player.seekTo(time);
}
}
}
this.trigger(
'videoProgressSlider.updatePlayTime',
{

View File

@@ -57,18 +57,11 @@
VideoCaption
) {
var youtubeXhr = null,
oldVideo = window.Video,
// Because this constructor can be called multiple times on a single page (when the user switches
// verticals, the page doesn't reload, but the content changes), we must will check each time if there
// is a previous copy of 'state' object. If there is, we will make sure that copy exists cleanly. We
// have to do this because when verticals switch, the code does not handle any Xmodule JS code that is
// running - it simply removes DOM elements from the page. Any functions that were running during this,
// and that will run afterwards (expecting the DOM elements to be present) must be stopped by hand.
previousState = null;
oldVideo = window.Video;
window.Video = function (element) {
var state;
var previousState = window.Video.previousState,
state;
// Check for existance of previous state, uninitialize it if necessary, and create a new state. Store
// new state for future invocation of this module consturctor function.
@@ -78,7 +71,13 @@
}
state = {};
previousState = state;
// Because this constructor can be called multiple times on a single page (when the user switches
// verticals, the page doesn't reload, but the content changes), we must will check each time if there
// is a previous copy of 'state' object. If there is, we will make sure that copy exists cleanly. We
// have to do this because when verticals switch, the code does not handle any Xmodule JS code that is
// running - it simply removes DOM elements from the page. Any functions that were running during this,
// and that will run afterwards (expecting the DOM elements to be present) must be stopped by hand.
window.Video.previousState = state;
state.modules = [
FocusGrabber,

View File

@@ -0,0 +1,363 @@
# pylint: disable=attribute-defined-outside-init
"""
A mixin class for LTI 2.0 functionality. This is really just done to refactor the code to
keep the LTIModule class from getting too big
"""
import json
import re
import mock
import urllib
import hashlib
import base64
import logging
from webob import Response
from xblock.core import XBlock
from oauthlib.oauth1 import Client
log = logging.getLogger(__name__)
LTI_2_0_REST_SUFFIX_PARSER = re.compile(r"^user/(?P<anon_id>\w+)", re.UNICODE)
LTI_2_0_JSON_CONTENT_TYPE = 'application/vnd.ims.lis.v2.result+json'
class LTIError(Exception):
"""Error class for LTIModule and LTI20ModuleMixin"""
pass
class LTI20ModuleMixin(object):
"""
This class MUST be mixed into LTIModule. It does not do anything on its own. It's just factored
out for modularity.
"""
# LTI 2.0 Result Service Support
@XBlock.handler
def lti_2_0_result_rest_handler(self, request, suffix):
"""
Handler function for LTI 2.0 JSON/REST result service.
See http://www.imsglobal.org/lti/ltiv2p0/uml/purl.imsglobal.org/vocab/lis/v2/outcomes/Result/service.html
An example JSON object:
{
"@context" : "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type" : "Result",
"resultScore" : 0.83,
"comment" : "This is exceptional work."
}
For PUTs, the content type must be "application/vnd.ims.lis.v2.result+json".
We use the "suffix" parameter to parse out the user from the end of the URL. An example endpoint url is
http://localhost:8000/courses/org/num/run/xblock/i4x:;_;_org;_num;_lti;_GUID/handler_noauth/lti_2_0_result_rest_handler/user/<anon_id>
so suffix is of the form "user/<anon_id>"
Failures result in 401, 404, or 500s without any body. Successes result in 200. Again see
http://www.imsglobal.org/lti/ltiv2p0/uml/purl.imsglobal.org/vocab/lis/v2/outcomes/Result/service.html
(Note: this prevents good debug messages for the client, so we might want to change this, or the spec)
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object for current HTTP request
suffix (unicode): request path after "lti_2_0_result_rest_handler/". expected to be "user/<anon_id>"
Returns:
webob.response: response to this request. See above for details.
"""
if self.system.debug:
self._log_correct_authorization_header(request)
try:
anon_id = self.parse_lti_2_0_handler_suffix(suffix)
except LTIError:
return Response(status=404) # 404 because a part of the URL (denoting the anon user id) is invalid
try:
self.verify_lti_2_0_result_rest_headers(request, verify_content_type=True)
except LTIError:
return Response(status=401) # Unauthorized in this case. 401 is right
real_user = self.system.get_real_user(anon_id)
if not real_user: # that means we can't save to database, as we do not have real user id.
msg = "[LTI]: Real user not found against anon_id: {}".format(anon_id)
log.info(msg)
return Response(status=404) # have to do 404 due to spec, but 400 is better, with error msg in body
if request.method == "PUT":
return self._lti_2_0_result_put_handler(request, real_user)
elif request.method == "GET":
return self._lti_2_0_result_get_handler(request, real_user)
elif request.method == "DELETE":
return self._lti_2_0_result_del_handler(request, real_user)
else:
return Response(status=404) # have to do 404 due to spec, but 405 is better, with error msg in body
def _log_correct_authorization_header(self, request):
"""
Helper function that logs proper HTTP Authorization header for a given request
Used only in debug situations, this logs the correct Authorization header based on
the request header and body according to OAuth 1 Body signing
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object to log Authorization header for
Returns:
nothing
"""
sha1 = hashlib.sha1()
sha1.update(request.body)
oauth_body_hash = unicode(base64.b64encode(sha1.digest())) # pylint: disable=too-many-function-args
log.debug("[LTI] oauth_body_hash = {}".format(oauth_body_hash))
client_key, client_secret = self.get_client_key_secret()
client = Client(client_key, client_secret)
params = client.get_oauth_params()
params.append((u'oauth_body_hash', oauth_body_hash))
mock_request = mock.Mock(
uri=unicode(urllib.unquote(request.url)),
headers=request.headers,
body=u"",
decoded_body=u"",
oauth_params=params,
http_method=unicode(request.method),
)
sig = client.get_oauth_signature(mock_request)
mock_request.oauth_params.append((u'oauth_signature', sig))
_, headers, _ = client._render(mock_request) # pylint: disable=protected-access
log.debug("\n\n#### COPY AND PASTE AUTHORIZATION HEADER ####\n{}\n####################################\n\n"
.format(headers['Authorization']))
def parse_lti_2_0_handler_suffix(self, suffix):
"""
Parser function for HTTP request path suffixes
parses the suffix argument (the trailing parts of the URL) of the LTI2.0 REST handler.
must be of the form "user/<anon_id>". Returns anon_id if match found, otherwise raises LTIError
Arguments:
suffix (unicode): suffix to parse
Returns:
unicode: anon_id if match found
Raises:
LTIError if suffix cannot be parsed or is not in its expected form
"""
if suffix:
match_obj = LTI_2_0_REST_SUFFIX_PARSER.match(suffix)
if match_obj:
return match_obj.group('anon_id')
# fall-through handles all error cases
msg = "No valid user id found in endpoint URL"
log.info("[LTI]: {}".format(msg))
raise LTIError(msg)
def _lti_2_0_result_get_handler(self, request, real_user): # pylint: disable=unused-argument
"""
Helper request handler for GET requests to LTI 2.0 result endpoint
GET handler for lti_2_0_result. Assumes all authorization has been checked.
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object (unused)
real_user (django.contrib.auth.models.User): Actual user linked to anon_id in request path suffix
Returns:
webob.response: response to this request, in JSON format with status 200 if success
"""
base_json_obj = {
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type": "Result"
}
self.system.rebind_noauth_module_to_user(self, real_user)
if self.module_score is None: # In this case, no score has been ever set
return Response(json.dumps(base_json_obj), content_type=LTI_2_0_JSON_CONTENT_TYPE)
# Fall through to returning grade and comment
base_json_obj['resultScore'] = round(self.module_score, 2)
base_json_obj['comment'] = self.score_comment
return Response(json.dumps(base_json_obj), content_type=LTI_2_0_JSON_CONTENT_TYPE)
def _lti_2_0_result_del_handler(self, request, real_user): # pylint: disable=unused-argument
"""
Helper request handler for DELETE requests to LTI 2.0 result endpoint
DELETE handler for lti_2_0_result. Assumes all authorization has been checked.
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object (unused)
real_user (django.contrib.auth.models.User): Actual user linked to anon_id in request path suffix
Returns:
webob.response: response to this request. status 200 if success
"""
self.clear_user_module_score(real_user)
return Response(status=200)
def _lti_2_0_result_put_handler(self, request, real_user):
"""
Helper request handler for PUT requests to LTI 2.0 result endpoint
PUT handler for lti_2_0_result. Assumes all authorization has been checked.
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object
real_user (django.contrib.auth.models.User): Actual user linked to anon_id in request path suffix
Returns:
webob.response: response to this request. status 200 if success. 404 if body of PUT request is malformed
"""
try:
(score, comment) = self.parse_lti_2_0_result_json(request.body)
except LTIError:
return Response(status=404) # have to do 404 due to spec, but 400 is better, with error msg in body
# According to http://www.imsglobal.org/lti/ltiv2p0/ltiIMGv2p0.html#_Toc361225514
# PUTting a JSON object with no "resultScore" field is equivalent to a DELETE.
if score is None:
self.clear_user_module_score(real_user)
return Response(status=200)
# Fall-through record the score and the comment in the module
self.set_user_module_score(real_user, score, self.max_score(), comment)
return Response(status=200)
def clear_user_module_score(self, user):
"""
Clears the module user state, including grades and comments, and also scoring in db's courseware_studentmodule
Arguments:
user (django.contrib.auth.models.User): Actual user whose module state is to be cleared
Returns:
nothing
"""
self.set_user_module_score(user, None, None)
def set_user_module_score(self, user, score, max_score, comment=u""):
"""
Sets the module user state, including grades and comments, and also scoring in db's courseware_studentmodule
Arguments:
user (django.contrib.auth.models.User): Actual user whose module state is to be set
score (float): user's numeric score to set. Must be in the range [0.0, 1.0]
max_score (float): max score that could have been achieved on this module
comment (unicode): comments provided by the grader as feedback to the student
Returns:
nothing
"""
if score is not None and max_score is not None:
scaled_score = score * max_score
else:
scaled_score = None
self.system.rebind_noauth_module_to_user(self, user)
# have to publish for the progress page...
self.system.publish(
self,
'grade',
{
'value': scaled_score,
'max_value': max_score,
'user_id': user.id,
},
)
self.module_score = scaled_score
self.score_comment = comment
def verify_lti_2_0_result_rest_headers(self, request, verify_content_type=True):
"""
Helper method to validate LTI 2.0 REST result service HTTP headers. returns if correct, else raises LTIError
Arguments:
request (xblock.django.request.DjangoWebobRequest): Request object
verify_content_type (bool): If true, verifies the content type of the request is that spec'ed by LTI 2.0
Returns:
nothing, but will only return if verification succeeds
Raises:
LTIError if verification fails
"""
content_type = request.headers.get('Content-Type')
if verify_content_type and content_type != LTI_2_0_JSON_CONTENT_TYPE:
log.info("[LTI]: v2.0 result service -- bad Content-Type: {}".format(content_type))
raise LTIError(
"For LTI 2.0 result service, Content-Type must be {}. Got {}".format(LTI_2_0_JSON_CONTENT_TYPE,
content_type))
try:
self.verify_oauth_body_sign(request, content_type=LTI_2_0_JSON_CONTENT_TYPE)
except (ValueError, LTIError) as err:
log.info("[LTI]: v2.0 result service -- OAuth body verification failed: {}".format(err.message))
raise LTIError(err.message)
def parse_lti_2_0_result_json(self, json_str):
"""
Helper method for verifying LTI 2.0 JSON object contained in the body of the request.
The json_str must be loadable. It can either be an dict (object) or an array whose first element is an dict,
in which case that first dict is considered.
The dict must have the "@type" key with value equal to "Result",
"resultScore" key with value equal to a number [0, 1],
The "@context" key must be present, but we don't do anything with it. And the "comment" key may be
present, in which case it must be a string.
Arguments:
json_str (unicode): The body of the LTI 2.0 results service request, which is a JSON string]
Returns:
(float, str): (score, [optional]comment) if verification checks out
Raises:
LTIError (with message) if verification fails
"""
try:
json_obj = json.loads(json_str)
except (ValueError, TypeError):
msg = "Supplied JSON string in request body could not be decoded: {}".format(json_str)
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
# the standard supports a list of objects, who knows why. It must contain at least 1 element, and the
# first element must be a dict
if type(json_obj) != dict:
if type(json_obj) == list and len(json_obj) >= 1 and type(json_obj[0]) == dict:
json_obj = json_obj[0]
else:
msg = ("Supplied JSON string is a list that does not contain an object as the first element. {}"
.format(json_str))
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
# '@type' must be "Result"
result_type = json_obj.get("@type")
if result_type != "Result":
msg = "JSON object does not contain correct @type attribute (should be 'Result', is {})".format(result_type)
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
# '@context' must be present as a key
REQUIRED_KEYS = ["@context"] # pylint: disable=invalid-name
for key in REQUIRED_KEYS:
if key not in json_obj:
msg = "JSON object does not contain required key {}".format(key)
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
# 'resultScore' is not present. If this was a PUT this means it's actually a DELETE according
# to the LTI spec. We will indicate this by returning None as score, "" as comment.
# The actual delete will be handled by the caller
if "resultScore" not in json_obj:
return None, json_obj.get('comment', "")
# if present, 'resultScore' must be a number between 0 and 1 inclusive
try:
score = float(json_obj.get('resultScore', "unconvertable")) # Check if float is present and the right type
if not 0 <= score <= 1:
msg = 'score value outside the permitted range of 0-1.'
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
except (TypeError, ValueError) as err:
msg = "Could not convert resultScore to float: {}".format(err.message)
log.info("[LTI] {}".format(msg))
raise LTIError(msg)
return score, json_obj.get('comment', "")

View File

@@ -22,6 +22,11 @@ A resource to test the LTI protocol (PHP realization):
http://www.imsglobal.org/developers/LTI/test/v1p1/lms.php
We have also begun to add support for LTI 1.2/2.0. We will keep this
docstring in synch with what support is available. The first LTI 2.0
feature to be supported is the REST API results service, see specification
at
http://www.imsglobal.org/lti/ltiv2p0/uml/purl.imsglobal.org/vocab/lis/v2/outcomes/Result/service.html
What is supported:
------------------
@@ -30,9 +35,20 @@ What is supported:
2.) Multiple LTI components on a single page.
3.) The use of multiple LTI providers per course.
4.) Use of advanced LTI component that provides back a grade.
a.) The LTI provider sends back a grade to a specified URL.
b.) Currently only action "update" is supported. "Read", and "delete"
actions initially weren't required.
A) LTI 1.1.1 XML endpoint
a.) The LTI provider sends back a grade to a specified URL.
b.) Currently only action "update" is supported. "Read", and "delete"
actions initially weren't required.
B) LTI 2.0 Result Service JSON REST endpoint
(http://www.imsglobal.org/lti/ltiv2p0/uml/purl.imsglobal.org/vocab/lis/v2/outcomes/Result/service.html)
a.) Discovery of all such LTI http endpoints for a course. External tools GET from this discovery
endpoint and receive URLs for interacting with individual grading units.
(see lms/djangoapps/courseware/views.py:get_course_lti_endpoints)
b.) GET, PUT and DELETE in LTI Result JSON binding
(http://www.imsglobal.org/lti/ltiv2p0/mediatype/application/vnd/ims/lis/v2/result+json/index.html)
for a provider to synchronize grades into edx-platform. Reading, Setting, and Deleteing
Numeric grades between 0 and 1 and text + basic HTML feedback comments are supported, via
GET / PUT / DELETE HTTP methods respectively
"""
import logging
@@ -42,6 +58,7 @@ import hashlib
import base64
import urllib
import textwrap
import bleach
from lxml import etree
from webob import Response
import mock
@@ -51,15 +68,18 @@ from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.x_module import XModule, module_attr
from xmodule.course_module import CourseDescriptor
from xmodule.lti_2_util import LTI20ModuleMixin, LTIError
from pkg_resources import resource_string
from xblock.core import String, Scope, List, XBlock
from xblock.fields import Boolean, Float
log = logging.getLogger(__name__)
class LTIError(Exception):
pass
DOCS_ANCHOR_TAG = (
"<a target='_blank'"
"href='http://edx.readthedocs.org/projects/ca/en/latest/exercises_tools/lti_component.html'>"
"the edX LTI documentation</a>"
)
class LTIFields(object):
@@ -82,22 +102,95 @@ class LTIFields(object):
https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136
"""
display_name = String(display_name="Display Name", help="Display name for this module", scope=Scope.settings, default="LTI")
lti_id = String(help="Id of the tool", default='', scope=Scope.settings)
launch_url = String(help="URL of the tool", default='http://www.example.com', scope=Scope.settings)
custom_parameters = List(help="Custom parameters (vbid, book_location, etc..)", scope=Scope.settings)
open_in_a_new_page = Boolean(help="Should LTI be opened in new page?", default=True, scope=Scope.settings)
graded = Boolean(help="Grades will be considered in overall score.", default=False, scope=Scope.settings)
display_name = String(
display_name="Display Name",
help=(
"Enter the name that students see for this component. "
"Analytics reports may also use the display name to identify this component."
),
scope=Scope.settings,
default="LTI",
)
lti_id = String(
display_name="LTI ID",
help=(
"Enter the LTI ID for the external LTI provider. "
"This value must be the same LTI ID that you entered in the "
"LTI Passports setting on the Advanced Settings page."
"<br />See " + DOCS_ANCHOR_TAG + " for more details on this setting."
),
default='',
scope=Scope.settings
)
launch_url = String(
display_name="LTI URL",
help=(
"Enter the URL of the external tool that this component launches. "
"This setting is only used when Hide External Tool is set to False."
"<br />See " + DOCS_ANCHOR_TAG + " for more details on this setting."
),
default='http://www.example.com',
scope=Scope.settings)
custom_parameters = List(
display_name="Custom Parameters",
help=(
"Add the key/value pair for any custom parameters, such as the page your e-book should open to or "
"the background color for this component."
"<br />See " + DOCS_ANCHOR_TAG + " for more details on this setting."
),
scope=Scope.settings)
open_in_a_new_page = Boolean(
display_name="Open in New Page",
help=(
"Select True if you want students to click a link that opens the LTI tool in a new window. "
"Select False if you want the LTI content to open in an IFrame in the current page. "
"This setting is only used when Hide External Tool is set to False. "
),
default=True,
scope=Scope.settings
)
has_score = Boolean(
display_name="Scored",
help=(
"Select True if this component will receive a numerical score from the external LTI system."
),
default=False,
scope=Scope.settings
)
weight = Float(
help="Weight for student grades.",
display_name="Weight",
help=(
"Enter the number of points possible for this component. "
"The default value is 1.0. "
"This setting is only used when Scored is set to True."
),
default=1.0,
scope=Scope.settings,
values={"min": 0},
)
has_score = Boolean(help="Does this LTI module have score?", default=False, scope=Scope.settings)
module_score = Float(
help="The score kept in the xblock KVS -- duplicate of the published score in django DB",
default=None,
scope=Scope.user_state
)
score_comment = String(
help="Comment as returned from grader, LTI2.0 spec",
default="",
scope=Scope.user_state
)
hide_launch = Boolean(
display_name="Hide External Tool",
help=(
"Select True if you want to use this component as a placeholder for syncing with an external grading "
"system rather than launch an external tool. "
"This setting hides the Launch button and any IFrames for this component."
),
default=False,
scope=Scope.settings
)
class LTIModule(LTIFields, XModule):
class LTIModule(LTIFields, LTI20ModuleMixin, XModule):
"""
Module provides LTI integration to course.
@@ -247,6 +340,18 @@ class LTIModule(LTIFields, XModule):
"""
Returns a context.
"""
# use bleach defaults. see https://github.com/jsocol/bleach/blob/master/bleach/__init__.py
# ALLOWED_TAGS are
# ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul']
#
# ALLOWED_ATTRIBUTES are
# 'a': ['href', 'title'],
# 'abbr': ['title'],
# 'acronym': ['title'],
#
# This lets all plaintext through.
sanitized_comment = bleach.clean(self.score_comment)
return {
'input_fields': self.get_input_fields(),
@@ -257,6 +362,11 @@ class LTIModule(LTIFields, XModule):
'open_in_a_new_page': self.open_in_a_new_page,
'display_name': self.display_name,
'form_url': self.runtime.handler_url(self, 'preview_handler').rstrip('/?'),
'hide_launch': self.hide_launch,
'has_score': self.has_score,
'weight': self.weight,
'module_score': self.module_score,
'comment': sanitized_comment,
}
def get_html(self):
@@ -278,7 +388,7 @@ class LTIModule(LTIFields, XModule):
assert user_id is not None
return unicode(urllib.quote(user_id))
def get_outcome_service_url(self):
def get_outcome_service_url(self, service_name="grade_handler"):
"""
Return URL for storing grades.
@@ -286,14 +396,10 @@ class LTIModule(LTIFields, XModule):
While testing locally and on Jenkins, mock_lti_server use http.referer
to obtain scheme, so it is ok to have http(s) anyway.
The scheme logic is handled in lms/lib/xblock/runtime.py
"""
scheme = 'http' if 'sandbox' in self.system.hostname or self.system.debug else 'https'
uri = '{scheme}://{host}{path}'.format(
scheme=scheme,
host=self.system.hostname,
path=self.runtime.handler_url(self, 'grade_handler', thirdparty=True).rstrip('/?')
)
return uri
return self.runtime.handler_url(self, service_name, thirdparty=True).rstrip('/?')
def get_resource_link_id(self):
"""
@@ -453,9 +559,8 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
def max_score(self):
return self.weight if self.has_score else None
@XBlock.handler
def grade_handler(self, request, dispatch):
def grade_handler(self, request, suffix): # pylint: disable=unused-argument
"""
This is called by courseware.module_render, to handle an AJAX call.
@@ -554,15 +659,7 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
return Response(response_xml_template.format(**failure_values), content_type="application/xml")
if action == 'replaceResultRequest':
self.system.publish(
self,
'grade',
{
'value': score * self.max_score(),
'max_value': self.max_score(),
'user_id': real_user.id,
}
)
self.set_user_module_score(real_user, score, self.max_score())
values = {
'imsx_codeMajor': 'success',
@@ -607,7 +704,7 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
return imsx_messageIdentifier, sourcedId, score, action
def verify_oauth_body_sign(self, request):
def verify_oauth_body_sign(self, request, content_type='application/x-www-form-urlencoded'):
"""
Verify grade request from LTI provider using OAuth body signing.
@@ -625,26 +722,26 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'}
client_key, client_secret = self.get_client_key_secret()
headers = {
'Authorization':unicode(request.headers.get('Authorization')),
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': unicode(request.headers.get('Authorization')),
'Content-Type': content_type,
}
sha1 = hashlib.sha1()
sha1.update(request.body)
oauth_body_hash = base64.b64encode(sha1.digest())
oauth_params = signature.collect_parameters(headers=headers, exclude_oauth_signature=False)
oauth_headers =dict(oauth_params)
oauth_headers = dict(oauth_params)
oauth_signature = oauth_headers.pop('oauth_signature')
mock_request = mock.Mock(
uri=unicode(urllib.unquote(request.url)),
http_method=unicode(request.method),
params=oauth_headers.items(),
signature=oauth_signature
)
if oauth_body_hash != oauth_headers.get('oauth_body_hash'):
raise LTIError("OAuth body hash verification is failed.")
if not signature.verify_hmac_sha1(mock_request, client_secret):
raise LTIError("OAuth signature verification is failed.")
@@ -674,3 +771,6 @@ class LTIDescriptor(LTIFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescri
module_class = LTIModule
grade_handler = module_attr('grade_handler')
preview_handler = module_attr('preview_handler')
lti_2_0_result_rest_handler = module_attr('lti_2_0_result_rest_handler')
clear_user_module_score = module_attr('clear_user_module_score')
get_outcome_service_url = module_attr('get_outcome_service_url')

View File

@@ -198,7 +198,7 @@ class SplitTestModule(SplitTestFields, XModule):
conditions for staff.
"""
# When rendering a Studio preview, render all of the block's children
if context and context['runtime_type'] == 'studio':
if context and context.get('runtime_type', None) == 'studio':
return self.studio_preview_view(context)
if self.child is None:

View File

@@ -1,5 +1,5 @@
import unittest
from datetime import datetime
from datetime import datetime, timedelta
from fs.memoryfs import MemoryFS
@@ -49,7 +49,7 @@ class DummySystem(ImportSystem):
)
def get_dummy_course(start, announcement=None, is_new=None, advertised_start=None, end=None):
def get_dummy_course(start, announcement=None, is_new=None, advertised_start=None, end=None, certs=False):
"""Get a dummy course"""
system = DummySystem(load_error_modules=True)
@@ -69,17 +69,61 @@ def get_dummy_course(start, announcement=None, is_new=None, advertised_start=Non
{announcement}
{is_new}
{advertised_start}
{end}>
{end}
certificates_show_before_end="{certs}">
<chapter url="hi" url_name="ch" display_name="CH">
<html url_name="h" display_name="H">Two houses, ...</html>
</chapter>
</course>
'''.format(org=ORG, course=COURSE, start=start, is_new=is_new,
announcement=announcement, advertised_start=advertised_start, end=end)
announcement=announcement, advertised_start=advertised_start, end=end,
certs=certs)
return system.process_xml(start_xml)
class HasEndedMayCertifyTestCase(unittest.TestCase):
"""Double check the semantics around when to finalize courses."""
def setUp(self):
system = DummySystem(load_error_modules=True)
#sample_xml = """
# <course org="{org}" course="{course}" display_organization="{org}_display" display_coursenumber="{course}_display"
# graceperiod="1 day" url_name="test"
# start="2012-01-01T12:00"
# {end}
# certificates_show_before_end={cert}>
# <chapter url="hi" url_name="ch" display_name="CH">
# <html url_name="h" display_name="H">Two houses, ...</html>
# </chapter>
# </course>
#""".format(org=ORG, course=COURSE)
past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
self.past_show_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs=True)
self.past_noshow_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs=False)
self.future_show_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs=True)
self.future_noshow_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs=False)
#self.past_show_certs = system.process_xml(sample_xml.format(end=past_end, cert=True))
#self.past_noshow_certs = system.process_xml(sample_xml.format(end=past_end, cert=False))
#self.future_show_certs = system.process_xml(sample_xml.format(end=future_end, cert=True))
#self.future_noshow_certs = system.process_xml(sample_xml.format(end=future_end, cert=False))
def test_has_ended(self):
"""Check that has_ended correctly tells us when a course is over."""
self.assertTrue(self.past_show_certs.has_ended())
self.assertTrue(self.past_noshow_certs.has_ended())
self.assertFalse(self.future_show_certs.has_ended())
self.assertFalse(self.future_noshow_certs.has_ended())
def test_may_certify(self):
"""Check that may_certify correctly tells us when a course may wrap."""
self.assertTrue(self.past_show_certs.may_certify())
self.assertTrue(self.past_noshow_certs.may_certify())
self.assertTrue(self.future_show_certs.may_certify())
self.assertFalse(self.future_noshow_certs.may_certify())
class IsNewCourseTestCase(unittest.TestCase):
"""Make sure the property is_new works on courses"""

View File

@@ -0,0 +1,372 @@
# -*- coding: utf-8 -*-
"""Tests for LTI Xmodule LTIv2.0 functional logic."""
import textwrap
from mock import Mock
from xmodule.lti_module import LTIDescriptor
from xmodule.lti_2_util import LTIError
from . import LogicTest
class LTI20RESTResultServiceTest(LogicTest):
"""Logic tests for LTI module. LTI2.0 REST ResultService"""
descriptor_class = LTIDescriptor
def setUp(self):
super(LTI20RESTResultServiceTest, self).setUp()
self.environ = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'POST'}
self.system.get_real_user = Mock()
self.system.publish = Mock()
self.system.rebind_noauth_module_to_user = Mock()
self.user_id = self.xmodule.runtime.anonymous_student_id
self.lti_id = self.xmodule.lti_id
def test_sanitize_get_context(self):
"""Tests that the get_context function does basic sanitization"""
# get_context, unfortunately, requires a lot of mocking machinery
mocked_course = Mock(lti_passports=['lti_id:test_client:test_secret'])
modulestore = Mock()
modulestore.get_item.return_value = mocked_course
runtime = Mock(modulestore=modulestore)
self.xmodule.descriptor.runtime = runtime
self.xmodule.lti_id = "lti_id"
self.xmodule.scope_ids.usage_id = "mocked"
test_cases = ( # (before sanitize, after sanitize)
(u"plaintext", u"plaintext"),
(u"a <script>alert(3)</script>", u"a &lt;script&gt;alert(3)&lt;/script&gt;"), # encodes scripts
(u"<b>bold 包</b>", u"<b>bold 包</b>"), # unicode, and <b> tags pass through
)
for case in test_cases:
self.xmodule.score_comment = case[0]
self.assertEqual(
case[1],
self.xmodule.get_context()['comment']
)
def test_lti20_rest_bad_contenttype(self):
"""
Input with bad content type
"""
with self.assertRaisesRegexp(LTIError, "Content-Type must be"):
request = Mock(headers={u'Content-Type': u'Non-existent'})
self.xmodule.verify_lti_2_0_result_rest_headers(request)
def test_lti20_rest_failed_oauth_body_verify(self):
"""
Input with bad oauth body hash verification
"""
err_msg = "OAuth body verification failed"
self.xmodule.verify_oauth_body_sign = Mock(side_effect=LTIError(err_msg))
with self.assertRaisesRegexp(LTIError, err_msg):
request = Mock(headers={u'Content-Type': u'application/vnd.ims.lis.v2.result+json'})
self.xmodule.verify_lti_2_0_result_rest_headers(request)
def test_lti20_rest_good_headers(self):
"""
Input with good oauth body hash verification
"""
self.xmodule.verify_oauth_body_sign = Mock(return_value=True)
request = Mock(headers={u'Content-Type': u'application/vnd.ims.lis.v2.result+json'})
self.xmodule.verify_lti_2_0_result_rest_headers(request)
# We just want the above call to complete without exceptions, and to have called verify_oauth_body_sign
self.assertTrue(self.xmodule.verify_oauth_body_sign.called)
BAD_DISPATCH_INPUTS = [
None,
u"",
u"abcd"
u"notuser/abcd"
u"user/"
u"user//"
u"user/gbere/"
u"user/gbere/xsdf"
u"user/ಠ益ಠ" # not alphanumeric
]
def test_lti20_rest_bad_dispatch(self):
"""
Test the error cases for the "dispatch" argument to the LTI 2.0 handler. Anything that doesn't
fit the form user/<anon_id>
"""
for einput in self.BAD_DISPATCH_INPUTS:
with self.assertRaisesRegexp(LTIError, "No valid user id found in endpoint URL"):
self.xmodule.parse_lti_2_0_handler_suffix(einput)
GOOD_DISPATCH_INPUTS = [
(u"user/abcd3", u"abcd3"),
(u"user/Äbcdè2", u"Äbcdè2"), # unicode, just to make sure
]
def test_lti20_rest_good_dispatch(self):
"""
Test the good cases for the "dispatch" argument to the LTI 2.0 handler. Anything that does
fit the form user/<anon_id>
"""
for ginput, expected in self.GOOD_DISPATCH_INPUTS:
self.assertEquals(self.xmodule.parse_lti_2_0_handler_suffix(ginput), expected)
BAD_JSON_INPUTS = [
# (bad inputs, error message expected)
([
u"kk", # ValueError
u"{{}", # ValueError
u"{}}", # ValueError
3, # TypeError
{}, # TypeError
], u"Supplied JSON string in request body could not be decoded"),
([
u"3", # valid json, not array or object
u"[]", # valid json, array too small
u"[3, {}]", # valid json, 1st element not an object
], u"Supplied JSON string is a list that does not contain an object as the first element"),
([
u'{"@type": "NOTResult"}', # @type key must have value 'Result'
], u"JSON object does not contain correct @type attribute"),
([
# @context missing
u'{"@type": "Result", "resultScore": 0.1}',
], u"JSON object does not contain required key"),
([
u'''
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"resultScore": 100}''' # score out of range
], u"score value outside the permitted range of 0-1."),
([
u'''
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"resultScore": "1b"}''', # score ValueError
u'''
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"resultScore": {}}''', # score TypeError
], u"Could not convert resultScore to float"),
]
def test_lti20_bad_json(self):
"""
Test that bad json_str to parse_lti_2_0_result_json inputs raise appropriate LTI Error
"""
for error_inputs, error_message in self.BAD_JSON_INPUTS:
for einput in error_inputs:
with self.assertRaisesRegexp(LTIError, error_message):
self.xmodule.parse_lti_2_0_result_json(einput)
GOOD_JSON_INPUTS = [
(u'''
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"resultScore": 0.1}''', u""), # no comment means we expect ""
(u'''
[{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@id": "anon_id:abcdef0123456789",
"resultScore": 0.1}]''', u""), # OK to have array of objects -- just take the first. @id is okay too
(u'''
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"resultScore": 0.1,
"comment": "ಠ益ಠ"}''', u"ಠ益ಠ"), # unicode comment
]
def test_lti20_good_json(self):
"""
Test the parsing of good comments
"""
for json_str, expected_comment in self.GOOD_JSON_INPUTS:
score, comment = self.xmodule.parse_lti_2_0_result_json(json_str)
self.assertEqual(score, 0.1)
self.assertEqual(comment, expected_comment)
GOOD_JSON_PUT = textwrap.dedent(u"""
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@id": "anon_id:abcdef0123456789",
"resultScore": 0.1,
"comment": "ಠ益ಠ"}
""").encode('utf-8')
GOOD_JSON_PUT_LIKE_DELETE = textwrap.dedent(u"""
{"@type": "Result",
"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@id": "anon_id:abcdef0123456789",
"comment": "ಠ益ಠ"}
""").encode('utf-8')
def get_signed_lti20_mock_request(self, body, method=u'PUT'):
"""
Example of signed from LTI 2.0 Provider. Signatures and hashes are example only and won't verify
"""
mock_request = Mock()
mock_request.headers = {
'Content-Type': 'application/vnd.ims.lis.v2.result+json',
'Authorization': (
u'OAuth oauth_nonce="135685044251684026041377608307", '
u'oauth_timestamp="1234567890", oauth_version="1.0", '
u'oauth_signature_method="HMAC-SHA1", '
u'oauth_consumer_key="test_client_key", '
u'oauth_signature="my_signature%3D", '
u'oauth_body_hash="gz+PeJZuF2//n9hNUnDj2v5kN70="'
)
}
mock_request.url = u'http://testurl'
mock_request.http_method = method
mock_request.method = method
mock_request.body = body
return mock_request
USER_STANDIN = Mock()
USER_STANDIN.id = 999
def setup_system_xmodule_mocks_for_lti20_request_test(self):
"""
Helper fn to set up mocking for lti 2.0 request test
"""
self.system.get_real_user = Mock(return_value=self.USER_STANDIN)
self.xmodule.max_score = Mock(return_value=1.0)
self.xmodule.get_client_key_secret = Mock(return_value=('test_client_key', u'test_client_secret'))
self.xmodule.verify_oauth_body_sign = Mock()
def test_lti20_put_like_delete_success(self):
"""
The happy path for LTI 2.0 PUT that acts like a delete
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
SCORE = 0.55 # pylint: disable=invalid-name
COMMENT = u"ಠ益ಠ" # pylint: disable=invalid-name
self.xmodule.module_score = SCORE
self.xmodule.score_comment = COMMENT
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT_LIKE_DELETE)
# Now call the handler
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
# Now assert there's no score
self.assertEqual(response.status_code, 200)
self.assertIsNone(self.xmodule.module_score)
self.assertEqual(self.xmodule.score_comment, u"")
(_, evt_type, called_grade_obj), _ = self.system.publish.call_args
self.assertEqual(called_grade_obj, {'user_id': self.USER_STANDIN.id, 'value': None, 'max_value': None})
self.assertEqual(evt_type, 'grade')
def test_lti20_delete_success(self):
"""
The happy path for LTI 2.0 DELETE
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
SCORE = 0.55 # pylint: disable=invalid-name
COMMENT = u"ಠ益ಠ" # pylint: disable=invalid-name
self.xmodule.module_score = SCORE
self.xmodule.score_comment = COMMENT
mock_request = self.get_signed_lti20_mock_request("", method=u'DELETE')
# Now call the handler
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
# Now assert there's no score
self.assertEqual(response.status_code, 200)
self.assertIsNone(self.xmodule.module_score)
self.assertEqual(self.xmodule.score_comment, u"")
(_, evt_type, called_grade_obj), _ = self.system.publish.call_args
self.assertEqual(called_grade_obj, {'user_id': self.USER_STANDIN.id, 'value': None, 'max_value': None})
self.assertEqual(evt_type, 'grade')
def test_lti20_put_set_score_success(self):
"""
The happy path for LTI 2.0 PUT that sets a score
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
# Now call the handler
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
# Now assert
self.assertEqual(response.status_code, 200)
self.assertEqual(self.xmodule.module_score, 0.1)
self.assertEqual(self.xmodule.score_comment, u"ಠ益ಠ")
(_, evt_type, called_grade_obj), _ = self.system.publish.call_args
self.assertEqual(evt_type, 'grade')
self.assertEqual(called_grade_obj, {'user_id': self.USER_STANDIN.id, 'value': 0.1, 'max_value': 1.0})
def test_lti20_get_no_score_success(self):
"""
The happy path for LTI 2.0 GET when there's no score
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
mock_request = self.get_signed_lti20_mock_request("", method=u'GET')
# Now call the handler
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
# Now assert
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, {"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type": "Result"})
def test_lti20_get_with_score_success(self):
"""
The happy path for LTI 2.0 GET when there is a score
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
SCORE = 0.55 # pylint: disable=invalid-name
COMMENT = u"ಠ益ಠ" # pylint: disable=invalid-name
self.xmodule.module_score = SCORE
self.xmodule.score_comment = COMMENT
mock_request = self.get_signed_lti20_mock_request("", method=u'GET')
# Now call the handler
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
# Now assert
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, {"@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
"@type": "Result",
"resultScore": SCORE,
"comment": COMMENT})
UNSUPPORTED_HTTP_METHODS = ["OPTIONS", "HEAD", "POST", "TRACE", "CONNECT"]
def test_lti20_unsupported_method_error(self):
"""
Test we get a 404 when we don't GET or PUT
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
for bad_method in self.UNSUPPORTED_HTTP_METHODS:
mock_request.method = bad_method
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
self.assertEqual(response.status_code, 404)
def test_lti20_request_handler_bad_headers(self):
"""
Test that we get a 401 when header verification fails
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
self.xmodule.verify_lti_2_0_result_rest_headers = Mock(side_effect=LTIError())
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
self.assertEqual(response.status_code, 401)
def test_lti20_request_handler_bad_dispatch_user(self):
"""
Test that we get a 404 when there's no (or badly formatted) user specified in the url
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, None)
self.assertEqual(response.status_code, 404)
def test_lti20_request_handler_bad_json(self):
"""
Test that we get a 404 when json verification fails
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
self.xmodule.parse_lti_2_0_result_json = Mock(side_effect=LTIError())
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
self.assertEqual(response.status_code, 404)
def test_lti20_request_handler_bad_user(self):
"""
Test that we get a 404 when the supplied user does not exist
"""
self.setup_system_xmodule_mocks_for_lti20_request_test()
self.system.get_real_user = Mock(return_value=None)
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
response = self.xmodule.lti_2_0_result_rest_handler(mock_request, "user/abcd")
self.assertEqual(response.status_code, 404)

View File

@@ -2,21 +2,15 @@
"""Test for LTI Xmodule functional logic."""
from mock import Mock, patch, PropertyMock
import mock
import textwrap
import json
from lxml import etree
import json
from webob.request import Request
from copy import copy
from collections import OrderedDict
import urllib
import oauthlib
import hashlib
import base64
from xmodule.lti_module import LTIDescriptor, LTIError
from xmodule.lti_module import LTIDescriptor
from xmodule.lti_2_util import LTIError
from . import LogicTest
@@ -56,6 +50,7 @@ class LTIModuleTest(LogicTest):
""")
self.system.get_real_user = Mock()
self.system.publish = Mock()
self.system.rebind_noauth_module_to_user = Mock()
self.user_id = self.xmodule.runtime.anonymous_student_id
self.lti_id = self.xmodule.lti_id
@@ -239,6 +234,7 @@ class LTIModuleTest(LogicTest):
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
self.assertEqual(self.xmodule.module_score, float(self.DEFAULTS['grade']))
def test_user_id(self):
expected_user_id = unicode(urllib.quote(self.xmodule.runtime.anonymous_student_id))
@@ -246,13 +242,16 @@ class LTIModuleTest(LogicTest):
self.assertEqual(real_user_id, expected_user_id)
def test_outcome_service_url(self):
expected_outcome_service_url = '{scheme}://{host}{path}'.format(
scheme='http' if self.xmodule.runtime.debug else 'https',
host=self.xmodule.runtime.hostname,
path=self.xmodule.runtime.handler_url(self.xmodule, 'grade_handler', thirdparty=True).rstrip('/?')
)
real_outcome_service_url = self.xmodule.get_outcome_service_url()
self.assertEqual(real_outcome_service_url, expected_outcome_service_url)
mock_url_prefix = 'https://hostname/'
test_service_name = "test_service"
def mock_handler_url(block, handler_name, **kwargs): # pylint: disable=unused-argument
"""Mock function for returning fully-qualified handler urls"""
return mock_url_prefix + handler_name
self.xmodule.runtime.handler_url = Mock(side_effect=mock_handler_url)
real_outcome_service_url = self.xmodule.get_outcome_service_url(service_name=test_service_name)
self.assertEqual(real_outcome_service_url, mock_url_prefix + test_service_name)
def test_resource_link_id(self):
with patch('xmodule.lti_module.LTIModule.location', new_callable=PropertyMock) as mock_location:
@@ -398,13 +397,11 @@ class LTIModuleTest(LogicTest):
def test_max_score(self):
self.xmodule.weight = 100.0
self.xmodule.graded = True
self.assertFalse(self.xmodule.has_score)
self.assertEqual(self.xmodule.max_score(), None)
self.xmodule.has_score = True
self.assertEqual(self.xmodule.max_score(), 100.0)
self.xmodule.graded = False
self.assertEqual(self.xmodule.max_score(), 100.0)
def test_context_id(self):

View File

@@ -3,6 +3,7 @@ from xmodule.x_module import XModule
from xmodule.seq_module import SequenceDescriptor
from xmodule.progress import Progress
from pkg_resources import resource_string
from copy import copy
# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
@@ -20,8 +21,11 @@ class VerticalModule(VerticalFields, XModule):
fragment = Fragment()
contents = []
child_context = {} if not context else copy(context)
child_context['child_of_vertical'] = True
for child in self.get_display_items():
rendered_child = child.render('student_view', context)
rendered_child = child.render('student_view', child_context)
fragment.add_frag_resources(rendered_child)
contents.append({

View File

@@ -1134,7 +1134,7 @@ class XMLParsingSystem(DescriptorSystem):
self.process_xml = process_xml
class ModuleSystem(MetricsMixin,ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method
class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method
"""
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
@@ -1154,7 +1154,7 @@ class ModuleSystem(MetricsMixin,ConfigurableFragmentWrapper, Runtime): # pylint
open_ended_grading_interface=None, s3_interface=None,
cache=None, can_execute_unsafe_code=None, replace_course_urls=None,
replace_jump_to_id_urls=None, error_descriptor_class=None, get_real_user=None,
field_data=None, get_user_role=None,
field_data=None, get_user_role=None, rebind_noauth_module_to_user=None,
**kwargs):
"""
Create a closure around the system environment.
@@ -1213,6 +1213,9 @@ class ModuleSystem(MetricsMixin,ConfigurableFragmentWrapper, Runtime): # pylint
for LMS and Studio.
field_data - the `FieldData` to use for backing XBlock storage.
rebind_noauth_module_to_user - rebinds module bound to AnonymousUser to a real user...used in LTI
modules, which have an anonymous handler, to set legitimate users' data
"""
# Usage_store is unused, and field_data is often supplanted with an
@@ -1251,6 +1254,7 @@ class ModuleSystem(MetricsMixin,ConfigurableFragmentWrapper, Runtime): # pylint
self.get_user_role = get_user_role
self.descriptor_runtime = descriptor_runtime
self.rebind_noauth_module_to_user = rebind_noauth_module_to_user
def get(self, attr):
""" provide uniform access to attributes (like etree)."""

View File

@@ -141,8 +141,6 @@ describe 'ResponseCommentView', ->
spyOn(@view, 'cancelEdit')
spyOn($, "ajax").andCallFake(
(params) =>
expect(params.url._parts.path).toEqual("/courses/edX/999/test/discussion/comments/01234567/update")
expect(params.data.body).toEqual(@updatedBody)
if @ajaxSucceed
params.success()
else
@@ -154,6 +152,8 @@ describe 'ResponseCommentView', ->
@ajaxSucceed = true
@view.update(makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect($.ajax.mostRecentCall.args[0].url._parts.path).toEqual('/courses/edX/999/test/discussion/comments/01234567/update')
expect($.ajax.mostRecentCall.args[0].data.body).toEqual(@updatedBody)
expect(@view.model.get("body")).toEqual(@updatedBody)
expect(@view.cancelEdit).toHaveBeenCalled()
@@ -162,6 +162,8 @@ describe 'ResponseCommentView', ->
@ajaxSucceed = false
@view.update(makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect($.ajax.mostRecentCall.args[0].url._parts.path).toEqual('/courses/edX/999/test/discussion/comments/01234567/update')
expect($.ajax.mostRecentCall.args[0].data.body).toEqual(@updatedBody)
expect(@view.model.get("body")).toEqual(originalBody)
expect(@view.cancelEdit).not.toHaveBeenCalled()
expect(@view.$(".edit-comment-form-errors *").length).toEqual(1)

View File

@@ -2,3 +2,6 @@ import os
# Get the URL of the instance under test
BASE_URL = os.environ.get('test_url', 'http://localhost:8003')
# The URL used for user auth in testing
AUTH_BASE_URL = os.environ.get('test_url', 'http://localhost:8031')

View File

@@ -0,0 +1,76 @@
"""
Auto-auth page (used to automatically log in during testing).
"""
import re
import urllib
from bok_choy.page_object import PageObject
from . import AUTH_BASE_URL
class AutoAuthPage(PageObject):
"""
The automatic authorization page.
When allowed via the django settings file, visiting
this url will create a user and log them in.
"""
def __init__(self, browser, username=None, email=None, password=None, staff=None, course_id=None, roles=None):
"""
Auto-auth is an end-point for HTTP GET requests.
By default, it will create accounts with random user credentials,
but you can also specify credentials using querystring parameters.
`username`, `email`, and `password` are the user's credentials (strings)
`staff` is a boolean indicating whether the user is global staff.
`course_id` is the ID of the course to enroll the student in.
Currently, this has the form "org/number/run"
Note that "global staff" is NOT the same as course staff.
"""
super(AutoAuthPage, self).__init__(browser)
# Create query string parameters if provided
self._params = {}
if username is not None:
self._params['username'] = username
if email is not None:
self._params['email'] = email
if password is not None:
self._params['password'] = password
if staff is not None:
self._params['staff'] = "true" if staff else "false"
if course_id is not None:
self._params['course_id'] = course_id
if roles is not None:
self._params['roles'] = roles
@property
def url(self):
"""
Construct the URL.
"""
url = AUTH_BASE_URL + "/auto_auth"
query_str = urllib.urlencode(self._params)
if query_str:
url += "?" + query_str
return url
def is_browser_on_page(self):
return True
def get_user_id(self):
"""
Finds and returns the user_id
"""
message = self.q(css='BODY').text[0].strip()
match = re.search(r' user_id ([^$]+)$', message)
return match.groups()[0] if match else None

View File

@@ -5,7 +5,7 @@ Tests for discussion pages
from uuid import uuid4
from .helpers import UniqueCourseTest
from ..pages.studio.auto_auth import AutoAuthPage
from ..pages.lms.auto_auth import AutoAuthPage
from ..pages.lms.courseware import CoursewarePage
from ..pages.lms.discussion import (
DiscussionTabSingleThreadPage,

View File

@@ -6,7 +6,7 @@ E2E tests for the LMS.
from unittest import skip
from .helpers import UniqueCourseTest, load_data_str
from ..pages.studio.auto_auth import AutoAuthPage
from ..pages.lms.auto_auth import AutoAuthPage
from ..pages.lms.find_courses import FindCoursesPage
from ..pages.lms.course_about import CourseAboutPage
from ..pages.lms.course_info import CourseInfoPage

View File

@@ -7,7 +7,7 @@ from unittest import skip
from bok_choy.promise import Promise, BrokenPromise
from ..pages.lms.peer_confirm import PeerConfirmPage
from ..pages.studio.auto_auth import AutoAuthPage
from ..pages.lms.auto_auth import AutoAuthPage
from ..pages.lms.course_info import CourseInfoPage
from ..pages.lms.tab_nav import TabNavPage
from ..pages.lms.course_nav import CourseNavPage

View File

@@ -8,7 +8,7 @@ from .helpers import UniqueCourseTest
from ..pages.lms.video import VideoPage
from ..pages.lms.tab_nav import TabNavPage
from ..pages.lms.course_nav import CourseNavPage
from ..pages.studio.auto_auth import AutoAuthPage
from ..pages.lms.auto_auth import AutoAuthPage
from ..pages.lms.course_info import CourseInfoPage
from ..fixtures.course import CourseFixture, XBlockFixtureDesc

View File

@@ -2,7 +2,7 @@
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2011-07-17T12:00",
"display_name": "Toy Course",
"display_name": "Toy Course"
},
"chapter/Overview": {
"display_name": "Overview"

Binary file not shown.

View File

@@ -6,11 +6,12 @@
# Translators:
# abdallah_n <abdoosh00@gmail.com>, 2013
# abdallah.nassif <abdallah_n@hotmail.com>, 2013
# abdallah_n <abdoosh00@gmail.com>, 2013
# abdallah_n <abdoosh00@gmail.com>, 2013-2014
# abdallah.nassif <abdallah_n@hotmail.com>, 2013
# Ahmad Abd Arrahman <mygooglizer@gmail.com>, 2013-2014
# a.nassif <a.nassif@sit-mena.com>, 2013
# a.nassif <a.nassif@sit-mena.com>, 2013
# Hassan05 <hassan.upb@gmail.com>, 2014
# khateeb <eng.elkhteeb@gmail.com>, 2013
# khateeb <eng.elkhteeb@gmail.com>, 2013
# may <may@qordoba.com>, 2013
@@ -40,10 +41,11 @@
# Translators:
# abdallah_n <abdoosh00@gmail.com>, 2013
# abdallah.nassif <abdallah_n@hotmail.com>, 2013
# abdallah_n <abdoosh00@gmail.com>, 2013
# abdallah_n <abdoosh00@gmail.com>, 2013-2014
# abdallah.nassif <abdallah_n@hotmail.com>, 2013
# Ahmad Abd Arrahman <mygooglizer@gmail.com>, 2013-2014
# hani1460 <hani0a@hotmail.com>, 2014
# Hassan05 <hassan.upb@gmail.com>, 2014
# jkfreij <jkfreij@gmail.com>, 2014
# khateeb <eng.elkhteeb@gmail.com>, 2013
# khateeb <eng.elkhteeb@gmail.com>, 2013
@@ -59,9 +61,12 @@
# Translators:
# abdallah_n <abdoosh00@gmail.com>, 2013
# abdallah.nassif <abdallah_n@hotmail.com>, 2013
# abdallah_n <abdoosh00@gmail.com>, 2014
# Safaa_fadl <fadlaoui_safaa@yahoo.fr>, 2014
# Hassan05 <hassan.upb@gmail.com>, 2014
# jkfreij <jkfreij@gmail.com>, 2014
# khateeb <eng.elkhteeb@gmail.com>, 2013
# may <may@qordoba.com>, 2014
# najwan <najwanrousan@gmail.com>, 2013
# sarina <sarina@edx.org>, 2014
# #-#-#-#-# messages.po (edx-platform) #-#-#-#-#
@@ -81,13 +86,16 @@
#
# Translators:
# Almaazon <Ahmed.Almaazon@hotmail.com>, 2014
# Hassan05 <hassan.upb@gmail.com>, 2014
# mabdelhaq <mabdelhaq@qou.edu>, 2014
# SalmaGhazal <salma-ghazal1@hotmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-04-27 11:11-0400\n"
"PO-Revision-Date: 2014-03-11 14:11+0000\n"
"Last-Translator: Almaazon <Ahmed.Almaazon@hotmail.com>\n"
"POT-Creation-Date: 2014-05-02 17:10-0400\n"
"PO-Revision-Date: 2014-05-04 11:18+0000\n"
"Last-Translator: Hassan05 <hassan.upb@gmail.com>\n"
"Language-Team: Arabic (http://www.transifex.com/projects/p/edx-platform/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -250,8 +258,13 @@ msgid "Too many failed login attempts. Try again later."
msgstr ""
#: common/djangoapps/student/views.py lms/templates/provider_login.html
#, fuzzy
msgid "Email or password is incorrect."
msgstr ""
"#-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#\n"
"البريد الإلكتروني أو كلمة السر غير صحيحة\n"
"#-#-#-#-# mako.po (edx-platform) #-#-#-#-#\n"
"البريد الإلكتروني أو كلمة المرور غير صحيحة"
#: common/djangoapps/student/views.py
msgid ""
@@ -847,7 +860,7 @@ msgid "unanswered"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
msgid "queued"
msgid "processing"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
@@ -1412,6 +1425,16 @@ msgstr ""
msgid "A YouTube URL or a link to a file hosted anywhere on the web."
msgstr ""
#. Translators: This is a type of file used for captioning in the video
#. player.
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "SubRip (.srt) file"
msgstr ""
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "Text (.txt) file"
msgstr ""
#: common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/mjtheme/layout.html
msgid "Navigation"
msgstr ""
@@ -2517,6 +2540,8 @@ msgid ""
"Please visit your <a href=\"{dashboard_link}\">dashboard</a> to see your new"
" enrollments."
msgstr ""
"الرجاء زيارة <a href=\"{dashboard_link}\">صفحة المعلومات الرئيسية</a> الخاصة"
" بك للاطلاع على المساقات التي تم تسجيلك بها مؤخراً."
#: lms/djangoapps/shoppingcart/models.py
msgid "[Refund] User-Requested Refund"
@@ -3135,8 +3160,15 @@ msgid "Password Reset Help"
msgstr ""
#: lms/templates/registration/password_reset_confirm.html
#: cms/templates/login.html lms/templates/login-sidebar.html
#: lms/templates/register-sidebar.html
#, fuzzy
msgid "Need Help?"
msgstr "تحتاج إلى مساعدة؟"
msgstr ""
"#-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#\n"
"تحتاج إلى مساعدة؟\n"
"#-#-#-#-# mako.po (edx-platform) #-#-#-#-#\n"
"هل تحتاج مساعدة؟"
#: lms/templates/registration/password_reset_confirm.html
msgid ""
@@ -4486,6 +4518,12 @@ msgid ""
"{platform_name}'s free online MOOCs are interactive and subjects include "
"computer science, public health, and artificial intelligence."
msgstr ""
"{platform_name} هي منشأة غير ربحية تم تأسيسها من قبل الشركاء {Harvard} "
"و{MIT}، الذين تتمثّل مهمتهم في تقديم أفضل مستويات الدراسات العليا وجعلها في "
"متناول الطلاب من جميع الأعمار وفي أي مكان في العالم يتوفّر فيه الاتصال "
"بالإنترنت. إن مساقات {platform_name} الضخمة المتاحة عبر الإنترنت والمقدّمة "
"بشكلٍ مجاني هي مساقات تفاعلية، وتشمل عدة مواد من ضمنها علوم الحاسوب، الصحة "
"العامة، و الذكاء الاصطناعي."
#: lms/templates/footer.html
msgid "&copy; 2014 {platform_name}, some rights reserved."
@@ -4774,7 +4812,7 @@ msgstr ""
#: lms/templates/login.html
msgid "Your email or password is incorrect"
msgstr ""
msgstr "عنوان بريدك الإلكتروني أو كلمة السر غير صحيحين"
#: lms/templates/login.html
msgid ""
@@ -4811,10 +4849,24 @@ msgstr ""
msgid "Sign in with {provider_name}"
msgstr ""
#. Translators: "External resource" means that this learning module is hosted
#. on a platform external to the edX LMS
#: lms/templates/lti.html
msgid "External resource"
msgstr ""
#. Translators: "points" is the student's achieved score on this LTI unit, and
#. "total_points" is the maximum number of points achievable.
#: lms/templates/lti.html
msgid "{points} / {total_points} points"
msgstr ""
#. Translators: "total_points" is the maximum number of points achievable on
#. this LTI unit
#: lms/templates/lti.html
msgid "{total_points} points possible"
msgstr ""
#: lms/templates/lti.html
msgid "View resource in a new window"
msgstr ""
@@ -4824,6 +4876,10 @@ msgid ""
"Please provide launch_url. Click \"Edit\", and fill in the required fields."
msgstr ""
#: lms/templates/lti.html
msgid "Feedback on your work from the grader:"
msgstr ""
#: lms/templates/lti_form.html
msgid "Press to Launch"
msgstr ""
@@ -5127,7 +5183,7 @@ msgstr ""
#: lms/templates/register.html
msgid "Create My {platform_name} Account"
msgstr ""
msgstr "إنشئ حسابي على {platform_name}"
#: lms/templates/register.html
msgid "Welcome!"
@@ -5167,7 +5223,7 @@ msgstr ""
#: lms/templates/register.html lms/templates/verify_student/face_upload.html
msgid "example: Jane Doe"
msgstr ""
msgstr "الإسم الكامل"
#: lms/templates/register.html lms/templates/register.html
msgid "Needed for any certificates you may earn"
@@ -5623,10 +5679,6 @@ msgstr ""
msgid "Download transcript"
msgstr ""
#: lms/templates/video.html lms/templates/video.html
msgid "{file_format}"
msgstr ""
#: lms/templates/video.html
msgid "Download Handout"
msgstr ""
@@ -5902,11 +5954,11 @@ msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Classes Start"
msgstr ""
msgstr "تبدأ المساقات"
#: lms/templates/courseware/course_about.html
msgid "Classes End"
msgstr ""
msgstr "تنتهي المساقات"
#: lms/templates/courseware/course_about.html
msgid "Estimated Effort"
@@ -9768,6 +9820,8 @@ msgid ""
"Thank you for activating your account. You may now sign in and start using "
"edX Studio to author courses."
msgstr ""
"نشكرك على تفعيل حسابك. بإمكانك الآن تسجيل الدخول و البدء باستعمال استوديو "
"edX لتأليف المساقات."
#: cms/templates/activation_invalid.html
msgid "Your account activation is invalid"
@@ -10196,7 +10250,7 @@ msgstr ""
#: cms/templates/export.html
msgid "About Exporting Courses"
msgstr ""
msgstr "عن تصدير المساقات"
#. Translators: ".tar.gz" is a file extension, and should not be translated
#: cms/templates/export.html
@@ -10356,6 +10410,8 @@ msgstr ""
msgid ""
"Studio helps manage your courses online, so you can focus on teaching them"
msgstr ""
"يساعدك الاستوديو على إدارة مساقاتك عبر الإنترنت، مما يمكّنك من التركيز على "
"عملية إعطاء هذه المقرّرات"
#: cms/templates/howitworks.html
msgid "Studio's Many Features"
@@ -10635,6 +10691,8 @@ msgid ""
"Integrating your imported content into this course. This may take a while "
"with larger courses."
msgstr ""
"يتم دمج المحتوى الذي قمت باستيراده في هذا المقرر. قد يستغرق ذلك وقتاً مع "
"المساقات الكبيرة. "
#: cms/templates/import.html
msgid "Success"
@@ -10714,7 +10772,7 @@ msgstr ""
#: cms/templates/index.html cms/templates/index.html
#: cms/templates/widgets/header.html
msgid "My Courses"
msgstr ""
msgstr "مساقاتي"
#: cms/templates/index.html
msgid "New Course"
@@ -10731,6 +10789,7 @@ msgstr ""
#: cms/templates/index.html
msgid "Here are all of the courses you currently have access to in Studio:"
msgstr ""
"ستجد هنا قائمة بجميع المساقات التي لديك صلاحية الدخول عليها في الاستوديو:"
#: cms/templates/index.html
msgid "You currently aren't associated with any Studio Courses."
@@ -10817,7 +10876,7 @@ msgstr ""
#: cms/templates/index.html
msgid "Are you staff on an existing Studio course?"
msgstr ""
msgstr "هل أنت ضمن طاقم أي من مساقات الاستوديو؟"
#: cms/templates/index.html
msgid ""
@@ -10846,6 +10905,10 @@ msgid ""
"evaluate your request and provide you feedback within 24 hours during the "
"work week."
msgstr ""
"EDX استوديو هو حل برمجي مستضاف نوفّره لشركاء xConsortium وللضيوف المحددين. "
"تظهر المساقات التي كنت عضواً في فريق إنشائها في الأعلى وبإمكانك تعديلها، في "
"الأثناء التي يتم خلالها منح امتيازات صاحب المقرر من قبل EDX. سيقوم فريقنا "
"بتقييم طلبك وإعطائك إجابةً بشأنه في غضون 24 ساعة خلال أسبوع العمل."
#: cms/templates/index.html cms/templates/index.html cms/templates/index.html
msgid "Your Course Creator Request Status:"
@@ -10853,7 +10916,7 @@ msgstr ""
#: cms/templates/index.html
msgid "Request the Ability to Create Courses"
msgstr ""
msgstr "طلب صلاحية إنشاء المساقات"
#: cms/templates/index.html cms/templates/index.html
msgid "Your Course Creator Request Status"
@@ -10866,6 +10929,10 @@ msgid ""
"edit, while course creator privileges are granted by edX. Our team is has "
"completed evaluating your request."
msgstr ""
"EDX استوديو هو حل برمجي مستضاف نوفّره لشركاء xConsortium وللضيوف المحددين. "
"تظهر المساقات التي كنت عضواً في فريق إنشائها في الأعلى وبإمكانك تعديلها، في "
"الأثناء التي يتم خلالها منح امتيازات صاحب المقرر من قبل EDX. لقد أنهى فريقنا"
" عملية تقييم طلبك."
#: cms/templates/index.html cms/templates/index.html
msgid "Your Course Creator request is:"
@@ -10887,6 +10954,10 @@ msgid ""
"edit, while course creator privileges are granted by edX. Our team is "
"currently evaluating your request."
msgstr ""
"EDX استوديو هو حل برمجي مستضاف نوفّره لشركاء xConsortium وللضيوف المحددين. "
"تظهر المساقات التي كنت عضواً في فريق إنشائها في الأعلى وبإمكانك تعديلها، في "
"الأثناء التي يتم خلالها منح امتيازات صاحب المقرر من قبل EDX. طلبك قيد "
"التقييم الآن من قبل فريقنا."
#: cms/templates/index.html
msgid ""
@@ -10896,7 +10967,7 @@ msgstr ""
#: cms/templates/index.html cms/templates/index.html
msgid "Need help?"
msgstr ""
msgstr "هل تحتاج إلى مساعدة؟"
#: cms/templates/index.html
msgid ""
@@ -10914,7 +10985,7 @@ msgstr ""
#: cms/templates/index.html cms/templates/index.html cms/templates/index.html
msgid "Can I create courses in Studio?"
msgstr ""
msgstr "هل أستطيع إنشاء مساقات باستخدام برنامج استوديو؟"
#: cms/templates/index.html
msgid "In order to create courses in Studio, you must"
@@ -10932,7 +11003,7 @@ msgstr ""
#: cms/templates/index.html
msgid "Your request to author courses in studio has been denied. Please"
msgstr ""
msgstr "الطلب الذي تقدمت به لتأليف المساقات في الاستوديو قد رفض. الرجاء"
#: cms/templates/index.html
msgid "contact edX Staff with further questions"
@@ -11251,7 +11322,7 @@ msgstr ""
#: cms/templates/register.html
msgid "Create My Account &amp; Start Authoring Courses"
msgstr ""
msgstr "قم بإنشاء حسابي و ابدأ بتأليف المساقات"
#: cms/templates/register.html
msgid "Common Studio Questions"
@@ -11960,6 +12031,9 @@ msgid ""
"Take advantage of our documentation, help center, as well as our edX101 "
"introduction course for course authors."
msgstr ""
"هل تحتاج مساعدة في استعمال برنامج استوديو؟ إن وضع مقررٍ ما هو أمرٌ معقد، "
"لذلك فنحن هنا للمساعدة. استفد من التوثيق الكامل الذي نوفّره، ومن مركز "
"المساعدة، وأيضاً من مقرر edX101 التمهيدي لمؤلفي المساقات."
#: cms/templates/widgets/sock.html
msgid "Download Studio Documentation"

View File

@@ -36,7 +36,7 @@ msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-04-27 11:10-0400\n"
"POT-Creation-Date: 2014-05-02 17:09-0400\n"
"PO-Revision-Date: 2014-04-26 09:40+0000\n"
"Last-Translator: may <may@qordoba.com>\n"
"Language-Team: Arabic (http://www.transifex.com/projects/p/edx-platform/language/ar/)\n"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,1712 @@
# #-#-#-#-# djangojs-partial.po (edx-platform) #-#-#-#-#
# edX community translations have been downloaded from Azerbaijani (http://www.transifex.com/projects/p/edx-platform/language/az/).
# Copyright (C) 2014 EdX
# This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.
#
# Translators:
# #-#-#-#-# djangojs-studio.po (edx-platform) #-#-#-#-#
# edX translation file.
# Copyright (C) 2014 EdX
# This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-05-02 17:09-0400\n"
"PO-Revision-Date: 2014-04-24 13:00+0000\n"
"Last-Translator: sarina <sarina@edx.org>\n"
"Language-Team: Azerbaijani (http://www.transifex.com/projects/p/edx-platform/language/az/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: az\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: cms/static/coffee/src/views/tabs.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/modals/edit_xblock.js
#: common/static/coffee/src/discussion/utils.js
msgid "OK"
msgstr ""
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/unit.js
#: cms/static/js/base.js cms/static/js/views/asset.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/show_textbook.js cms/static/js/views/validation.js
#: cms/static/js/views/modals/base_modal.js
#: cms/static/js/views/pages/container.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Cancel"
msgstr ""
#: cms/static/js/base.js lms/static/js/verify_student/photocapture.js
msgid "This link will open in a new browser window/tab"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Show Annotations"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Hide Annotations"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Expand Instructions"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Collapse Instructions"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Commentary"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/annotatable/display.js
msgid "Reply to Annotation"
msgstr ""
#. Translators: %(earned)s is the number of points earned. %(total)s is the
#. total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of
#. points will always be at least 1. We pluralize based on the total number of
#. points (example: 0/1 point; 1/2 points);
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "(%(earned)s/%(possible)s point)"
msgid_plural "(%(earned)s/%(possible)s points)"
msgstr[0] ""
msgstr[1] ""
#. Translators: %(num_points)s is the number of points possible (examples: 1,
#. 3, 10). There will always be at least 1 point possible.;
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "(%(num_points)s point possible)"
msgid_plural "(%(num_points)s points possible)"
msgstr[0] ""
msgstr[1] ""
#: common/lib/xmodule/xmodule/js/src/capa/display.js
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Answer:"
msgstr ""
#. Translators: the word Answer here refers to the answer to a problem the
#. student must solve.;
#: common/lib/xmodule/xmodule/js/src/capa/display.js
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Hide Answer"
msgstr ""
#. Translators: the word Answer here refers to the answer to a problem the
#. student must solve.;
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Show Answer"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Reveal Answer"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Answer hidden"
msgstr ""
#. Translators: the word unanswered here is about answering a problem the
#. student must solve.;
#: common/lib/xmodule/xmodule/js/src/capa/display.js
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "unanswered"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/capa/display.js
msgid "Status: unsubmitted"
msgstr ""
#. Translators: A "rating" is a score a student gives to indicate how well
#. they feel they were graded on this problem
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "You need to pick a rating before you can submit."
msgstr ""
#. Translators: this message appears when transitioning between openended
#. grading
#. types (i.e. self assesment to peer assessment). Sometimes, if a student
#. did not perform well at one step, they cannot move on to the next one.
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Your score did not meet the criteria to move to the next step."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Submit"
msgstr ""
#. Translators: one clicks this button after one has finished filling out the
#. grading
#. form for an openended assessment
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Submit assessment"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid ""
"Your response has been submitted. Please check back later for your grade."
msgstr ""
#. Translators: this button is clicked to submit a student's rating of
#. an evaluator's assessment
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Submit post-assessment"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Answer saved, but not yet submitted."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid ""
"Please confirm that you wish to submit your work. You will not be able to "
"make any changes after submitting."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid ""
"You are trying to upload a file that is too large for our system. Please "
"choose a file under 2MB or paste a link to it into the answer box."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid ""
"Are you sure you want to remove your previous response to this question?"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Moved to next step."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid ""
"File uploads are required for this question, but are not supported in your "
"browser. Try the newest version of Google Chrome. Alternatively, if you have"
" uploaded the image to another website, you can paste a link to it into the "
"answer box."
msgstr ""
#. Translators: "Show Question" is some text that, when clicked, shows a
#. question's
#. content that had been hidden
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Show Question"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
#: common/lib/xmodule/xmodule/js/src/combinedopenended/display.js
msgid "Hide Question"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/sequence/display.js
msgid ""
"Sequence error! Cannot navigate to tab %(tab_name)s in the current "
"SequenceModule. Please contact the course staff."
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/04_video_control.js
msgid "Video slider"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/04_video_control.js
msgid "Pause"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/04_video_control.js
msgid "Play"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/04_video_control.js
msgid "Fill browser"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/04_video_control.js
msgid "Exit full browser"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
msgid "HD on"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
msgid "HD off"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
msgid "Video position"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
msgid "Video ended"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
msgid "%(value)s hour"
msgid_plural "%(value)s hours"
msgstr[0] ""
msgstr[1] ""
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
msgid "%(value)s minute"
msgid_plural "%(value)s minutes"
msgstr[0] ""
msgstr[1] ""
#: common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
msgid "%(value)s second"
msgid_plural "%(value)s seconds"
msgstr[0] ""
msgstr[1] ""
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Volume"
msgstr ""
#. Translators: Volume level equals 0%.
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Muted"
msgstr ""
#. Translators: Volume level in range (0,20]%
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Very low"
msgstr ""
#. Translators: Volume level in range (20,40]%
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Low"
msgstr ""
#. Translators: Volume level in range (40,60]%
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Average"
msgstr ""
#. Translators: Volume level in range (60,80]%
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Loud"
msgstr ""
#. Translators: Volume level in range (80,100)%
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Very loud"
msgstr ""
#. Translators: Volume level equals 100%.
#: common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
msgid "Maximum"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
msgid "Caption will be displayed when "
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
msgid "Turn on captions"
msgstr ""
#: common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
msgid "Turn off captions"
msgstr ""
#: common/static/coffee/src/discussion/discussion_module_view.js
#: common/static/coffee/src/discussion/discussion_module_view.js
msgid "Hide Discussion"
msgstr ""
#: common/static/coffee/src/discussion/discussion_module_view.js
msgid "Show Discussion"
msgstr ""
#: common/static/coffee/src/discussion/discussion_module_view.js
#: common/static/coffee/src/discussion/discussion_module_view.js
#: common/static/coffee/src/discussion/utils.js
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
#: common/static/coffee/src/discussion/views/response_comment_view.js
msgid "Sorry"
msgstr ""
#: common/static/coffee/src/discussion/discussion_module_view.js
msgid "We had some trouble loading the discussion. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/discussion_module_view.js
msgid ""
"We had some trouble loading the threads you requested. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/utils.js
msgid "Loading content"
msgstr ""
#: common/static/coffee/src/discussion/utils.js
msgid ""
"We had some trouble processing your request. Please ensure you have copied "
"any unsaved work and then reload the page."
msgstr ""
#: common/static/coffee/src/discussion/utils.js
msgid "We had some trouble processing your request. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/utils.js
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/new_post_view.js
#: common/static/coffee/src/discussion/views/new_post_view.js
#: common/static/coffee/src/discussion/views/new_post_view.js
msgid "…"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "Close"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "Open"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "remove vote"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "vote"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "vote (click to remove your vote)"
msgid_plural "votes (click to remove your vote)"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "vote (click to vote)"
msgid_plural "votes (click to vote)"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
msgid "Load more"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
msgid "Loading more threads"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
msgid "We had some trouble loading more threads. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
msgid "%(unread_count)s new comment"
msgid_plural "%(unread_count)s new comments"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
msgid "Loading thread list"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
msgid "Click to remove report"
msgstr ""
#. Translators: The text between start_sr_span and end_span is not shown
#. in most browsers but will be read by screen readers.
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
#: common/static/coffee/src/discussion/views/thread_response_show_view.js
msgid "Misuse Reported%(start_sr_span)s, click to remove report%(end_span)s"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/thread_response_show_view.js
msgid "Report Misuse"
msgstr ""
#. Translators: The text between start_sr_span and end_span is not shown
#. in most browsers but will be read by screen readers.
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
msgid "Pinned%(start_sr_span)s, click to unpin%(end_span)s"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
msgid "Click to unpin"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
msgid "Pinned"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_show_view.js
msgid "Pin Thread"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid ""
"The thread you selected has been deleted. Please select another thread."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "We had some trouble loading responses. Please reload the page."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "We had some trouble loading more responses. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "%(numResponses)s response"
msgid_plural "%(numResponses)s responses"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "Showing all responses"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "Showing first response"
msgid_plural "Showing first %(numResponses)s responses"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "Load all responses"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "Load next %(numResponses)s responses"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "Are you sure you want to delete this post?"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
msgid "We had some trouble loading the page you requested. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
msgid "anonymous"
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/thread_response_show_view.js
msgid "staff"
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/thread_response_show_view.js
msgid "Community TA"
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
#: common/static/coffee/src/discussion/views/thread_response_show_view.js
msgid "Misuse Reported, click to remove report"
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_view.js
msgid "Are you sure you want to delete this comment?"
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_view.js
msgid "We had some trouble deleting this comment. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/thread_response_view.js
msgid "Are you sure you want to delete this response?"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%s ago"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%s from now"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "less than a minute"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "about a minute"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
msgstr[1] ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "about an hour"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "about %d hour"
msgid_plural "about %d hours"
msgstr[0] ""
msgstr[1] ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "a day"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%d day"
msgid_plural "%d days"
msgstr[0] ""
msgstr[1] ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "about a month"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%d month"
msgid_plural "%d months"
msgstr[0] ""
msgstr[1] ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "about a year"
msgstr ""
#: common/static/js/src/jquery.timeago.locale.js
msgid "%d year"
msgid_plural "%d years"
msgstr[0] ""
msgstr[1] ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Available %s"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid ""
"This is the list of available %s. You may choose some by selecting them in "
"the box below and then clicking the \"Choose\" arrow between the two boxes."
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Type into this box to filter down the list of available %s."
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Filter"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Choose all"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Click to choose all %s at once."
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Choose"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Remove"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Chosen %s"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid ""
"This is the list of chosen %s. You may remove some by selecting them in the "
"box below and then clicking the \"Remove\" arrow between the two boxes."
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Remove all"
msgstr ""
#: lms/static/admin/js/SelectFilter2.js
msgid "Click to remove all chosen %s at once."
msgstr ""
#: lms/static/admin/js/actions.js lms/static/admin/js/actions.min.js
msgid "%(sel)s of %(cnt)s selected"
msgid_plural "%(sel)s of %(cnt)s selected"
msgstr[0] ""
msgstr[1] ""
#: lms/static/admin/js/actions.js lms/static/admin/js/actions.min.js
msgid ""
"You have unsaved changes on individual editable fields. If you run an "
"action, your unsaved changes will be lost."
msgstr ""
#: lms/static/admin/js/actions.js lms/static/admin/js/actions.min.js
msgid ""
"You have selected an action, but you haven't saved your changes to "
"individual fields yet. Please click OK to save. You'll need to re-run the "
"action."
msgstr ""
#: lms/static/admin/js/actions.js lms/static/admin/js/actions.min.js
msgid ""
"You have selected an action, and you haven't made any changes on individual "
"fields. You're probably looking for the Go button rather than the Save "
"button."
msgstr ""
#. Translators: the names of months, keep the pipe (|) separators.
#: lms/static/admin/js/calendar.js lms/static/admin/js/dateparse.js
msgid ""
"January|February|March|April|May|June|July|August|September|October|November|December"
msgstr ""
#. Translators: abbreviations for days of the week, keep the pipe (|)
#. separators.
#: lms/static/admin/js/calendar.js
msgid "S|M|T|W|T|F|S"
msgstr ""
#: lms/static/admin/js/collapse.js lms/static/admin/js/collapse.js.c
#: lms/static/admin/js/collapse.min.js
msgid "Show"
msgstr ""
#: lms/static/admin/js/collapse.js lms/static/admin/js/collapse.min.js
msgid "Hide"
msgstr ""
#. Translators: the names of days, keep the pipe (|) separators.
#: lms/static/admin/js/dateparse.js
msgid "Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Now"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Clock"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Choose a time"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Midnight"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "6 a.m."
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Noon"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Today"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Calendar"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Yesterday"
msgstr ""
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Tomorrow"
msgstr ""
#: lms/static/coffee/src/calculator.js
msgid "Open Calculator"
msgstr ""
#: lms/static/coffee/src/calculator.js
msgid "Close Calculator"
msgstr ""
#: lms/static/coffee/src/customwmd.js
msgid "Preview"
msgstr ""
#: lms/static/coffee/src/customwmd.js
msgid "Post body"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/analytics.js
msgid "Error fetching distribution."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/analytics.js
msgid "Unavailable metric display."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/analytics.js
msgid "Error fetching grade distributions."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/analytics.js
msgid "Last Updated: <%= timestamp %>"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/analytics.js
msgid "<%= num_students %> students scored."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid "Loading..."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid "Error getting student list."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid "Error retrieving grading configuration."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid "Error generating grades. Please try again."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid "File Name"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/data_download.js
msgid ""
"Links are generated on demand and expire within 5 minutes due to the "
"sensitive nature of student information."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Username"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Email"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Revoke access"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Enter username or email"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Please enter a username or email."
msgstr ""
#. Translators: A rolename appears this sentence.;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Error fetching list for role"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Error changing user's permissions."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Could not find a user with username or email address '<%= identifier %>'."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Error: User '<%= username %>' has not yet activated their account. Users "
"must create and activate their accounts before they can be assigned a role."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Error: You cannot remove yourself from the Instructor group!"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Error adding/removing users as beta testers."
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users were successfully added as beta testers:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users were successfully removed as beta testers:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users were not added as beta testers:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users were not removed as beta testers:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Users must create and activate their account before they can be promoted to "
"beta tester."
msgstr ""
#. Translators: A list of identifiers (which are email addresses and/or
#. usernames) appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Could not find users associated with the following identifiers:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Error enrolling/unenrolling users."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "The following email addresses and/or usernames are invalid:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Successfully enrolled and sent email to the following users:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Successfully enrolled the following users:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Successfully sent enrollment emails to the following users. They will be "
"allowed to enroll once they register:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users will be allowed to enroll once they register:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Successfully sent enrollment emails to the following users. They will be "
"enrolled once they register:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "These users will be enrolled once they register:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"Emails successfully sent. The following users are no longer enrolled in the "
"course:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "The following users are no longer enrolled in the course:"
msgstr ""
#. Translators: A list of users appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid ""
"These users were not affiliated with the course so could not be unenrolled:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "Your message must have a subject."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "Your message cannot be blank."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "Your email was successfully queued for sending."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid ""
"You are about to send an email titled '<%= subject %>' to yourself. Is this "
"OK?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid ""
"You are about to send an email titled '<%= subject %>' to everyone who is "
"staff or instructor on this course. Is this OK?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid ""
"You are about to send an email titled '<%= subject %>' to ALL (everyone who "
"is enrolled in this course as student, staff, or instructor). Is this OK?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid ""
"Your email was successfully queued for sending. Please note that for large "
"classes, it may take up to an hour (or more, if other courses are "
"simultaneously sending email) to send all emails."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "Error sending email."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "There is no email history for this course."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/send_email.js
msgid "There was an error obtaining email task history for this course."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Please enter a student email address or username."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error getting student progress url for '<%= student_id %>'. Check that the "
"student identifier is spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Please enter a problem urlname."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Success! Problem attempts reset for problem '<%= problem_id %>' and student "
"'<%= student_id %>'."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error resetting problem attempts for problem '<%= problem_id %>' and student"
" '<%= student_id %>'. Check that the problem and student identifiers are "
"spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Delete student '<%= student_id %>'s state on problem '<%= problem_id %>'?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error deleting student '<%= student_id %>'s state on problem '<%= problem_id"
" %>'. Check that the problem and student identifiers are spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Module state successfully deleted."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Started rescore problem task for problem '<%= problem_id %>' and student "
"'<%= student_id %>'. Click the 'Show Background Task History for Student' "
"button to see the status of the task."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error starting a task to rescore problem '<%= problem_id %>' for student "
"'<%= student_id %>'. Check that the problem and student identifiers are "
"spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error getting task history for problem '<%= problem_id %>' and student '<%= "
"student_id %>'. Check that the problem and student identifiers are spelled "
"correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Reset attempts for all students on problem '<%= problem_id %>'?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Successfully started task to reset attempts for problem '<%= problem_id %>'."
" Click the 'Show Background Task History for Problem' button to see the "
"status of the task."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error starting a task to reset attempts for all students on problem '<%= "
"problem_id %>'. Check that the problem identifier is spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Rescore problem '<%= problem_id %>' for all students?"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Successfully started task to rescore problem '<%= problem_id %>' for all "
"students. Click the 'Show Background Task History for Problem' button to see"
" the status of the task."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid ""
"Error starting a task to rescore problem '<%= problem_id %>'. Check that the"
" problem identifier is spelled correctly."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/student_admin.js
msgid "Error listing task history for this student and problem."
msgstr ""
#. Translators: a "Task" is a background process such as grading students or
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Task Type"
msgstr ""
#. Translators: a "Task" is a background process such as grading students or
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Task inputs"
msgstr ""
#. Translators: a "Task" is a background process such as grading students or
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Task ID"
msgstr ""
#. Translators: a "Requester" is a username that requested a task such as
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Requester"
msgstr ""
#. Translators: A timestamp of when a task (eg, sending email) was submitted
#. appears after this
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Submitted"
msgstr ""
#. Translators: The length of a task (eg, sending email) in seconds appears
#. this
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Duration (sec)"
msgstr ""
#. Translators: The state (eg, "In progress") of a task (eg, sending email)
#. appears after this.
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "State"
msgstr ""
#. Translators: a "Task" is a background process such as grading students or
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Task Status"
msgstr ""
#. Translators: a "Task" is a background process such as grading students or
#. sending email
#: lms/static/coffee/src/instructor_dashboard/util.js
msgid "Task Progress"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Grades saved. Fetching the next submission to grade."
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Problem Name"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Graded"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Available to Grade"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Required"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Progress"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Back to problem list"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Try loading again"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "<%= num %> available "
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "<%= num %> graded "
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "<%= num %> more needed to start ML"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "Re-check for submissions"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "System got into invalid state: <%= state %>"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "System got into invalid state for submission: "
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "(Hide)"
msgstr ""
#: lms/static/coffee/src/staff_grading/staff_grading.js
msgid "(Show)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Insert Hyperlink"
msgstr ""
#. Translators: Please keep the quotation marks (") around this text
#: lms/static/js/Markdown.Editor.js lms/static/js/Markdown.Editor.js.c
msgid "\"optional title\""
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Insert Image (upload file or type url)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Markdown Editing Help"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Bold (Ctrl+B)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Italic (Ctrl+I)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Hyperlink (Ctrl+L)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Blockquote (Ctrl+Q)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Code Sample (Ctrl+K)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Image (Ctrl+G)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Numbered List (Ctrl+O)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Bulleted List (Ctrl+U)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Heading (Ctrl+H)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Horizontal Rule (Ctrl+R)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Undo (Ctrl+Z)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Redo (Ctrl+Y)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Redo (Ctrl+Shift+Z)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "strong text"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "emphasized text"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "enter image description here"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "enter link description here"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Blockquote"
msgstr ""
#: lms/static/js/Markdown.Editor.js lms/static/js/Markdown.Editor.js
msgid "enter code here"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "List item"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid "Heading"
msgstr ""
#: lms/templates/class_dashboard/all_section_metrics.js
#: lms/templates/class_dashboard/all_section_metrics.js
msgid "Unable to retrieve data, please try again later."
msgstr ""
#: lms/templates/class_dashboard/d3_stacked_bar_graph.js
msgid "Number of Students"
msgstr ""
#: cms/static/coffee/src/main.js
msgid ""
"This may be happening because of an error with our server or your internet "
"connection. Try refreshing the page or making sure you are online."
msgstr ""
#: cms/static/coffee/src/main.js
msgid "Studio's having trouble saving your work"
msgstr ""
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/tabs.js
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/xblock/cms.runtime.v1.js
#: cms/static/js/models/section.js cms/static/js/utils/drag_and_drop.js
#: cms/static/js/views/asset.js cms/static/js/views/course_info_handout.js
#: cms/static/js/views/course_info_update.js cms/static/js/views/overview.js
#: cms/static/js/views/xblock_editor.js
msgid "Saving&hellip;"
msgstr ""
#: cms/static/coffee/src/views/tabs.js
msgid "Delete Component Confirmation"
msgstr ""
#: cms/static/coffee/src/views/tabs.js
msgid ""
"Are you sure you want to delete this component? This action cannot be "
"undone."
msgstr ""
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/unit.js
#: cms/static/js/base.js cms/static/js/views/course_info_update.js
#: cms/static/js/views/pages/container.js
msgid "Deleting&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js
msgid "Adding&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Duplicating&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Delete this component?"
msgstr ""
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Deleting this component is permanent and cannot be undone."
msgstr ""
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Yes, delete this component"
msgstr ""
#: cms/static/js/base.js
msgid "This link will open in a modal window"
msgstr ""
#: cms/static/js/base.js
msgid "Delete this "
msgstr ""
#: cms/static/js/base.js
msgid "Deleting this "
msgstr ""
#: cms/static/js/base.js
msgid "Yes, delete this "
msgstr ""
#: cms/static/js/index.js
msgid "Please do not use any spaces in this field."
msgstr ""
#: cms/static/js/index.js
msgid "Please do not use any spaces or special characters in this field."
msgstr ""
#: cms/static/js/index.js
msgid ""
"The combined length of the organization, course number, and course run "
"fields cannot be more than 65 characters."
msgstr ""
#: cms/static/js/index.js
msgid "Required field."
msgstr ""
#: cms/static/js/sock.js
msgid "Hide Studio Help"
msgstr ""
#: cms/static/js/sock.js
msgid "Looking for Help with Studio?"
msgstr ""
#: cms/static/js/models/course.js cms/static/js/models/section.js
msgid "You must specify a name"
msgstr ""
#: cms/static/js/models/uploads.js
msgid ""
"Only <%= fileTypes %> files can be uploaded. Please select a file ending in "
"<%= fileExtensions %> to upload."
msgstr ""
#: cms/static/js/models/uploads.js
msgid "or"
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "The course must have an assigned start date."
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "The course end date cannot be before the course start date."
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "The course start date cannot be before the enrollment start date."
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "The enrollment start date cannot be after the enrollment end date."
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "The enrollment end date cannot be after the course end date."
msgstr ""
#: cms/static/js/models/settings/course_details.js
msgid "Key should only contain letters, numbers, _, or -"
msgstr ""
#: cms/static/js/models/settings/course_grader.js
msgid "There's already another assignment type with this name."
msgstr ""
#: cms/static/js/models/settings/course_grader.js
msgid "Please enter an integer between 0 and 100."
msgstr ""
#: cms/static/js/models/settings/course_grader.js
msgid "Please enter an integer greater than 0."
msgstr ""
#: cms/static/js/models/settings/course_grader.js
msgid "Please enter non-negative integer."
msgstr ""
#: cms/static/js/models/settings/course_grader.js
msgid "Cannot drop more <% attrs.types %> than will assigned."
msgstr ""
#: cms/static/js/models/settings/course_grading_policy.js
msgid "Grace period must be specified in HH:MM format."
msgstr ""
#: cms/static/js/views/asset.js
msgid "Delete File Confirmation"
msgstr ""
#: cms/static/js/views/asset.js
msgid ""
"Are you sure you wish to delete this item. It cannot be reversed!\n"
"\n"
"Also any content that links/refers to this item will no longer work (e.g. broken images and/or links)"
msgstr ""
#: cms/static/js/views/asset.js cms/static/js/views/show_textbook.js
msgid "Delete"
msgstr ""
#: cms/static/js/views/asset.js
msgid "Your file has been deleted."
msgstr ""
#: cms/static/js/views/assets.js
msgid "Name"
msgstr ""
#: cms/static/js/views/assets.js
msgid "Date Added"
msgstr ""
#: cms/static/js/views/course_info_update.js
msgid "Are you sure you want to delete this update?"
msgstr ""
#: cms/static/js/views/course_info_update.js
msgid "This action cannot be undone."
msgstr ""
#: cms/static/js/views/edit_chapter.js
msgid "Upload a new PDF to “<%= name %>”"
msgstr ""
#: cms/static/js/views/edit_chapter.js
msgid "Please select a PDF file to upload."
msgstr ""
#: cms/static/js/views/edit_textbook.js
#: cms/static/js/views/overview_assignment_grader.js
msgid "Saving"
msgstr ""
#: cms/static/js/views/import.js
msgid "There was an error with the upload"
msgstr ""
#: cms/static/js/views/import.js
msgid ""
"File format not supported. Please upload a file with a <code>tar.gz</code> "
"extension."
msgstr ""
#: cms/static/js/views/metadata.js
msgid "Upload File"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Collapse All Sections"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Expand All Sections"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Release date:"
msgstr ""
#: cms/static/js/views/overview.js
msgid "{month}/{day}/{year} at {hour}:{minute} UTC"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Edit section release date"
msgstr ""
#: cms/static/js/views/overview_assignment_grader.js
msgid "Not Graded"
msgstr ""
#: cms/static/js/views/paging.js
msgid "ascending"
msgstr ""
#: cms/static/js/views/paging.js
msgid "descending"
msgstr ""
#: cms/static/js/views/paging_header.js
msgid ""
"Showing %(current_span)s%(start)s-%(end)s%(end_span)s out of "
"%(total_span)s%(total)s total%(end_span)s, sorted by "
"%(order_span)s%(sort_order)s%(end_span)s %(sort_direction)s"
msgstr ""
#: cms/static/js/views/section_edit.js
msgid "Your change could not be saved"
msgstr ""
#: cms/static/js/views/section_edit.js
msgid "Return and resolve this issue"
msgstr ""
#: cms/static/js/views/show_textbook.js
msgid "Delete “<%= name %>”?"
msgstr ""
#: cms/static/js/views/show_textbook.js
msgid ""
"Deleting a textbook cannot be undone and once deleted any reference to it in"
" your courseware's navigation will also be removed."
msgstr ""
#: cms/static/js/views/show_textbook.js
msgid "Deleting"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "Upload"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "We're sorry, there was an error"
msgstr ""
#: cms/static/js/views/validation.js
msgid "You've made some changes"
msgstr ""
#: cms/static/js/views/validation.js
msgid "Your changes will not take effect until you save your progress."
msgstr ""
#: cms/static/js/views/validation.js
msgid "You've made some changes, but there are some errors"
msgstr ""
#: cms/static/js/views/validation.js
msgid ""
"Please address the errors on this page first, and then save your progress."
msgstr ""
#: cms/static/js/views/validation.js
msgid "Save Changes"
msgstr ""
#: cms/static/js/views/validation.js
msgid "Your changes have been saved."
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Editor"
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Settings"
msgstr ""
#: cms/static/js/views/modals/base_modal.js
msgid "Save"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Component"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Editing: %(title)s"
msgstr ""
#: cms/static/js/views/settings/advanced.js
msgid ""
"Your changes will not take effect until you save your progress. Take care "
"with key and value formatting, as validation is not implemented."
msgstr ""
#: cms/static/js/views/settings/advanced.js
msgid "Your policy changes have been saved."
msgstr ""
#: cms/static/js/views/settings/advanced.js
msgid ""
"Please note that validation of your policy key and value pairs is not "
"currently in place yet. If you are having difficulties, please review your "
"policy pairs."
msgstr ""
#: cms/static/js/views/settings/main.js
msgid "Upload your course image."
msgstr ""
#: cms/static/js/views/settings/main.js
msgid "Files must be in JPEG or PNG format."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid ""
"Sorry, there was an error parsing the subtitles that you uploaded. Please "
"check the format and try again."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid "Upload translation"
msgstr ""

View File

@@ -38,7 +38,7 @@ msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-03-31 09:26-0400\n"
"POT-Creation-Date: 2014-05-02 17:10-0400\n"
"PO-Revision-Date: 2014-02-06 03:04+0000\n"
"Last-Translator: nedbat <ned@edx.org>\n"
"Language-Team: Bulgarian (Bulgaria) (http://www.transifex.com/projects/p/edx-platform/language/bg_BG/)\n"
@@ -171,6 +171,16 @@ msgstr ""
msgid "Enrollment action is invalid"
msgstr ""
#. Translators: provider_name is the name of an external, third-party user
#. authentication service (like
#. Google or LinkedIn).
#: common/djangoapps/student/views.py
msgid ""
"There is no {platform_name} account associated with your {provider_name} "
"account. Please use your {platform_name} credentials or pick another "
"provider."
msgstr ""
#: common/djangoapps/student/views.py
msgid "There was an error receiving your login information. Please email us."
msgstr ""
@@ -181,6 +191,13 @@ msgid ""
"Try again later."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"Your password has expired due to password policy on this account. You must "
"reset your password before you can log in again. Please click the Forgot "
"Password\" link on this page to reset your password before logging in again."
msgstr ""
#: common/djangoapps/student/views.py
msgid "Too many failed login attempts. Try again later."
msgstr ""
@@ -307,7 +324,7 @@ msgstr ""
msgid "Username should only consist of A-Z and 0-9, with no spaces."
msgstr ""
#: common/djangoapps/student/views.py
#: common/djangoapps/student/views.py common/djangoapps/student/views.py
msgid "Password: "
msgstr ""
@@ -319,6 +336,22 @@ msgstr ""
msgid "Unknown error. Please e-mail us to let us know how it happened."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"You are re-using a password that you have used recently. You must have {0} "
"distinct password(s) before reusing a previous password."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"You are resetting passwords too frequently. Due to security policies, {0} "
"day(s) must elapse between password resets"
msgstr ""
#: common/djangoapps/student/views.py
msgid "Password reset unsuccessful"
msgstr ""
#: common/djangoapps/student/views.py
msgid "No inactive user with this e-mail exists"
msgstr ""
@@ -767,7 +800,7 @@ msgid "unanswered"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
msgid "queued"
msgid "processing"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
@@ -788,6 +821,10 @@ msgid ""
"by that feedback."
msgstr ""
#: common/lib/capa/capa/inputtypes.py
msgid "No response from Xqueue within {xqueue_timeout} seconds. Aborted."
msgstr ""
#: common/lib/capa/capa/responsetypes.py
msgid "Error {err} in evaluating hint function {hintfn}."
msgstr ""
@@ -800,6 +837,28 @@ msgstr ""
msgid "See XML source line {sourcenum}."
msgstr ""
#. Translators: 'unmask_name' is a method name and should not be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "unmask_name called on response that is not masked"
msgstr ""
#. Translators: 'shuffle' and 'answer-pool' are attribute names and should not
#. be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "Do not use shuffle and answer-pool at the same time"
msgstr ""
#. Translators: 'answer-pool' is an attribute name and should not be
#. translated.
#: common/lib/capa/capa/responsetypes.py
msgid "answer-pool value should be an integer"
msgstr ""
#. Translators: 'Choicegroup' is an input type and should not be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "Choicegroup must include at least 1 correct and 1 incorrect choice"
msgstr ""
#: common/lib/capa/capa/responsetypes.py common/lib/capa/capa/responsetypes.py
msgid "There was a problem with the staff answer to this problem."
msgstr ""
@@ -894,6 +953,10 @@ msgstr ""
msgid "Final Check"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Checking..."
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Warning: The problem has been reset to its initial state!"
msgstr ""
@@ -926,11 +989,29 @@ msgstr ""
msgid "You must wait at least {wait} seconds between submissions."
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid ""
"You must wait at least {wait_secs} between submissions. {remaining_secs} "
"remaining."
msgstr ""
#. Translators: {msg} will be replaced with a problem's error message.
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Error: {msg}"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_hour} hour"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_minute} minute"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_second} second"
msgstr ""
#. Translators: 'rescoring' refers to the act of re-submitting a student's
#. solution so it can get a new score.
#: common/lib/xmodule/xmodule/capa_base.py
@@ -980,6 +1061,18 @@ msgstr ""
msgid "TBD"
msgstr ""
#: common/lib/xmodule/xmodule/lti_module.py
msgid ""
"Could not parse custom parameter: {custom_parameter}. Should be \"x=y\" "
"string."
msgstr ""
#: common/lib/xmodule/xmodule/lti_module.py
msgid ""
"Could not parse LTI passport: {lti_passport}. Should be \"id:key:secret\" "
"string."
msgstr ""
#. #-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#
#. Translators: 'Courseware' refers to the tab in the courseware that leads to
#. the content of a course
@@ -1264,10 +1357,24 @@ msgstr ""
msgid "Something wrong with SubRip transcripts file during parsing."
msgstr ""
#: common/lib/xmodule/xmodule/video_module/transcripts_utils.py
msgid "{exception_message}: Can't find uploaded transcripts: {user_filename}"
msgstr ""
#: common/lib/xmodule/xmodule/video_module/video_module.py
msgid "A YouTube URL or a link to a file hosted anywhere on the web."
msgstr ""
#. Translators: This is a type of file used for captioning in the video
#. player.
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "SubRip (.srt) file"
msgstr ""
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "Text (.txt) file"
msgstr ""
#: common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/mjtheme/layout.html
msgid "Navigation"
msgstr ""
@@ -1695,10 +1802,14 @@ msgstr ""
#: lms/djangoapps/instructor/views/legacy.py
#: lms/djangoapps/instructor/views/legacy.py
#: lms/djangoapps/instructor/views/tools.py
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Username"
msgstr ""
#: lms/djangoapps/instructor/views/api.py lms/templates/help_modal.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/open_ended_problems/open_ended_flagged_problems.html
msgid "Name"
msgstr ""
@@ -1741,6 +1852,15 @@ msgstr ""
msgid "Goals"
msgstr ""
#: lms/djangoapps/instructor/views/api.py
msgid "Module does not exist."
msgstr ""
#: lms/djangoapps/instructor/views/api.py
#: lms/djangoapps/instructor/views/legacy.py
msgid "An error occurred while deleting the score."
msgstr ""
#: lms/djangoapps/instructor/views/api.py
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "Complete"
@@ -2028,7 +2148,7 @@ msgstr ""
#: lms/djangoapps/instructor/views/tools.py cms/templates/register.html
#: lms/templates/dashboard.html lms/templates/register-shib.html
#: lms/templates/register.html lms/templates/register.html
#: lms/templates/sysadmin_dashboard.html
#: lms/templates/signup_modal.html lms/templates/sysadmin_dashboard.html
#: lms/templates/verify_student/_modal_editname.html
#: lms/templates/verify_student/face_upload.html
msgid "Full Name"
@@ -2255,37 +2375,6 @@ msgstr ""
msgid "Add to profile"
msgstr ""
#. #-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#
#. Translators: "Peer Grading" is a panel where peer can grade student-
#. provided answers.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
#: lms/templates/peer_grading/peer_grading.html
#: lms/templates/peer_grading/peer_grading_closed.html
#: lms/templates/peer_grading/peer_grading_problem.html
msgid "Peer Grading"
msgstr ""
#. Translators: "Staff Grading" is a panel where instructor can grade student-
#. provided answers.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Staff Grading"
msgstr ""
#. Translators: "Problems you have submitted" refers to the problems that the
#. currently-logged-in
#. student has provided an answer for.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Problems you have submitted"
msgstr ""
#. Translators: "Flagged Submissions" refers to student-provided answers to a
#. problem which are
#. marked by instructor or peer graders as 'flagged' potentially
#. inappropriate.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Flagged Submissions"
msgstr ""
#: lms/djangoapps/open_ended_grading/staff_grading_service.py
msgid ""
"Could not contact the external grading server. Please contact the "
@@ -2385,6 +2474,10 @@ msgstr ""
msgid "Trying to add a different currency into the cart"
msgstr ""
#: lms/djangoapps/shoppingcart/models.py
msgid "Registration for Course: {course_name}"
msgstr ""
#: lms/djangoapps/shoppingcart/models.py
msgid ""
"Please visit your <a href=\"{dashboard_link}\">dashboard</a> to see your new"
@@ -3077,6 +3170,7 @@ msgstr ""
#: lms/templates/wiki/delete.html
#: lms/templates/wiki/plugins/attachments/index.html
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/studio_xblock_wrapper.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
@@ -3143,6 +3237,7 @@ msgstr ""
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/modal/_modal-settings-language.html
#: lms/templates/modal/accessible_confirm.html
msgid "Close"
@@ -3684,35 +3779,11 @@ msgstr ""
msgid "close"
msgstr ""
#: cms/templates/component.html cms/templates/manage_users.html
#: cms/templates/settings.html cms/templates/settings_advanced.html
#: cms/templates/settings_graders.html cms/templates/widgets/header.html
#: lms/templates/wiki/includes/article_menu.html
msgid "Settings"
msgstr ""
#: cms/templates/component.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html lms/templates/problem.html
#: lms/templates/word_cloud.html
#: lms/templates/combinedopenended/openended/open_ended.html
#: lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
#: lms/templates/verify_student/face_upload.html
msgid "Save"
msgstr ""
#: cms/templates/component.html cms/templates/index.html
#: cms/templates/manage_users.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: lms/templates/discussion/_inline_new_post.html
#: lms/templates/discussion/_new_post.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_discussion.mustache
#: lms/templates/discussion/mustache/_inline_discussion_cohorted.mustache
#: lms/templates/verify_student/face_upload.html
msgid "Cancel"
#: cms/templates/container.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Loading..."
msgstr ""
#. Translators: this is a verb describing the action of viewing more details
@@ -3730,6 +3801,19 @@ msgstr ""
msgid "Course Number"
msgstr ""
#: cms/templates/index.html cms/templates/manage_users.html
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html lms/templates/discussion/_inline_new_post.html
#: lms/templates/discussion/_new_post.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_discussion.mustache
#: lms/templates/discussion/mustache/_inline_discussion_cohorted.mustache
#: lms/templates/verify_student/face_upload.html
msgid "Cancel"
msgstr ""
#: cms/templates/index.html
#: lms/templates/instructor/instructor_dashboard_2/course_info.html
msgid "Organization:"
@@ -3754,23 +3838,41 @@ msgstr ""
#: cms/templates/login.html cms/templates/register.html
#: lms/templates/login.html lms/templates/provider_login.html
#: lms/templates/provider_login.html lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/sysadmin_dashboard.html
#: lms/templates/university_profile/edge.html
msgid "Password"
msgstr ""
#: cms/templates/manage_users.html cms/templates/settings.html
#: cms/templates/settings_advanced.html cms/templates/settings_graders.html
#: cms/templates/widgets/header.html
#: lms/templates/wiki/includes/article_menu.html
msgid "Settings"
msgstr ""
#: cms/templates/manage_users.html
#: lms/templates/courseware/instructor_dashboard.html
msgid "Admin"
msgstr ""
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: lms/templates/problem.html lms/templates/word_cloud.html
#: lms/templates/combinedopenended/openended/open_ended.html
#: lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
#: lms/templates/verify_student/face_upload.html
msgid "Save"
msgstr ""
#: cms/templates/register.html cms/templates/widgets/header.html
#: lms/templates/index.html
msgid "Sign Up"
msgstr ""
#: cms/templates/register.html lms/templates/register-shib.html
#: lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/signup_modal.html
msgid "Public Username"
msgstr ""
@@ -3811,14 +3913,6 @@ msgstr ""
msgid "Help"
msgstr ""
#: cms/templates/widgets/html-edit.html lms/templates/widgets/html-edit.html
msgid "Visual"
msgstr ""
#: cms/templates/widgets/html-edit.html lms/templates/widgets/html-edit.html
msgid "HTML"
msgstr ""
#: common/templates/course_modes/choose.html
msgid "Upgrade Your Registration for {} | Choose Your Track"
msgstr ""
@@ -4026,12 +4120,11 @@ msgstr ""
#: lms/templates/contact.html
msgid ""
"If you have a general question about {platform_name} please email <a "
"href=\"mailto:{contact_email}\">{contact_email}</a>. To see if your question"
" has already been answered, visit our {faq_link_start}FAQ "
"page{faq_link_end}. You can also join the discussion on our "
"{fb_link_start}facebook page{fb_link_end}. Though we may not have a chance "
"to respond to every email, we take all feedback into consideration."
"If you have a general question about {platform_name} please email "
"{contact_email}. To see if your question has already been answered, visit "
"our {faq_link_start}FAQ page{faq_link_end}. You can also join the discussion"
" on our {fb_link_start}facebook page{fb_link_end}. Though we may not have a "
"chance to respond to every email, we take all feedback into consideration."
msgstr ""
#: lms/templates/contact.html
@@ -4042,12 +4135,11 @@ msgstr ""
msgid ""
"If you have suggestions/feedback about the overall {platform_name} platform,"
" or are facing general technical issues with the platform (e.g., issues with"
" email addresses and passwords), you can reach us at <a "
"href=\"mailto:{tech_email}\">{tech_email}</a>. For technical questions, "
"please make sure you are using a current version of Firefox or Chrome, and "
"include browser and version in your e-mail, as well as screenshots or other "
"pertinent details. If you find a bug or other issues, you can reach us at "
"the following: <a href=\"mailto:{bugs_email}\">{bugs_email}</a>."
" email addresses and passwords), you can reach us at {tech_email}. For "
"technical questions, please make sure you are using a current version of "
"Firefox or Chrome, and include browser and version in your e-mail, as well "
"as screenshots or other pertinent details. If you find a bug or other "
"issues, you can reach us at the following: {bugs_email}."
msgstr ""
#: lms/templates/contact.html
@@ -4088,11 +4180,47 @@ msgstr ""
msgid "Please verify your new email"
msgstr ""
#. Translators: this message is displayed when a user tries to link their
#. account with a third-party authentication provider (for example, Google or
#. LinkedIn) with a given edX account, but their third-party account is
#. already
#. associated with another edX account. provider_name is the name of the
#. third-party authentication provider, and platform_name is the name of the
#. edX deployment.
#: lms/templates/dashboard.html
msgid ""
"The selected {provider_name} account is already linked to another "
"{platform_name} account. Please {link_start}log out{link_end}, then log in "
"with your {provider_name} account."
msgstr ""
#: lms/templates/dashboard.html lms/templates/dashboard.html
#: lms/templates/dashboard/_dashboard_info_language.html
msgid "edit"
msgstr ""
#. Translators: this section lists all the third-party authentication
#. providers
#. (for example, Google and LinkedIn) the user can link with or unlink from
#. their edX account.
#: lms/templates/dashboard.html
msgid "Account Links"
msgstr ""
#. Translators: clicking on this removes the link between a user's edX account
#. and their account with an external authentication provider (like Google or
#. LinkedIn).
#: lms/templates/dashboard.html
msgid "unlink"
msgstr ""
#. Translators: clicking on this creates a link between a user's edX account
#. and their account with an external authentication provider (like Google or
#. LinkedIn).
#: lms/templates/dashboard.html
msgid "link"
msgstr ""
#: lms/templates/dashboard.html lms/templates/dashboard.html
msgid "Reset Password"
msgstr ""
@@ -4201,6 +4329,10 @@ msgstr ""
msgid "Unregister"
msgstr ""
#: lms/templates/edit_unit_link.html
msgid "View Unit in Studio"
msgstr ""
#: lms/templates/email_change_failed.html lms/templates/email_exists.html
msgid "E-mail change failed"
msgstr ""
@@ -4256,18 +4388,6 @@ msgstr ""
msgid "Debug: "
msgstr ""
#: lms/templates/enroll_students.html
msgid "foo"
msgstr ""
#: lms/templates/enroll_students.html
msgid "bar"
msgstr ""
#: lms/templates/enroll_students.html
msgid "biff"
msgstr ""
#: lms/templates/extauth_failure.html lms/templates/extauth_failure.html
msgid "External Authentication failed"
msgstr ""
@@ -4375,14 +4495,10 @@ msgstr ""
msgid "Email is incorrect."
msgstr ""
#: lms/templates/help_modal.html
#: lms/templates/help_modal.html lms/templates/help_modal.html
msgid "{platform_name} Help"
msgstr ""
#: lms/templates/help_modal.html
msgid "{span_start}{platform_name}{span_end} Help"
msgstr ""
#: lms/templates/help_modal.html
msgid ""
"For <strong>questions on course lectures, homework, tools, or materials for "
@@ -4425,7 +4541,8 @@ msgstr ""
#: lms/templates/help_modal.html lms/templates/login.html
#: lms/templates/provider_login.html lms/templates/provider_login.html
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/signup_modal.html
msgid "E-mail"
msgstr ""
@@ -4652,10 +4769,39 @@ msgstr ""
msgid "Remember me"
msgstr ""
#. Translators: this is the last choice of a number of choices of how to log
#. in
#. to the site.
#: lms/templates/login.html
msgid "or, if you have connected one of these providers, log in below."
msgstr ""
#. Translators: provider_name is the name of an external, third-party user
#. authentication provider (like Google or LinkedIn).
#. Translators: provider_name is the name of an external, third-party user
#. authentication service (like Google or LinkedIn).
#: lms/templates/login.html lms/templates/register.html
msgid "Sign in with {provider_name}"
msgstr ""
#. Translators: "External resource" means that this learning module is hosted
#. on a platform external to the edX LMS
#: lms/templates/lti.html
msgid "External resource"
msgstr ""
#. Translators: "points" is the student's achieved score on this LTI unit, and
#. "total_points" is the maximum number of points achievable.
#: lms/templates/lti.html
msgid "{points} / {total_points} points"
msgstr ""
#. Translators: "total_points" is the maximum number of points achievable on
#. this LTI unit
#: lms/templates/lti.html
msgid "{total_points} points possible"
msgstr ""
#: lms/templates/lti.html
msgid "View resource in a new window"
msgstr ""
@@ -4665,6 +4811,14 @@ msgid ""
"Please provide launch_url. Click \"Edit\", and fill in the required fields."
msgstr ""
#: lms/templates/lti.html
msgid "Feedback on your work from the grader:"
msgstr ""
#: lms/templates/lti_form.html
msgid "Press to Launch"
msgstr ""
#: lms/templates/manage_user_standing.html
msgid "Disable or Reenable student accounts"
msgstr ""
@@ -4708,15 +4862,15 @@ msgid ""
msgstr ""
#: lms/templates/module-error.html
msgid "There has been an error on the <em>{platform_name}</em> servers"
#: lms/templates/courseware/courseware-error.html
msgid "There has been an error on the {platform_name} servers"
msgstr ""
#: lms/templates/module-error.html
msgid ""
"We're sorry, this module is temporarily unavailable. Our staff is working to"
" fix it as soon as possible. Please email us at <a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a> to report any "
"problems or downtime."
" fix it as soon as possible. Please email us at {tech_support_email} to "
"report any problems or downtime."
msgstr ""
#: lms/templates/module-error.html
@@ -4768,6 +4922,10 @@ msgstr ""
msgid "Log Out"
msgstr ""
#: lms/templates/navigation.html
msgid "Shopping Cart"
msgstr ""
#: lms/templates/navigation.html
msgid "How it Works"
msgstr ""
@@ -4825,8 +4983,9 @@ msgstr ""
#: lms/templates/provider_login.html
msgid ""
"Please note that we will be sending your user name, email, and full name to "
"this third party site."
"Your username, email, and full name will be sent to {destination}, where the"
" collection and use of this information will be governed by their terms of "
"service and privacy policy."
msgstr ""
#: lms/templates/provider_login.html
@@ -4883,10 +5042,12 @@ msgid "Account Acknowledgements"
msgstr ""
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Terms of Service{link_end}"
msgstr ""
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Honor Code{link_end}"
msgstr ""
@@ -4967,6 +5128,26 @@ msgstr ""
msgid "Register below to create your {platform_name} account"
msgstr ""
#: lms/templates/register.html
msgid "Register to start learning today!"
msgstr ""
#: lms/templates/register.html
msgid ""
"or create your own {platform_name} account by completing all "
"<strong>required*</strong> fields below."
msgstr ""
#. Translators: selected_provider is the name of an external, third-party user
#. authentication service (like Google or LinkedIn).
#: lms/templates/register.html
msgid "You've successfully signed in with {selected_provider}."
msgstr ""
#: lms/templates/register.html
msgid "Finish your account registration below to start learning."
msgstr ""
#: lms/templates/register.html
msgid "Please complete the following fields to register for an account. "
msgstr ""
@@ -5057,33 +5238,17 @@ msgid "Next"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Sign Up for {span_start}{platform_name}{span_end}"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "E-mail *"
msgid "Sign Up for {platform_name}"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. yourname@domain.com"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Password *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "Public Username *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. yourname (shown on forums)"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "Full Name *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. Your Name (for certificates)"
msgstr ""
@@ -5092,6 +5257,10 @@ msgstr ""
msgid "<i>Welcome</i> {name}"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Full Name *"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Ed. Completed"
msgstr ""
@@ -5108,14 +5277,6 @@ msgstr ""
msgid "Goals in signing up for {platform_name}"
msgstr ""
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Terms of Service{link_end}*"
msgstr ""
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Honor Code{link_end}*"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Already have an account?"
msgstr ""
@@ -5155,7 +5316,7 @@ msgid "Tag"
msgstr ""
#: lms/templates/staff_problem_info.html
msgid "Optional tag (eg \"done\" or \"broken\"):&nbsp; "
msgid "Optional tag (eg \"done\" or \"broken\"):"
msgstr ""
#: lms/templates/staff_problem_info.html
@@ -5200,43 +5361,11 @@ msgstr ""
msgid "Textbook Navigation"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Page:"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/static_pdfbook.html
msgid "Zoom Out"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/static_pdfbook.html
msgid "Zoom In"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Zoom"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Automatic Zoom"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Actual Size"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Fit Page"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Full Width"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/staticbook.html
#: lms/templates/staticbook.html
msgid "Previous page"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/staticbook.html
#: lms/templates/staticbook.html
msgid "Next page"
msgstr ""
@@ -5485,8 +5614,8 @@ msgstr ""
msgid "Download transcript"
msgstr ""
#: lms/templates/video.html lms/templates/video.html
msgid "{file_format}"
#: lms/templates/video.html
msgid "Download Handout"
msgstr ""
#: lms/templates/word_cloud.html
@@ -5724,6 +5853,10 @@ msgstr ""
msgid "Register for {course.display_number_with_default}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "View About Page in studio"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Overview"
msgstr ""
@@ -5732,6 +5865,20 @@ msgstr ""
msgid "Share with friends and family!"
msgstr ""
#. Translators: This text will be automatically posted to the student's
#. Twitter account. {url} should appear at the end of the text.
#: lms/templates/courseware/course_about.html
msgid "I just registered for {number} {title} through {account}: {url}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Take a course with {platform} online"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "I just registered for {number} {title} through {platform} {url}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Classes Start"
msgstr ""
@@ -5775,17 +5922,11 @@ msgstr ""
msgid "Explore free courses from {university_name}."
msgstr ""
#: lms/templates/courseware/courseware-error.html
msgid ""
"There has been an error on the {span_start}{platform_name}{span_end} servers"
msgstr ""
#: lms/templates/courseware/courseware-error.html
msgid ""
"We're sorry, this module is temporarily unavailable. Our staff is working to"
" fix it as soon as possible. Please email us at '<a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a>' to report any"
" problems or downtime."
" fix it as soon as possible. Please email us at {tech_support_email}' to "
"report any problems or downtime."
msgstr ""
#: lms/templates/courseware/courseware.html
@@ -5915,6 +6056,10 @@ msgstr ""
msgid "{course_number} Course Info"
msgstr ""
#: lms/templates/courseware/info.html
msgid "View Updates in Studio"
msgstr ""
#: lms/templates/courseware/info.html lms/templates/courseware/info.html
msgid "Course Updates &amp; News"
msgstr ""
@@ -5935,12 +6080,12 @@ msgid "Instructor Dashboard"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
msgid "Try New Beta Dashboard"
#: lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
msgid "View Course in Studio"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
msgid "Edit Course In Studio"
msgid "Try New Beta Dashboard"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
@@ -6275,12 +6420,6 @@ msgstr ""
msgid "Count of Students that Opened a Subsection"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Loading..."
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Grade Distribution per Problem"
@@ -6394,10 +6533,18 @@ msgstr ""
msgid "Course Progress"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "View Grading in studio"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "Course Progress for Student '{username}' ({email})"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "Download your certificate"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "{earned:.3n} of {total:.3n} possible points"
msgstr ""
@@ -6570,6 +6717,13 @@ msgid ""
" of"
msgstr ""
#: lms/templates/dashboard/_dashboard_course_listing.html
msgid ""
"In order to request a refund for the amount you paid, you will need to send "
"an email to {billing_email}. Be sure to include your email and the course "
"name."
msgstr ""
#: lms/templates/dashboard/_dashboard_course_listing.html
msgid "Email Settings"
msgstr ""
@@ -6688,6 +6842,10 @@ msgstr ""
msgid "Show Discussion"
msgstr ""
#: lms/templates/discussion/_discussion_module_studio.html
msgid "To view live discussions, click Preview or View Live in Unit Settings."
msgstr ""
#: lms/templates/discussion/_filter_dropdown.html
msgid "Filter Topics"
msgstr ""
@@ -7050,24 +7208,14 @@ msgstr ""
msgid "User Profile"
msgstr ""
#: lms/templates/discussion/user_profile.html
msgid "Active Threads"
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Expand discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Loading content"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
#: lms/templates/discussion/mustache/_profile_thread.mustache
msgid "View discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Hide discussion"
msgid "Collapse discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
@@ -7079,6 +7227,14 @@ msgstr ""
msgid "…"
msgstr ""
#: lms/templates/discussion/mustache/_profile_thread.mustache
msgid "View discussion"
msgstr ""
#: lms/templates/discussion/mustache/_user_profile.mustache
msgid "Active Threads"
msgstr ""
#: lms/templates/emails/activation_email.txt
msgid ""
"Thank you for signing up for {platform_name}! To activate your account, "
@@ -7118,10 +7274,19 @@ msgid ""
"by a member of the course staff."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
#: lms/templates/emails/enroll_email_enrolledmessage.txt
msgid "To start accessing course materials, please visit {course_url}"
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
msgid "Visit {course_about_url} to join the course and begin the beta test."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
msgid "Visit {site_name} to enroll in the course and begin the beta test."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
#: lms/templates/emails/enroll_email_allowedmessage.txt
#: lms/templates/emails/remove_beta_tester_email_message.txt
@@ -7202,6 +7367,10 @@ msgid ""
"{course_about_url} to join the course."
msgstr ""
#: lms/templates/emails/enroll_email_allowedmessage.txt
msgid "You can then enroll in {course_name}."
msgstr ""
#: lms/templates/emails/enroll_email_allowedsubject.txt
msgid "You have been invited to register for {course_name}"
msgstr ""
@@ -7212,10 +7381,6 @@ msgid ""
"course staff. The course should now appear on your {site_name} dashboard."
msgstr ""
#: lms/templates/emails/enroll_email_enrolledmessage.txt
msgid "To start accessing course materials, please visit {course_url}"
msgstr ""
#: lms/templates/emails/enroll_email_enrolledmessage.txt
#: lms/templates/emails/unenroll_email_enrolledmessage.txt
msgid "This email was automatically sent from {site_name} to {full_name}"
@@ -7639,7 +7804,8 @@ msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Enter email addresses separated by new lines or commas."
msgid ""
"Enter email addresses and/or usernames separated by new lines or commas."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
@@ -7650,9 +7816,10 @@ msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Email Addresses"
msgid "Email Addresses/Usernames"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Auto Enroll"
msgstr ""
@@ -7670,6 +7837,10 @@ msgid ""
"once they make an account."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Checking this box has no effect if 'Unenroll' is selected."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Notify users by email"
@@ -7691,7 +7862,7 @@ msgid "Unenroll"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Batch Beta Testers"
msgid "Batch Beta Tester Addition"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
@@ -7700,6 +7871,16 @@ msgid ""
"be enrolled as a beta tester."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid ""
"If this option is <em>checked</em>, users who have not enrolled in your "
"course will be automatically enrolled."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Checking this box has no effect if 'Remove beta testers' is selected."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Add beta testers"
msgstr ""
@@ -7831,6 +8012,27 @@ msgstr ""
msgid "Count of Students Opened a Subsection"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Download Student Opened as a CSV"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Download Student Grades as a CSV"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "This is a partial list, to view all students download as a csv."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Grade"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Percent"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/send_email.html
#: lms/templates/instructor/instructor_dashboard_2/send_email.html
msgid "Send Email"
@@ -8062,6 +8264,12 @@ msgid ""
"{end_p_tag}\n"
msgstr ""
#: lms/templates/peer_grading/peer_grading.html
#: lms/templates/peer_grading/peer_grading_closed.html
#: lms/templates/peer_grading/peer_grading_problem.html
msgid "Peer Grading"
msgstr ""
#: lms/templates/peer_grading/peer_grading.html
msgid ""
"Here are a list of problems that need to be peer graded for this course."
@@ -8298,6 +8506,16 @@ msgstr ""
msgid "Register for [Course Name] | Receipt (Order"
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid "Thank you for your Purchase!"
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid ""
"Please print this receipt page for your records. You should also have "
"received a receipt in your email."
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid " () Electronic Receipt"
msgstr ""
@@ -8537,20 +8755,18 @@ msgid "In the Press"
msgstr ""
#: lms/templates/static_templates/server-down.html
msgid "Currently the <em>{platform_name}</em> servers are down"
msgid "Currently the {platform_name} servers are down"
msgstr ""
#: lms/templates/static_templates/server-down.html
#: lms/templates/static_templates/server-overloaded.html
msgid ""
"Our staff is currently working to get the site back up as soon as possible. "
"Please email us at <a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a> to report any "
"problems or downtime."
"Please email us at {tech_support_email} to report any problems or downtime."
msgstr ""
#: lms/templates/static_templates/server-error.html
msgid "There has been a 500 error on the <em>{platform_name}</em> servers"
msgid "There has been a 500 error on the {platform_name} servers"
msgstr ""
#: lms/templates/static_templates/server-error.html
@@ -8560,7 +8776,7 @@ msgid ""
msgstr ""
#: lms/templates/static_templates/server-overloaded.html
msgid "Currently the <em>{platform_name}</em> servers are overloaded"
msgid "Currently the {platform_name} servers are overloaded"
msgstr ""
#: lms/templates/university_profile/edge.html
@@ -8931,6 +9147,8 @@ msgid "Complete your other re-verifications"
msgstr ""
#: lms/templates/verify_student/midcourse_reverification_confirmation.html
#: lms/templates/verify_student/midcourse_reverify_dash.html
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "Return to where you left off"
msgstr ""
@@ -8971,13 +9189,7 @@ msgid "Failed"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid ""
"Don't want to re-verify right now? {a_start}Return to where you left "
"off{a_end}"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "{a_start}Return to where you left off{a_end}"
msgid "Don't want to re-verify right now?"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
@@ -9666,19 +9878,16 @@ msgid ""
"immediately visible to other course team members."
msgstr ""
#: cms/templates/component.html
msgid "Editor"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/studio_xblock_wrapper.html
msgid "Duplicate"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/component.html
msgid "Duplicate this component"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/component.html
msgid "Delete this component"
msgstr ""
@@ -9692,23 +9901,40 @@ msgstr ""
msgid "Container"
msgstr ""
#: cms/templates/container.html cms/templates/studio_vertical_wrapper.html
msgid "No Actions"
#: cms/templates/container.html
msgid "This page has no content yet."
msgstr ""
#: cms/templates/container.html
#: cms/templates/container.html cms/templates/container.html
msgid "Publishing Status"
msgstr ""
#: cms/templates/container.html
msgid "This content is published with unit {unit_name}."
msgid "Published"
msgstr ""
#: cms/templates/container.html
msgid ""
"You can view course components that contain other components on this page. "
"In the case of experiment blocks, this allows you to confirm that you have "
"properly configured your experiment groups."
"To make changes to the content of this page, you need to edit unit "
"{unit_link} as a draft."
msgstr ""
#: cms/templates/container.html
msgid "Draft"
msgstr ""
#: cms/templates/container.html
msgid ""
"You can edit the content of this page, and your changes will be published "
"with unit {unit_link}."
msgstr ""
#: cms/templates/container.html
msgid ""
"You can view and edit course components that contain other components on "
"this page. In the case of experiment blocks, this allows you to confirm that"
" you have properly configured your experiment groups and make changes to "
"existing content."
msgstr ""
#: cms/templates/course_info.html cms/templates/course_info.html
@@ -9743,6 +9969,13 @@ msgstr ""
msgid "View Live"
msgstr ""
#: cms/templates/edit-tabs.html
msgid ""
"Note: Pages are publicly visible. If users know the URL of a page, they can "
"view the page even if they are not registered for or logged in to your "
"course."
msgstr ""
#: cms/templates/edit-tabs.html
msgid "Show this page"
msgstr ""
@@ -11377,6 +11610,10 @@ msgstr ""
msgid "Expand or Collapse"
msgstr ""
#: cms/templates/studio_vertical_wrapper.html
msgid "No Actions"
msgstr ""
#: cms/templates/textbooks.html
msgid "You have unsaved changes. Do you really want to leave this page?"
msgstr ""
@@ -11465,15 +11702,15 @@ msgid ""
msgstr ""
#: cms/templates/unit.html
msgid "This unit is scheduled to be released to <strong>students</strong>"
msgid ""
"This unit is scheduled to be released to <strong>students</strong> on "
"<strong>{date}</strong> with the subsection {link_start}{name}{link_end}"
msgstr ""
#: cms/templates/unit.html
msgid "on {date}"
msgstr ""
#: cms/templates/unit.html
msgid "with the subsection {link_start}{name}{link_end}"
msgid ""
"This unit is scheduled to be released to <strong>students</strong> with the "
"subsection {link_start}{name}{link_end}"
msgstr ""
#: cms/templates/unit.html

View File

@@ -14,8 +14,8 @@ msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-03-31 09:26-0400\n"
"PO-Revision-Date: 2014-03-19 20:22+0000\n"
"POT-Creation-Date: 2014-05-02 17:09-0400\n"
"PO-Revision-Date: 2014-04-24 13:00+0000\n"
"Last-Translator: sarina <sarina@edx.org>\n"
"Language-Team: Bulgarian (Bulgaria) (http://www.transifex.com/projects/p/edx-platform/language/bg_BG/)\n"
"MIME-Version: 1.0\n"
@@ -26,6 +26,7 @@ msgstr ""
#: cms/static/coffee/src/views/tabs.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/modals/edit_xblock.js
#: common/static/coffee/src/discussion/utils.js
msgid "OK"
msgstr ""
@@ -34,6 +35,8 @@ msgstr ""
#: cms/static/js/base.js cms/static/js/views/asset.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/show_textbook.js cms/static/js/views/validation.js
#: cms/static/js/views/modals/base_modal.js
#: cms/static/js/views/pages/container.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Cancel"
@@ -333,6 +336,8 @@ msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
#: common/static/coffee/src/discussion/views/response_comment_view.js
msgid "Sorry"
msgstr ""
@@ -389,16 +394,14 @@ msgid "vote"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid ""
"%(voteNum)s%(startSrSpan)s vote (click to remove your vote)%(endSrSpan)s"
msgid_plural ""
"%(voteNum)s%(startSrSpan)s votes (click to remove your vote)%(endSrSpan)s"
msgid "vote (click to remove your vote)"
msgid_plural "votes (click to remove your vote)"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "%(voteNum)s%(startSrSpan)s vote (click to vote)%(endSrSpan)s"
msgid_plural "%(voteNum)s%(startSrSpan)s votes (click to vote)%(endSrSpan)s"
msgid "vote (click to vote)"
msgid_plural "votes (click to vote)"
msgstr[0] ""
msgstr[1] ""
@@ -460,6 +463,11 @@ msgstr ""
msgid "Pin Thread"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid ""
"The thread you selected has been deleted. Please select another thread."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "We had some trouble loading responses. Please reload the page."
msgstr ""
@@ -496,6 +504,10 @@ msgstr ""
msgid "Are you sure you want to delete this post?"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
msgid "We had some trouble loading the page you requested. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
msgid "anonymous"
msgstr ""
@@ -874,9 +886,10 @@ msgid ""
"beta tester."
msgstr ""
#. Translators: A list of email addresses appears after this sentence;
#. Translators: A list of identifiers (which are email addresses and/or
#. usernames) appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Could not find users associated with the following email addresses:"
msgid "Could not find users associated with the following identifiers:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
@@ -884,7 +897,7 @@ msgid "Error enrolling/unenrolling users."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "The following email addresses are invalid:"
msgid "The following email addresses and/or usernames are invalid:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
@@ -1218,15 +1231,16 @@ msgid "(Show)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid ""
"<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>"
msgid "Insert Hyperlink"
msgstr ""
#. Translators: Please keep the quotation marks (") around this text
#: lms/static/js/Markdown.Editor.js lms/static/js/Markdown.Editor.js.c
msgid "\"optional title\""
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid ""
"<p><b>Insert Image (upload file or type "
"url)</b></p><p>http://example.com/images/diagram.jpg \"optional "
"title\"<br><br></p>"
msgid "Insert Image (upload file or type url)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
@@ -1336,18 +1350,13 @@ msgstr ""
msgid "Studio's having trouble saving your work"
msgstr ""
#: cms/static/coffee/src/views/module_edit.js
msgid "<em>Editing:</em> %s"
msgstr ""
#: cms/static/coffee/src/views/module_edit.js
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/tabs.js
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/xblock/cms.runtime.v1.js
#: cms/static/js/models/section.js cms/static/js/views/asset.js
#: cms/static/js/views/course_info_handout.js
#: cms/static/js/models/section.js cms/static/js/utils/drag_and_drop.js
#: cms/static/js/views/asset.js cms/static/js/views/course_info_handout.js
#: cms/static/js/views/course_info_update.js cms/static/js/views/overview.js
#: cms/static/js/views/overview.js.c
#: cms/static/js/views/xblock_editor.js
msgid "Saving&hellip;"
msgstr ""
@@ -1363,6 +1372,7 @@ msgstr ""
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/unit.js
#: cms/static/js/base.js cms/static/js/views/course_info_update.js
#: cms/static/js/views/pages/container.js
msgid "Deleting&hellip;"
msgstr ""
@@ -1370,19 +1380,19 @@ msgstr ""
msgid "Adding&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Duplicating&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Delete this component?"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Deleting this component is permanent and cannot be undone."
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Yes, delete this component"
msgstr ""
@@ -1529,6 +1539,10 @@ msgstr ""
msgid "Upload a new PDF to “<%= name %>”"
msgstr ""
#: cms/static/js/views/edit_chapter.js
msgid "Please select a PDF file to upload."
msgstr ""
#: cms/static/js/views/edit_textbook.js
#: cms/static/js/views/overview_assignment_grader.js
msgid "Saving"
@@ -1544,6 +1558,10 @@ msgid ""
"extension."
msgstr ""
#: cms/static/js/views/metadata.js
msgid "Upload File"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Collapse All Sections"
msgstr ""
@@ -1605,6 +1623,10 @@ msgstr ""
msgid "Deleting"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "Upload"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "We're sorry, there was an error"
msgstr ""
@@ -1634,6 +1656,26 @@ msgstr ""
msgid "Your changes have been saved."
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Editor"
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Settings"
msgstr ""
#: cms/static/js/views/modals/base_modal.js
msgid "Save"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Component"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Editing: %(title)s"
msgstr ""
#: cms/static/js/views/settings/advanced.js
msgid ""
"Your changes will not take effect until you save your progress. Take care "
@@ -1658,3 +1700,13 @@ msgstr ""
#: cms/static/js/views/settings/main.js
msgid "Files must be in JPEG or PNG format."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid ""
"Sorry, there was an error parsing the subtitles that you uploaded. Please "
"check the format and try again."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid "Upload translation"
msgstr ""

View File

@@ -41,7 +41,7 @@ msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-03-31 09:26-0400\n"
"POT-Creation-Date: 2014-05-02 17:10-0400\n"
"PO-Revision-Date: 2014-02-06 03:20+0000\n"
"Last-Translator: nedbat <ned@edx.org>\n"
"Language-Team: Bengali (Bangladesh) (http://www.transifex.com/projects/p/edx-platform/language/bn_BD/)\n"
@@ -174,6 +174,16 @@ msgstr ""
msgid "Enrollment action is invalid"
msgstr ""
#. Translators: provider_name is the name of an external, third-party user
#. authentication service (like
#. Google or LinkedIn).
#: common/djangoapps/student/views.py
msgid ""
"There is no {platform_name} account associated with your {provider_name} "
"account. Please use your {platform_name} credentials or pick another "
"provider."
msgstr ""
#: common/djangoapps/student/views.py
msgid "There was an error receiving your login information. Please email us."
msgstr ""
@@ -184,6 +194,13 @@ msgid ""
"Try again later."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"Your password has expired due to password policy on this account. You must "
"reset your password before you can log in again. Please click the Forgot "
"Password\" link on this page to reset your password before logging in again."
msgstr ""
#: common/djangoapps/student/views.py
msgid "Too many failed login attempts. Try again later."
msgstr ""
@@ -310,7 +327,7 @@ msgstr ""
msgid "Username should only consist of A-Z and 0-9, with no spaces."
msgstr ""
#: common/djangoapps/student/views.py
#: common/djangoapps/student/views.py common/djangoapps/student/views.py
msgid "Password: "
msgstr ""
@@ -322,6 +339,22 @@ msgstr ""
msgid "Unknown error. Please e-mail us to let us know how it happened."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"You are re-using a password that you have used recently. You must have {0} "
"distinct password(s) before reusing a previous password."
msgstr ""
#: common/djangoapps/student/views.py
msgid ""
"You are resetting passwords too frequently. Due to security policies, {0} "
"day(s) must elapse between password resets"
msgstr ""
#: common/djangoapps/student/views.py
msgid "Password reset unsuccessful"
msgstr ""
#: common/djangoapps/student/views.py
msgid "No inactive user with this e-mail exists"
msgstr ""
@@ -770,7 +803,7 @@ msgid "unanswered"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
msgid "queued"
msgid "processing"
msgstr ""
#: common/lib/capa/capa/inputtypes.py
@@ -791,6 +824,10 @@ msgid ""
"by that feedback."
msgstr ""
#: common/lib/capa/capa/inputtypes.py
msgid "No response from Xqueue within {xqueue_timeout} seconds. Aborted."
msgstr ""
#: common/lib/capa/capa/responsetypes.py
msgid "Error {err} in evaluating hint function {hintfn}."
msgstr ""
@@ -803,6 +840,28 @@ msgstr ""
msgid "See XML source line {sourcenum}."
msgstr ""
#. Translators: 'unmask_name' is a method name and should not be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "unmask_name called on response that is not masked"
msgstr ""
#. Translators: 'shuffle' and 'answer-pool' are attribute names and should not
#. be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "Do not use shuffle and answer-pool at the same time"
msgstr ""
#. Translators: 'answer-pool' is an attribute name and should not be
#. translated.
#: common/lib/capa/capa/responsetypes.py
msgid "answer-pool value should be an integer"
msgstr ""
#. Translators: 'Choicegroup' is an input type and should not be translated.
#: common/lib/capa/capa/responsetypes.py
msgid "Choicegroup must include at least 1 correct and 1 incorrect choice"
msgstr ""
#: common/lib/capa/capa/responsetypes.py common/lib/capa/capa/responsetypes.py
msgid "There was a problem with the staff answer to this problem."
msgstr ""
@@ -897,6 +956,10 @@ msgstr ""
msgid "Final Check"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Checking..."
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Warning: The problem has been reset to its initial state!"
msgstr ""
@@ -929,11 +992,29 @@ msgstr ""
msgid "You must wait at least {wait} seconds between submissions."
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid ""
"You must wait at least {wait_secs} between submissions. {remaining_secs} "
"remaining."
msgstr ""
#. Translators: {msg} will be replaced with a problem's error message.
#: common/lib/xmodule/xmodule/capa_base.py
msgid "Error: {msg}"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_hour} hour"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_minute} minute"
msgstr ""
#: common/lib/xmodule/xmodule/capa_base.py
msgid "{num_second} second"
msgstr ""
#. Translators: 'rescoring' refers to the act of re-submitting a student's
#. solution so it can get a new score.
#: common/lib/xmodule/xmodule/capa_base.py
@@ -983,6 +1064,18 @@ msgstr ""
msgid "TBD"
msgstr ""
#: common/lib/xmodule/xmodule/lti_module.py
msgid ""
"Could not parse custom parameter: {custom_parameter}. Should be \"x=y\" "
"string."
msgstr ""
#: common/lib/xmodule/xmodule/lti_module.py
msgid ""
"Could not parse LTI passport: {lti_passport}. Should be \"id:key:secret\" "
"string."
msgstr ""
#. #-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#
#. Translators: 'Courseware' refers to the tab in the courseware that leads to
#. the content of a course
@@ -1267,10 +1360,24 @@ msgstr ""
msgid "Something wrong with SubRip transcripts file during parsing."
msgstr ""
#: common/lib/xmodule/xmodule/video_module/transcripts_utils.py
msgid "{exception_message}: Can't find uploaded transcripts: {user_filename}"
msgstr ""
#: common/lib/xmodule/xmodule/video_module/video_module.py
msgid "A YouTube URL or a link to a file hosted anywhere on the web."
msgstr ""
#. Translators: This is a type of file used for captioning in the video
#. player.
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "SubRip (.srt) file"
msgstr ""
#: common/lib/xmodule/xmodule/video_module/video_xfields.py
msgid "Text (.txt) file"
msgstr ""
#: common/static/js/vendor/mathjax-MathJax-c9db6ac/docs/source/mjtheme/layout.html
msgid "Navigation"
msgstr ""
@@ -1698,10 +1805,14 @@ msgstr ""
#: lms/djangoapps/instructor/views/legacy.py
#: lms/djangoapps/instructor/views/legacy.py
#: lms/djangoapps/instructor/views/tools.py
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Username"
msgstr ""
#: lms/djangoapps/instructor/views/api.py lms/templates/help_modal.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/open_ended_problems/open_ended_flagged_problems.html
msgid "Name"
msgstr ""
@@ -1744,6 +1855,15 @@ msgstr ""
msgid "Goals"
msgstr ""
#: lms/djangoapps/instructor/views/api.py
msgid "Module does not exist."
msgstr ""
#: lms/djangoapps/instructor/views/api.py
#: lms/djangoapps/instructor/views/legacy.py
msgid "An error occurred while deleting the score."
msgstr ""
#: lms/djangoapps/instructor/views/api.py
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "Complete"
@@ -2031,7 +2151,7 @@ msgstr ""
#: lms/djangoapps/instructor/views/tools.py cms/templates/register.html
#: lms/templates/dashboard.html lms/templates/register-shib.html
#: lms/templates/register.html lms/templates/register.html
#: lms/templates/sysadmin_dashboard.html
#: lms/templates/signup_modal.html lms/templates/sysadmin_dashboard.html
#: lms/templates/verify_student/_modal_editname.html
#: lms/templates/verify_student/face_upload.html
msgid "Full Name"
@@ -2258,37 +2378,6 @@ msgstr ""
msgid "Add to profile"
msgstr ""
#. #-#-#-#-# django-partial.po (edx-platform) #-#-#-#-#
#. Translators: "Peer Grading" is a panel where peer can grade student-
#. provided answers.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
#: lms/templates/peer_grading/peer_grading.html
#: lms/templates/peer_grading/peer_grading_closed.html
#: lms/templates/peer_grading/peer_grading_problem.html
msgid "Peer Grading"
msgstr ""
#. Translators: "Staff Grading" is a panel where instructor can grade student-
#. provided answers.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Staff Grading"
msgstr ""
#. Translators: "Problems you have submitted" refers to the problems that the
#. currently-logged-in
#. student has provided an answer for.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Problems you have submitted"
msgstr ""
#. Translators: "Flagged Submissions" refers to student-provided answers to a
#. problem which are
#. marked by instructor or peer graders as 'flagged' potentially
#. inappropriate.
#: lms/djangoapps/open_ended_grading/open_ended_notifications.py
msgid "Flagged Submissions"
msgstr ""
#: lms/djangoapps/open_ended_grading/staff_grading_service.py
msgid ""
"Could not contact the external grading server. Please contact the "
@@ -2388,6 +2477,10 @@ msgstr ""
msgid "Trying to add a different currency into the cart"
msgstr ""
#: lms/djangoapps/shoppingcart/models.py
msgid "Registration for Course: {course_name}"
msgstr ""
#: lms/djangoapps/shoppingcart/models.py
msgid ""
"Please visit your <a href=\"{dashboard_link}\">dashboard</a> to see your new"
@@ -3080,6 +3173,7 @@ msgstr ""
#: lms/templates/wiki/delete.html
#: lms/templates/wiki/plugins/attachments/index.html
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/studio_xblock_wrapper.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
@@ -3146,6 +3240,7 @@ msgstr ""
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/modal/_modal-settings-language.html
#: lms/templates/modal/accessible_confirm.html
msgid "Close"
@@ -3687,35 +3782,11 @@ msgstr ""
msgid "close"
msgstr ""
#: cms/templates/component.html cms/templates/manage_users.html
#: cms/templates/settings.html cms/templates/settings_advanced.html
#: cms/templates/settings_graders.html cms/templates/widgets/header.html
#: lms/templates/wiki/includes/article_menu.html
msgid "Settings"
msgstr ""
#: cms/templates/component.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html lms/templates/problem.html
#: lms/templates/word_cloud.html
#: lms/templates/combinedopenended/openended/open_ended.html
#: lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
#: lms/templates/verify_student/face_upload.html
msgid "Save"
msgstr ""
#: cms/templates/component.html cms/templates/index.html
#: cms/templates/manage_users.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: lms/templates/discussion/_inline_new_post.html
#: lms/templates/discussion/_new_post.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_discussion.mustache
#: lms/templates/discussion/mustache/_inline_discussion_cohorted.mustache
#: lms/templates/verify_student/face_upload.html
msgid "Cancel"
#: cms/templates/container.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Loading..."
msgstr ""
#. Translators: this is a verb describing the action of viewing more details
@@ -3733,6 +3804,19 @@ msgstr ""
msgid "Course Number"
msgstr ""
#: cms/templates/index.html cms/templates/manage_users.html
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html lms/templates/discussion/_inline_new_post.html
#: lms/templates/discussion/_new_post.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/_underscore_templates.html
#: lms/templates/discussion/mustache/_inline_discussion.mustache
#: lms/templates/discussion/mustache/_inline_discussion_cohorted.mustache
#: lms/templates/verify_student/face_upload.html
msgid "Cancel"
msgstr ""
#: cms/templates/index.html
#: lms/templates/instructor/instructor_dashboard_2/course_info.html
msgid "Organization:"
@@ -3757,23 +3841,41 @@ msgstr ""
#: cms/templates/login.html cms/templates/register.html
#: lms/templates/login.html lms/templates/provider_login.html
#: lms/templates/provider_login.html lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/sysadmin_dashboard.html
#: lms/templates/university_profile/edge.html
msgid "Password"
msgstr ""
#: cms/templates/manage_users.html cms/templates/settings.html
#: cms/templates/settings_advanced.html cms/templates/settings_graders.html
#: cms/templates/widgets/header.html
#: lms/templates/wiki/includes/article_menu.html
msgid "Settings"
msgstr ""
#: cms/templates/manage_users.html
#: lms/templates/courseware/instructor_dashboard.html
msgid "Admin"
msgstr ""
#: cms/templates/overview.html cms/templates/overview.html
#: cms/templates/overview.html cms/templates/overview.html
#: lms/templates/problem.html lms/templates/word_cloud.html
#: lms/templates/combinedopenended/openended/open_ended.html
#: lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
#: lms/templates/verify_student/face_upload.html
msgid "Save"
msgstr ""
#: cms/templates/register.html cms/templates/widgets/header.html
#: lms/templates/index.html
msgid "Sign Up"
msgstr ""
#: cms/templates/register.html lms/templates/register-shib.html
#: lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/signup_modal.html
msgid "Public Username"
msgstr ""
@@ -3814,14 +3916,6 @@ msgstr ""
msgid "Help"
msgstr ""
#: cms/templates/widgets/html-edit.html lms/templates/widgets/html-edit.html
msgid "Visual"
msgstr ""
#: cms/templates/widgets/html-edit.html lms/templates/widgets/html-edit.html
msgid "HTML"
msgstr ""
#: common/templates/course_modes/choose.html
msgid "Upgrade Your Registration for {} | Choose Your Track"
msgstr ""
@@ -4029,12 +4123,11 @@ msgstr ""
#: lms/templates/contact.html
msgid ""
"If you have a general question about {platform_name} please email <a "
"href=\"mailto:{contact_email}\">{contact_email}</a>. To see if your question"
" has already been answered, visit our {faq_link_start}FAQ "
"page{faq_link_end}. You can also join the discussion on our "
"{fb_link_start}facebook page{fb_link_end}. Though we may not have a chance "
"to respond to every email, we take all feedback into consideration."
"If you have a general question about {platform_name} please email "
"{contact_email}. To see if your question has already been answered, visit "
"our {faq_link_start}FAQ page{faq_link_end}. You can also join the discussion"
" on our {fb_link_start}facebook page{fb_link_end}. Though we may not have a "
"chance to respond to every email, we take all feedback into consideration."
msgstr ""
#: lms/templates/contact.html
@@ -4045,12 +4138,11 @@ msgstr ""
msgid ""
"If you have suggestions/feedback about the overall {platform_name} platform,"
" or are facing general technical issues with the platform (e.g., issues with"
" email addresses and passwords), you can reach us at <a "
"href=\"mailto:{tech_email}\">{tech_email}</a>. For technical questions, "
"please make sure you are using a current version of Firefox or Chrome, and "
"include browser and version in your e-mail, as well as screenshots or other "
"pertinent details. If you find a bug or other issues, you can reach us at "
"the following: <a href=\"mailto:{bugs_email}\">{bugs_email}</a>."
" email addresses and passwords), you can reach us at {tech_email}. For "
"technical questions, please make sure you are using a current version of "
"Firefox or Chrome, and include browser and version in your e-mail, as well "
"as screenshots or other pertinent details. If you find a bug or other "
"issues, you can reach us at the following: {bugs_email}."
msgstr ""
#: lms/templates/contact.html
@@ -4091,11 +4183,47 @@ msgstr ""
msgid "Please verify your new email"
msgstr ""
#. Translators: this message is displayed when a user tries to link their
#. account with a third-party authentication provider (for example, Google or
#. LinkedIn) with a given edX account, but their third-party account is
#. already
#. associated with another edX account. provider_name is the name of the
#. third-party authentication provider, and platform_name is the name of the
#. edX deployment.
#: lms/templates/dashboard.html
msgid ""
"The selected {provider_name} account is already linked to another "
"{platform_name} account. Please {link_start}log out{link_end}, then log in "
"with your {provider_name} account."
msgstr ""
#: lms/templates/dashboard.html lms/templates/dashboard.html
#: lms/templates/dashboard/_dashboard_info_language.html
msgid "edit"
msgstr ""
#. Translators: this section lists all the third-party authentication
#. providers
#. (for example, Google and LinkedIn) the user can link with or unlink from
#. their edX account.
#: lms/templates/dashboard.html
msgid "Account Links"
msgstr ""
#. Translators: clicking on this removes the link between a user's edX account
#. and their account with an external authentication provider (like Google or
#. LinkedIn).
#: lms/templates/dashboard.html
msgid "unlink"
msgstr ""
#. Translators: clicking on this creates a link between a user's edX account
#. and their account with an external authentication provider (like Google or
#. LinkedIn).
#: lms/templates/dashboard.html
msgid "link"
msgstr ""
#: lms/templates/dashboard.html lms/templates/dashboard.html
msgid "Reset Password"
msgstr ""
@@ -4204,6 +4332,10 @@ msgstr ""
msgid "Unregister"
msgstr ""
#: lms/templates/edit_unit_link.html
msgid "View Unit in Studio"
msgstr ""
#: lms/templates/email_change_failed.html lms/templates/email_exists.html
msgid "E-mail change failed"
msgstr ""
@@ -4259,18 +4391,6 @@ msgstr ""
msgid "Debug: "
msgstr ""
#: lms/templates/enroll_students.html
msgid "foo"
msgstr ""
#: lms/templates/enroll_students.html
msgid "bar"
msgstr ""
#: lms/templates/enroll_students.html
msgid "biff"
msgstr ""
#: lms/templates/extauth_failure.html lms/templates/extauth_failure.html
msgid "External Authentication failed"
msgstr ""
@@ -4378,14 +4498,10 @@ msgstr ""
msgid "Email is incorrect."
msgstr ""
#: lms/templates/help_modal.html
#: lms/templates/help_modal.html lms/templates/help_modal.html
msgid "{platform_name} Help"
msgstr ""
#: lms/templates/help_modal.html
msgid "{span_start}{platform_name}{span_end} Help"
msgstr ""
#: lms/templates/help_modal.html
msgid ""
"For <strong>questions on course lectures, homework, tools, or materials for "
@@ -4428,7 +4544,8 @@ msgstr ""
#: lms/templates/help_modal.html lms/templates/login.html
#: lms/templates/provider_login.html lms/templates/provider_login.html
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/register.html
#: lms/templates/register.html lms/templates/signup_modal.html
#: lms/templates/signup_modal.html
msgid "E-mail"
msgstr ""
@@ -4655,10 +4772,39 @@ msgstr ""
msgid "Remember me"
msgstr ""
#. Translators: this is the last choice of a number of choices of how to log
#. in
#. to the site.
#: lms/templates/login.html
msgid "or, if you have connected one of these providers, log in below."
msgstr ""
#. Translators: provider_name is the name of an external, third-party user
#. authentication provider (like Google or LinkedIn).
#. Translators: provider_name is the name of an external, third-party user
#. authentication service (like Google or LinkedIn).
#: lms/templates/login.html lms/templates/register.html
msgid "Sign in with {provider_name}"
msgstr ""
#. Translators: "External resource" means that this learning module is hosted
#. on a platform external to the edX LMS
#: lms/templates/lti.html
msgid "External resource"
msgstr ""
#. Translators: "points" is the student's achieved score on this LTI unit, and
#. "total_points" is the maximum number of points achievable.
#: lms/templates/lti.html
msgid "{points} / {total_points} points"
msgstr ""
#. Translators: "total_points" is the maximum number of points achievable on
#. this LTI unit
#: lms/templates/lti.html
msgid "{total_points} points possible"
msgstr ""
#: lms/templates/lti.html
msgid "View resource in a new window"
msgstr ""
@@ -4668,6 +4814,14 @@ msgid ""
"Please provide launch_url. Click \"Edit\", and fill in the required fields."
msgstr ""
#: lms/templates/lti.html
msgid "Feedback on your work from the grader:"
msgstr ""
#: lms/templates/lti_form.html
msgid "Press to Launch"
msgstr ""
#: lms/templates/manage_user_standing.html
msgid "Disable or Reenable student accounts"
msgstr ""
@@ -4711,15 +4865,15 @@ msgid ""
msgstr ""
#: lms/templates/module-error.html
msgid "There has been an error on the <em>{platform_name}</em> servers"
#: lms/templates/courseware/courseware-error.html
msgid "There has been an error on the {platform_name} servers"
msgstr ""
#: lms/templates/module-error.html
msgid ""
"We're sorry, this module is temporarily unavailable. Our staff is working to"
" fix it as soon as possible. Please email us at <a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a> to report any "
"problems or downtime."
" fix it as soon as possible. Please email us at {tech_support_email} to "
"report any problems or downtime."
msgstr ""
#: lms/templates/module-error.html
@@ -4771,6 +4925,10 @@ msgstr ""
msgid "Log Out"
msgstr ""
#: lms/templates/navigation.html
msgid "Shopping Cart"
msgstr ""
#: lms/templates/navigation.html
msgid "How it Works"
msgstr ""
@@ -4828,8 +4986,9 @@ msgstr ""
#: lms/templates/provider_login.html
msgid ""
"Please note that we will be sending your user name, email, and full name to "
"this third party site."
"Your username, email, and full name will be sent to {destination}, where the"
" collection and use of this information will be governed by their terms of "
"service and privacy policy."
msgstr ""
#: lms/templates/provider_login.html
@@ -4886,10 +5045,12 @@ msgid "Account Acknowledgements"
msgstr ""
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Terms of Service{link_end}"
msgstr ""
#: lms/templates/register-shib.html lms/templates/register.html
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Honor Code{link_end}"
msgstr ""
@@ -4970,6 +5131,26 @@ msgstr ""
msgid "Register below to create your {platform_name} account"
msgstr ""
#: lms/templates/register.html
msgid "Register to start learning today!"
msgstr ""
#: lms/templates/register.html
msgid ""
"or create your own {platform_name} account by completing all "
"<strong>required*</strong> fields below."
msgstr ""
#. Translators: selected_provider is the name of an external, third-party user
#. authentication service (like Google or LinkedIn).
#: lms/templates/register.html
msgid "You've successfully signed in with {selected_provider}."
msgstr ""
#: lms/templates/register.html
msgid "Finish your account registration below to start learning."
msgstr ""
#: lms/templates/register.html
msgid "Please complete the following fields to register for an account. "
msgstr ""
@@ -5060,33 +5241,17 @@ msgid "Next"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Sign Up for {span_start}{platform_name}{span_end}"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "E-mail *"
msgid "Sign Up for {platform_name}"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. yourname@domain.com"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Password *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "Public Username *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. yourname (shown on forums)"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "Full Name *"
msgstr ""
#: lms/templates/signup_modal.html lms/templates/signup_modal.html
msgid "e.g. Your Name (for certificates)"
msgstr ""
@@ -5095,6 +5260,10 @@ msgstr ""
msgid "<i>Welcome</i> {name}"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Full Name *"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Ed. Completed"
msgstr ""
@@ -5111,14 +5280,6 @@ msgstr ""
msgid "Goals in signing up for {platform_name}"
msgstr ""
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Terms of Service{link_end}*"
msgstr ""
#: lms/templates/signup_modal.html
msgid "I agree to the {link_start}Honor Code{link_end}*"
msgstr ""
#: lms/templates/signup_modal.html
msgid "Already have an account?"
msgstr ""
@@ -5158,7 +5319,7 @@ msgid "Tag"
msgstr ""
#: lms/templates/staff_problem_info.html
msgid "Optional tag (eg \"done\" or \"broken\"):&nbsp; "
msgid "Optional tag (eg \"done\" or \"broken\"):"
msgstr ""
#: lms/templates/staff_problem_info.html
@@ -5203,43 +5364,11 @@ msgstr ""
msgid "Textbook Navigation"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Page:"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/static_pdfbook.html
msgid "Zoom Out"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/static_pdfbook.html
msgid "Zoom In"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Zoom"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Automatic Zoom"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Actual Size"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Fit Page"
msgstr ""
#: lms/templates/static_pdfbook.html
msgid "Full Width"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/staticbook.html
#: lms/templates/staticbook.html
msgid "Previous page"
msgstr ""
#: lms/templates/static_pdfbook.html lms/templates/staticbook.html
#: lms/templates/staticbook.html
msgid "Next page"
msgstr ""
@@ -5488,8 +5617,8 @@ msgstr ""
msgid "Download transcript"
msgstr ""
#: lms/templates/video.html lms/templates/video.html
msgid "{file_format}"
#: lms/templates/video.html
msgid "Download Handout"
msgstr ""
#: lms/templates/word_cloud.html
@@ -5727,6 +5856,10 @@ msgstr ""
msgid "Register for {course.display_number_with_default}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "View About Page in studio"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Overview"
msgstr ""
@@ -5735,6 +5868,20 @@ msgstr ""
msgid "Share with friends and family!"
msgstr ""
#. Translators: This text will be automatically posted to the student's
#. Twitter account. {url} should appear at the end of the text.
#: lms/templates/courseware/course_about.html
msgid "I just registered for {number} {title} through {account}: {url}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Take a course with {platform} online"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "I just registered for {number} {title} through {platform} {url}"
msgstr ""
#: lms/templates/courseware/course_about.html
msgid "Classes Start"
msgstr ""
@@ -5778,17 +5925,11 @@ msgstr ""
msgid "Explore free courses from {university_name}."
msgstr ""
#: lms/templates/courseware/courseware-error.html
msgid ""
"There has been an error on the {span_start}{platform_name}{span_end} servers"
msgstr ""
#: lms/templates/courseware/courseware-error.html
msgid ""
"We're sorry, this module is temporarily unavailable. Our staff is working to"
" fix it as soon as possible. Please email us at '<a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a>' to report any"
" problems or downtime."
" fix it as soon as possible. Please email us at {tech_support_email}' to "
"report any problems or downtime."
msgstr ""
#: lms/templates/courseware/courseware.html
@@ -5918,6 +6059,10 @@ msgstr ""
msgid "{course_number} Course Info"
msgstr ""
#: lms/templates/courseware/info.html
msgid "View Updates in Studio"
msgstr ""
#: lms/templates/courseware/info.html lms/templates/courseware/info.html
msgid "Course Updates &amp; News"
msgstr ""
@@ -5938,12 +6083,12 @@ msgid "Instructor Dashboard"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
msgid "Try New Beta Dashboard"
#: lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
msgid "View Course in Studio"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
msgid "Edit Course In Studio"
msgid "Try New Beta Dashboard"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
@@ -6278,12 +6423,6 @@ msgstr ""
msgid "Count of Students that Opened a Subsection"
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Loading..."
msgstr ""
#: lms/templates/courseware/instructor_dashboard.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Grade Distribution per Problem"
@@ -6397,10 +6536,18 @@ msgstr ""
msgid "Course Progress"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "View Grading in studio"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "Course Progress for Student '{username}' ({email})"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "Download your certificate"
msgstr ""
#: lms/templates/courseware/progress.html
msgid "{earned:.3n} of {total:.3n} possible points"
msgstr ""
@@ -6573,6 +6720,13 @@ msgid ""
" of"
msgstr ""
#: lms/templates/dashboard/_dashboard_course_listing.html
msgid ""
"In order to request a refund for the amount you paid, you will need to send "
"an email to {billing_email}. Be sure to include your email and the course "
"name."
msgstr ""
#: lms/templates/dashboard/_dashboard_course_listing.html
msgid "Email Settings"
msgstr ""
@@ -6691,6 +6845,10 @@ msgstr ""
msgid "Show Discussion"
msgstr ""
#: lms/templates/discussion/_discussion_module_studio.html
msgid "To view live discussions, click Preview or View Live in Unit Settings."
msgstr ""
#: lms/templates/discussion/_filter_dropdown.html
msgid "Filter Topics"
msgstr ""
@@ -7053,24 +7211,14 @@ msgstr ""
msgid "User Profile"
msgstr ""
#: lms/templates/discussion/user_profile.html
msgid "Active Threads"
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Expand discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Loading content"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
#: lms/templates/discussion/mustache/_profile_thread.mustache
msgid "View discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread.mustache
#: lms/templates/discussion/mustache/_inline_thread_cohorted.mustache
msgid "Hide discussion"
msgid "Collapse discussion"
msgstr ""
#: lms/templates/discussion/mustache/_inline_thread_show.mustache
@@ -7082,6 +7230,14 @@ msgstr ""
msgid "…"
msgstr ""
#: lms/templates/discussion/mustache/_profile_thread.mustache
msgid "View discussion"
msgstr ""
#: lms/templates/discussion/mustache/_user_profile.mustache
msgid "Active Threads"
msgstr ""
#: lms/templates/emails/activation_email.txt
msgid ""
"Thank you for signing up for {platform_name}! To activate your account, "
@@ -7121,10 +7277,19 @@ msgid ""
"by a member of the course staff."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
#: lms/templates/emails/enroll_email_enrolledmessage.txt
msgid "To start accessing course materials, please visit {course_url}"
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
msgid "Visit {course_about_url} to join the course and begin the beta test."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
msgid "Visit {site_name} to enroll in the course and begin the beta test."
msgstr ""
#: lms/templates/emails/add_beta_tester_email_message.txt
#: lms/templates/emails/enroll_email_allowedmessage.txt
#: lms/templates/emails/remove_beta_tester_email_message.txt
@@ -7205,6 +7370,10 @@ msgid ""
"{course_about_url} to join the course."
msgstr ""
#: lms/templates/emails/enroll_email_allowedmessage.txt
msgid "You can then enroll in {course_name}."
msgstr ""
#: lms/templates/emails/enroll_email_allowedsubject.txt
msgid "You have been invited to register for {course_name}"
msgstr ""
@@ -7215,10 +7384,6 @@ msgid ""
"course staff. The course should now appear on your {site_name} dashboard."
msgstr ""
#: lms/templates/emails/enroll_email_enrolledmessage.txt
msgid "To start accessing course materials, please visit {course_url}"
msgstr ""
#: lms/templates/emails/enroll_email_enrolledmessage.txt
#: lms/templates/emails/unenroll_email_enrolledmessage.txt
msgid "This email was automatically sent from {site_name} to {full_name}"
@@ -7642,7 +7807,8 @@ msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Enter email addresses separated by new lines or commas."
msgid ""
"Enter email addresses and/or usernames separated by new lines or commas."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
@@ -7653,9 +7819,10 @@ msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Email Addresses"
msgid "Email Addresses/Usernames"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Auto Enroll"
msgstr ""
@@ -7673,6 +7840,10 @@ msgid ""
"once they make an account."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Checking this box has no effect if 'Unenroll' is selected."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Notify users by email"
@@ -7694,7 +7865,7 @@ msgid "Unenroll"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Batch Beta Testers"
msgid "Batch Beta Tester Addition"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
@@ -7703,6 +7874,16 @@ msgid ""
"be enrolled as a beta tester."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid ""
"If this option is <em>checked</em>, users who have not enrolled in your "
"course will be automatically enrolled."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Checking this box has no effect if 'Remove beta testers' is selected."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/membership.html
msgid "Add beta testers"
msgstr ""
@@ -7834,6 +8015,27 @@ msgstr ""
msgid "Count of Students Opened a Subsection"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Download Student Opened as a CSV"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Download Student Grades as a CSV"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "This is a partial list, to view all students download as a csv."
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Grade"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/metrics.html
msgid "Percent"
msgstr ""
#: lms/templates/instructor/instructor_dashboard_2/send_email.html
#: lms/templates/instructor/instructor_dashboard_2/send_email.html
msgid "Send Email"
@@ -8065,6 +8267,12 @@ msgid ""
"{end_p_tag}\n"
msgstr ""
#: lms/templates/peer_grading/peer_grading.html
#: lms/templates/peer_grading/peer_grading_closed.html
#: lms/templates/peer_grading/peer_grading_problem.html
msgid "Peer Grading"
msgstr ""
#: lms/templates/peer_grading/peer_grading.html
msgid ""
"Here are a list of problems that need to be peer graded for this course."
@@ -8301,6 +8509,16 @@ msgstr ""
msgid "Register for [Course Name] | Receipt (Order"
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid "Thank you for your Purchase!"
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid ""
"Please print this receipt page for your records. You should also have "
"received a receipt in your email."
msgstr ""
#: lms/templates/shoppingcart/receipt.html
msgid " () Electronic Receipt"
msgstr ""
@@ -8540,20 +8758,18 @@ msgid "In the Press"
msgstr ""
#: lms/templates/static_templates/server-down.html
msgid "Currently the <em>{platform_name}</em> servers are down"
msgid "Currently the {platform_name} servers are down"
msgstr ""
#: lms/templates/static_templates/server-down.html
#: lms/templates/static_templates/server-overloaded.html
msgid ""
"Our staff is currently working to get the site back up as soon as possible. "
"Please email us at <a "
"href=\"mailto:{tech_support_email}\">{tech_support_email}</a> to report any "
"problems or downtime."
"Please email us at {tech_support_email} to report any problems or downtime."
msgstr ""
#: lms/templates/static_templates/server-error.html
msgid "There has been a 500 error on the <em>{platform_name}</em> servers"
msgid "There has been a 500 error on the {platform_name} servers"
msgstr ""
#: lms/templates/static_templates/server-error.html
@@ -8563,7 +8779,7 @@ msgid ""
msgstr ""
#: lms/templates/static_templates/server-overloaded.html
msgid "Currently the <em>{platform_name}</em> servers are overloaded"
msgid "Currently the {platform_name} servers are overloaded"
msgstr ""
#: lms/templates/university_profile/edge.html
@@ -8934,6 +9150,8 @@ msgid "Complete your other re-verifications"
msgstr ""
#: lms/templates/verify_student/midcourse_reverification_confirmation.html
#: lms/templates/verify_student/midcourse_reverify_dash.html
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "Return to where you left off"
msgstr ""
@@ -8974,13 +9192,7 @@ msgid "Failed"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid ""
"Don't want to re-verify right now? {a_start}Return to where you left "
"off{a_end}"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
msgid "{a_start}Return to where you left off{a_end}"
msgid "Don't want to re-verify right now?"
msgstr ""
#: lms/templates/verify_student/midcourse_reverify_dash.html
@@ -9669,19 +9881,16 @@ msgid ""
"immediately visible to other course team members."
msgstr ""
#: cms/templates/component.html
msgid "Editor"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/studio_xblock_wrapper.html
msgid "Duplicate"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/component.html
msgid "Duplicate this component"
msgstr ""
#: cms/templates/component.html cms/templates/studio_xblock_wrapper.html
#: cms/templates/component.html
msgid "Delete this component"
msgstr ""
@@ -9695,23 +9904,40 @@ msgstr ""
msgid "Container"
msgstr ""
#: cms/templates/container.html cms/templates/studio_vertical_wrapper.html
msgid "No Actions"
#: cms/templates/container.html
msgid "This page has no content yet."
msgstr ""
#: cms/templates/container.html
#: cms/templates/container.html cms/templates/container.html
msgid "Publishing Status"
msgstr ""
#: cms/templates/container.html
msgid "This content is published with unit {unit_name}."
msgid "Published"
msgstr ""
#: cms/templates/container.html
msgid ""
"You can view course components that contain other components on this page. "
"In the case of experiment blocks, this allows you to confirm that you have "
"properly configured your experiment groups."
"To make changes to the content of this page, you need to edit unit "
"{unit_link} as a draft."
msgstr ""
#: cms/templates/container.html
msgid "Draft"
msgstr ""
#: cms/templates/container.html
msgid ""
"You can edit the content of this page, and your changes will be published "
"with unit {unit_link}."
msgstr ""
#: cms/templates/container.html
msgid ""
"You can view and edit course components that contain other components on "
"this page. In the case of experiment blocks, this allows you to confirm that"
" you have properly configured your experiment groups and make changes to "
"existing content."
msgstr ""
#: cms/templates/course_info.html cms/templates/course_info.html
@@ -9746,6 +9972,13 @@ msgstr ""
msgid "View Live"
msgstr ""
#: cms/templates/edit-tabs.html
msgid ""
"Note: Pages are publicly visible. If users know the URL of a page, they can "
"view the page even if they are not registered for or logged in to your "
"course."
msgstr ""
#: cms/templates/edit-tabs.html
msgid "Show this page"
msgstr ""
@@ -11380,6 +11613,10 @@ msgstr ""
msgid "Expand or Collapse"
msgstr ""
#: cms/templates/studio_vertical_wrapper.html
msgid "No Actions"
msgstr ""
#: cms/templates/textbooks.html
msgid "You have unsaved changes. Do you really want to leave this page?"
msgstr ""
@@ -11468,15 +11705,15 @@ msgid ""
msgstr ""
#: cms/templates/unit.html
msgid "This unit is scheduled to be released to <strong>students</strong>"
msgid ""
"This unit is scheduled to be released to <strong>students</strong> on "
"<strong>{date}</strong> with the subsection {link_start}{name}{link_end}"
msgstr ""
#: cms/templates/unit.html
msgid "on {date}"
msgstr ""
#: cms/templates/unit.html
msgid "with the subsection {link_start}{name}{link_end}"
msgid ""
"This unit is scheduled to be released to <strong>students</strong> with the "
"subsection {link_start}{name}{link_end}"
msgstr ""
#: cms/templates/unit.html

View File

@@ -15,8 +15,8 @@ msgid ""
msgstr ""
"Project-Id-Version: edx-platform\n"
"Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n"
"POT-Creation-Date: 2014-03-31 09:26-0400\n"
"PO-Revision-Date: 2014-03-19 20:22+0000\n"
"POT-Creation-Date: 2014-05-02 17:09-0400\n"
"PO-Revision-Date: 2014-04-24 13:20+0000\n"
"Last-Translator: sarina <sarina@edx.org>\n"
"Language-Team: Bengali (Bangladesh) (http://www.transifex.com/projects/p/edx-platform/language/bn_BD/)\n"
"MIME-Version: 1.0\n"
@@ -27,6 +27,7 @@ msgstr ""
#: cms/static/coffee/src/views/tabs.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/modals/edit_xblock.js
#: common/static/coffee/src/discussion/utils.js
msgid "OK"
msgstr ""
@@ -35,6 +36,8 @@ msgstr ""
#: cms/static/js/base.js cms/static/js/views/asset.js
#: cms/static/js/views/course_info_update.js
#: cms/static/js/views/show_textbook.js cms/static/js/views/validation.js
#: cms/static/js/views/modals/base_modal.js
#: cms/static/js/views/pages/container.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
#: lms/static/admin/js/admin/DateTimeShortcuts.js
msgid "Cancel"
@@ -334,6 +337,8 @@ msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_list_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
#: common/static/coffee/src/discussion/views/response_comment_view.js
msgid "Sorry"
msgstr ""
@@ -390,16 +395,14 @@ msgid "vote"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid ""
"%(voteNum)s%(startSrSpan)s vote (click to remove your vote)%(endSrSpan)s"
msgid_plural ""
"%(voteNum)s%(startSrSpan)s votes (click to remove your vote)%(endSrSpan)s"
msgid "vote (click to remove your vote)"
msgid_plural "votes (click to remove your vote)"
msgstr[0] ""
msgstr[1] ""
#: common/static/coffee/src/discussion/views/discussion_content_view.js
msgid "%(voteNum)s%(startSrSpan)s vote (click to vote)%(endSrSpan)s"
msgid_plural "%(voteNum)s%(startSrSpan)s votes (click to vote)%(endSrSpan)s"
msgid "vote (click to vote)"
msgid_plural "votes (click to vote)"
msgstr[0] ""
msgstr[1] ""
@@ -461,6 +464,11 @@ msgstr ""
msgid "Pin Thread"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid ""
"The thread you selected has been deleted. Please select another thread."
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_thread_view.js
msgid "We had some trouble loading responses. Please reload the page."
msgstr ""
@@ -497,6 +505,10 @@ msgstr ""
msgid "Are you sure you want to delete this post?"
msgstr ""
#: common/static/coffee/src/discussion/views/discussion_user_profile_view.js
msgid "We had some trouble loading the page you requested. Please try again."
msgstr ""
#: common/static/coffee/src/discussion/views/response_comment_show_view.js
msgid "anonymous"
msgstr ""
@@ -875,9 +887,10 @@ msgid ""
"beta tester."
msgstr ""
#. Translators: A list of email addresses appears after this sentence;
#. Translators: A list of identifiers (which are email addresses and/or
#. usernames) appears after this sentence;
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "Could not find users associated with the following email addresses:"
msgid "Could not find users associated with the following identifiers:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
@@ -885,7 +898,7 @@ msgid "Error enrolling/unenrolling users."
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
msgid "The following email addresses are invalid:"
msgid "The following email addresses and/or usernames are invalid:"
msgstr ""
#: lms/static/coffee/src/instructor_dashboard/membership.js
@@ -1219,15 +1232,16 @@ msgid "(Show)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid ""
"<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>"
msgid "Insert Hyperlink"
msgstr ""
#. Translators: Please keep the quotation marks (") around this text
#: lms/static/js/Markdown.Editor.js lms/static/js/Markdown.Editor.js.c
msgid "\"optional title\""
msgstr ""
#: lms/static/js/Markdown.Editor.js
msgid ""
"<p><b>Insert Image (upload file or type "
"url)</b></p><p>http://example.com/images/diagram.jpg \"optional "
"title\"<br><br></p>"
msgid "Insert Image (upload file or type url)"
msgstr ""
#: lms/static/js/Markdown.Editor.js
@@ -1337,18 +1351,13 @@ msgstr ""
msgid "Studio's having trouble saving your work"
msgstr ""
#: cms/static/coffee/src/views/module_edit.js
msgid "<em>Editing:</em> %s"
msgstr ""
#: cms/static/coffee/src/views/module_edit.js
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/tabs.js
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/xblock/cms.runtime.v1.js
#: cms/static/js/models/section.js cms/static/js/views/asset.js
#: cms/static/js/views/course_info_handout.js
#: cms/static/js/models/section.js cms/static/js/utils/drag_and_drop.js
#: cms/static/js/views/asset.js cms/static/js/views/course_info_handout.js
#: cms/static/js/views/course_info_update.js cms/static/js/views/overview.js
#: cms/static/js/views/overview.js.c
#: cms/static/js/views/xblock_editor.js
msgid "Saving&hellip;"
msgstr ""
@@ -1364,6 +1373,7 @@ msgstr ""
#: cms/static/coffee/src/views/tabs.js cms/static/coffee/src/views/unit.js
#: cms/static/js/base.js cms/static/js/views/course_info_update.js
#: cms/static/js/views/pages/container.js
msgid "Deleting&hellip;"
msgstr ""
@@ -1371,19 +1381,19 @@ msgstr ""
msgid "Adding&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Duplicating&hellip;"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Delete this component?"
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Deleting this component is permanent and cannot be undone."
msgstr ""
#: cms/static/coffee/src/views/unit.js
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
msgid "Yes, delete this component"
msgstr ""
@@ -1530,6 +1540,10 @@ msgstr ""
msgid "Upload a new PDF to “<%= name %>”"
msgstr ""
#: cms/static/js/views/edit_chapter.js
msgid "Please select a PDF file to upload."
msgstr ""
#: cms/static/js/views/edit_textbook.js
#: cms/static/js/views/overview_assignment_grader.js
msgid "Saving"
@@ -1545,6 +1559,10 @@ msgid ""
"extension."
msgstr ""
#: cms/static/js/views/metadata.js
msgid "Upload File"
msgstr ""
#: cms/static/js/views/overview.js
msgid "Collapse All Sections"
msgstr ""
@@ -1606,6 +1624,10 @@ msgstr ""
msgid "Deleting"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "Upload"
msgstr ""
#: cms/static/js/views/uploads.js
msgid "We're sorry, there was an error"
msgstr ""
@@ -1635,6 +1657,26 @@ msgstr ""
msgid "Your changes have been saved."
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Editor"
msgstr ""
#: cms/static/js/views/xblock_editor.js
msgid "Settings"
msgstr ""
#: cms/static/js/views/modals/base_modal.js
msgid "Save"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Component"
msgstr ""
#: cms/static/js/views/modals/edit_xblock.js
msgid "Editing: %(title)s"
msgstr ""
#: cms/static/js/views/settings/advanced.js
msgid ""
"Your changes will not take effect until you save your progress. Take care "
@@ -1659,3 +1701,13 @@ msgstr ""
#: cms/static/js/views/settings/main.js
msgid "Files must be in JPEG or PNG format."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid ""
"Sorry, there was an error parsing the subtitles that you uploaded. Please "
"check the format and try again."
msgstr ""
#: cms/static/js/views/video/translations_editor.js
msgid "Upload translation"
msgstr ""

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More