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:
1
AUTHORS
1
AUTHORS
@@ -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>
|
||||
|
||||
351
CONTRIBUTING.rst
351
CONTRIBUTING.rst
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
-----------------
|
||||
|
||||
@@ -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', [])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
define(["backbone", "js/models/course_relative"], function(Backbone, CourseRelativeModel) {
|
||||
var CourseRelativeCollection = Backbone.Collection.extend({
|
||||
model: CourseRelativeModel
|
||||
});
|
||||
return CourseRelativeCollection;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
42
common/djangoapps/track/shim.py
Normal file
42
common/djangoapps/track/shim.py
Normal 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
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
121
common/djangoapps/track/tests/test_shim.py
Normal file
121
common/djangoapps/track/tests/test_shim.py
Normal 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]
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, '"')
|
||||
.replace(/'/g, ''');
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
363
common/lib/xmodule/xmodule/lti_2_util.py
Normal file
363
common/lib/xmodule/xmodule/lti_2_util.py
Normal 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', "")
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
|
||||
372
common/lib/xmodule/xmodule/tests/test_lti20_unit.py
Normal file
372
common/lib/xmodule/xmodule/tests/test_lti20_unit.py
Normal 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 <script>alert(3)</script>"), # 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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
76
common/test/acceptance/pages/lms/auto_auth.py
Normal file
76
common/test/acceptance/pages/lms/auto_auth.py
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
@@ -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 "© 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 & 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"
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
BIN
conf/locale/az/LC_MESSAGES/django.mo
Normal file
BIN
conf/locale/az/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
12580
conf/locale/az/LC_MESSAGES/django.po
Normal file
12580
conf/locale/az/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
conf/locale/az/LC_MESSAGES/djangojs.mo
Normal file
BIN
conf/locale/az/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
1712
conf/locale/az/LC_MESSAGES/djangojs.po
Normal file
1712
conf/locale/az/LC_MESSAGES/djangojs.po
Normal 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…"
|
||||
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…"
|
||||
msgstr ""
|
||||
|
||||
#: cms/static/coffee/src/views/unit.js
|
||||
msgid "Adding…"
|
||||
msgstr ""
|
||||
|
||||
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
|
||||
msgid "Duplicating…"
|
||||
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 ""
|
||||
Binary file not shown.
@@ -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\"): "
|
||||
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 & 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
|
||||
|
||||
Binary file not shown.
@@ -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…"
|
||||
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…"
|
||||
msgstr ""
|
||||
|
||||
@@ -1370,19 +1380,19 @@ msgstr ""
|
||||
msgid "Adding…"
|
||||
msgstr ""
|
||||
|
||||
#: cms/static/coffee/src/views/unit.js
|
||||
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
|
||||
msgid "Duplicating…"
|
||||
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 ""
|
||||
|
||||
Binary file not shown.
@@ -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\"): "
|
||||
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 & 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
|
||||
|
||||
Binary file not shown.
@@ -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…"
|
||||
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…"
|
||||
msgstr ""
|
||||
|
||||
@@ -1371,19 +1381,19 @@ msgstr ""
|
||||
msgid "Adding…"
|
||||
msgstr ""
|
||||
|
||||
#: cms/static/coffee/src/views/unit.js
|
||||
#: cms/static/coffee/src/views/unit.js cms/static/js/views/pages/container.js
|
||||
msgid "Duplicating…"
|
||||
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 ""
|
||||
|
||||
BIN
conf/locale/bn_IN/LC_MESSAGES/django.mo
Normal file
BIN
conf/locale/bn_IN/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
12580
conf/locale/bn_IN/LC_MESSAGES/django.po
Normal file
12580
conf/locale/bn_IN/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
conf/locale/bn_IN/LC_MESSAGES/djangojs.mo
Normal file
BIN
conf/locale/bn_IN/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user