You can either run this locally with a default sqlite database after
installing the requirements.txt file, or if you have Docker and
prefer a cleaner environment, install docker-compose with pip
install docker-compose
and run docker-compose up
. This will set up
a near production-ready containerized development environment that
runs migrations, with the django development server running on
port 8070.
To run one-off commands, like shell, you can run
docker-compose run web python manage.py shell
or to create root
user, etc.
Currently in the development environment we compile JSX on every
request, which at the time of this writing is about 5 seconds. If you
want to disable this (because you are working just on python for
example), you can add the line LORE_COMPRESS_ENABLED: True
to
docker-compose.yml under the web -> environment section of the file.
The first request will then take 5 seconds, but subsequent ones will
be subsecond.
To add an application to this, add it to the requirements file, add its needed settings, include its URLs, and provide any needed template overrides.
We have bower installed and configured in the
docker image. This is very handy for documenting and adding
dependencies like backbone or bootstrap. To add a new dependency,
just run docker-compose run web bower install jquery --save
for
example. This will download jquery to the
lore/static/bower/jquery
folder and add it to the bower.json file.
The assets downloaded should be stripped down to as little as needed
before checking in, but the files should be checked into the repository.
We are using django-compressor for static asset compression, along with django-compressor-requirejs for creating requirejs packages. What this means to you is that you need to do static asset additions in your templates with something like:
{% load compress %}
{% compress css %}
<link rel="stylesheet"
href="{% static "bower/bootstrap/dist/css/bootstrap.css" %}"
type="text/css"
charset="utf-8"
>
<style type="text/css">p { border:5px solid green;}</style>
{% endcompress %}
{% compress js %}
<script type="text/requirejs"
src="{% static "bower/requirejs/require.js" %}">
</script>
<script type="text/javascript">
require.config({
baseUrl: '{% static "bower"%}',
paths: {
jquery: 'jquery/dist/jquery',
bootstrap: 'bootstrap/dist/js/boostrap'
}
requirejs(["jquery"], function($) {
.....
}
</script>
{% endcompress %}
The project is setup with
tox and
py.test. It will run pylint, pep8, and
py.test tests with coverage. It will also generate an HTML coverage
report. To run them all inside the docker image, run docker-compose
run web tox
, or if you are running locally, after installing the
requirements file, just run tox
.
The project also contains JavaScript tests which can be run using
[Karma](karma-runner.github.io). tox
will run the JavaScript tests
after the Python tests. You can run only the JavaScript tests using
docker-compose run web tox -e js
, or do continuous JavaScript
testing with docker-compose -f docker-karma.yml up
and connecting
to port 9876 on your docker host.
In addition to local testing, all commits and pull requests are tested on travis-ci.org.
If you want test to run on file changes, the test_requirements.txt
adds pytest-watcher, which can be started with:
docker-compose run web ptw --poll
For additional options like having it say “passed”
out loud, or sending desktop notifications for failures see the
README.
Keep in mind, there can be a bit of a lag between saves and the test running.
To build the sphinx documentation project, run:
docker-compose run web tox -e docs
This will build it on your local machine and you should be able to
point your browser at </path/to/lore/repo>/docs/_build/index.html
.
LORE has a RESTful API that is documented on Apiary http://docs.lore.apiary.io .
Contents:
For convenient reference in development, here are the LORE API docs.
Helper functions for using the models, so external apps don’t tie functionality to internal implementation.
learningresources.api.
LearningResourceException
[source]¶Bases: exceptions.Exception
Base class for our custom exceptions.
learningresources.api.
MissingTitle
[source]¶Bases: object
Class to describe the missing title for importer and for the description path
for_desc_path_field
= u'...'¶for_title_field
= u'Missing Title'¶learningresources.api.
NotFound
[source]¶Bases: learningresources.api.LearningResourceException
Raised by the API when the item requested does not exist.
learningresources.api.
PermissionDenied
[source]¶Bases: learningresources.api.LearningResourceException
Raised by the API when the requested item exists, but the user is not allowed to access it.
learningresources.api.
create_course
(org, repo_id, course_number, run, user_id)[source]¶Add a course to the database.
Parameters: |
|
---|---|
Raises: |
|
Returns: | course – The created course |
Return type: | learningresources.models.Course |
learningresources.api.
create_repo
(name, description, user_id)[source]¶Create a new repository.
Parameters: |
|
---|---|
Returns: | repo – Newly-created repository |
Return type: | learningresources.Repository |
learningresources.api.
create_resource
(course, parent, resource_type, title, content_xml, mpath, url_name, dpath)[source]¶Create a learning resource.
Parameters: |
|
---|---|
Returns: | resource – New LearningResource |
Return type: | learningresources.models.LearningResource |
learningresources.api.
create_static_asset
(course_id, handle)[source]¶Create a static asset. :param course_id: learningresources.models.Course pk :type course_id: int :param handle: file handle :type handle: django.core.files.File
Returns: | learningresources.models.StaticAsset |
---|
learningresources.api.
get_repo
(repo_slug, user_id)[source]¶Get repository for a user if s/he can access it. Returns a repository object if it exists or * raises a 404 if the object does not exist * raises a 403 if the object exists but the user doesn’t have permission
Parameters: |
|
---|---|
Returns: | repo – Repository |
Return type: | learningresource.Repository |
learningresources.api.
get_repos
(user_id)[source]¶Get all repositories a user may access.
Parameters: | user_id (int) – Primary key of user |
---|---|
Returns: | repos – Repositories |
Return type: | query set of learningresource.Repository |
learningresources.api.
get_resource
(resource_id, user_id)[source]¶Get single resource.
Parameters: |
|
---|---|
Returns: | resource – Resource May be None if the resource does not exist or the user does not have permissions. |
Return type: | learningresources.LearningResource |
learningresources.api.
get_resources
(repo_id)[source]¶Get resources from a repository ordered by title.
Parameters: | repo_id (int) – Primary key of the repository |
---|---|
Returns: | list – List of resources |
Return type: | list of learningresources.LearningResource |
learningresources.api.
get_video_sub
(xml)[source]¶Get subtitle IDs from <video> XML.
Parameters: | xml (lxml.etree) – xml for a LearningResource |
---|---|
Returns: | subtitle string |
Return type: | sub string |
learningresources.api.
import_static_assets
(course, path)[source]¶Upload all assets and create model records of them for a given course and path.
Parameters: |
|
---|---|
Returns: | None |
learningresources.api.
join_description_paths
(*args)[source]¶Helper function to format the description path. :param args: description path :type args: unicode
Returns: | Formatted dpath |
---|---|
Return type: | unicode |
learningresources.api.
type_id_by_name
(name)[source]¶Get or create a LearningResourceType by name.
This would do fewer queries if it did all the lookups up front, but this is simpler to read and understand and still prevents most lookups. Also, it can’t prevent inserts, so it’s never guaranteed to be just a single query.
Parameters: | name (unicode) – LearningResourceType.name |
---|---|
Returns: | type_id – Primary key of learningresources.LearningResourceType |
Return type: | int |
learningresources.api.
update_description_path
(resource, force_parent_update=False)[source]¶Updates the specified learning resource description path based on the current title and the parent’s description path :param resource: LearningResource :type resource: learningresources.models.LearningResource :param force_parent_update: force parent update :type force_parent_update: boolean
Returns: | None |
---|
Import OLX data into LORE.
importer.api.
import_children
(course, element, parent, parent_dpath)[source]¶Create LearningResource instances for each element of an XML tree.
Parameters: |
|
---|---|
Returns: | None |
importer.api.
import_course
(bundle, repo_id, user_id, static_dir)[source]¶Import a course from an XBundle object.
Parameters: |
|
---|---|
Returns: | learningresources.models.Course |
importer.api.
import_course_from_file
(filename, repo_id, user_id)[source]¶Import OLX archive from .zip or tar.gz.
Imports from a file and then deletes that file. A valid OLX archive has a single occurrence of the file course.xml in its root directory, or no course.xml in its root and a single occurrence of course.xml in one or more of the root directory’s children.
Parameters: |
|
---|---|
Returns: | None |
Raises: |
|
importer.api.
import_course_from_path
(path, repo_id, user_id)[source]¶Import course from an OLX directory.
Parameters: |
|
---|---|
Returns: | course (learningresources.Course) |
APIs for lore taxonomy application
taxonomy.api.
get_term
(repo_slug, user_id, vocab_slug, term_slug)[source]¶Get Term with existing slug, validating ownership for repo_slug and vocab_slug.
Parameters: | term_id (int) – Term slug |
---|---|
Returns: | Term – The Term with the id |
Return type: | Term |
taxonomy.api.
get_vocabulary
(repo_slug, user_id, vocab_slug)[source]¶Lookup vocabulary given its slug, using repo_slug to validate ownership.
Parameters: |
|
---|---|
Returns: | Vocabulary – The vocabulary from the database |
Return type: | Vocabulary |
Functions for handling roles.
roles.api.
assign_user_to_repo_group
(user, repo, group_type)[source]¶Assign an user to a repo specific group type.
Parameters: |
|
---|---|
Returns: | None |
roles.api.
is_last_admin_in_repo
(user, repo)[source]¶Check if user is the last administrator in the repository. It does not check if the user is an actual administrator and in that case it will simply return False
Parameters: |
|
---|---|
Returns: | bool |
roles.api.
list_users_in_repo
(repo, base_group_type=None)[source]¶List all the users in the repository groups. If the group type is specified, the list is limited to that group.
Parameters: |
|
---|---|
Returns: | list – list of users in one or all the repository groups |
Return type: | list of roles.user_models.UserGroup |
roles.api.
remove_user_from_repo_group
(user, repo, group_type)[source]¶Remove an user from a repository specific group type.
Parameters: |
|
---|---|
Returns: | None |
roles.api.
roles_clear_repo_permissions
(repo)[source]¶Remove all the permissions a group has on a repository. :param repo: repository :type repo: learningresources.models.Repository
Returns: | None |
---|
roles.api.
roles_init_new_repo
(repo)[source]¶Create new groups for the repository.
Parameters: | repo (learningresources.models.Repository) – repository used to create groups and assign permissions to them |
---|---|
Returns: | None |
roles.api.
roles_update_repo
(repo, old_slug)[source]¶Update the groups names for the repository.
Parameters: |
|
---|---|
Returns: | None |
doc_requirements
packages.(_score)
.[requires.io]
dependency update.display_name
so we can use it if url_name
isn’t present.django-compressor-requirejs
requirement.listing.js
to JSX.zIndex
to 0 to not have it float above everything else.get_or_create
to create
.id
attribute for vocab and term select.get_conn()
where conn
is used implicitly.Save
button to right of the term for edit term inside
taxonomy panel.page_size
parameter to allow users to set page size.edit
and delete
buttons.Save and Close
button to learning resource panel..gitignore
.Select2
element to refactor select2
widgets.Allow multiple terms
in the taxonomy panel.
consistent with the rest of the UI.Added view to serve static assets and modified REST API.
Added fix and test for handling deleted Elasticsearch index.
Refactored manage_taxonomies.jsx and related tests.
Sped up test discovery by removing node_modules from search.
Added learning resource types to manage taxonomies UI.
Added learning_resource_types API and learning_resource_types field for vocabularies.
Fixed bug with file path length in static assets.
Added learning resource UI to edit description and terms.
Added terms to the readonly lists.
Allowed blank descriptions for LearningResource model.
Implemented Enter key to add taxonomy term and added test case to fix coverage.
Updated Django to 1.8.3
Correct LORE production URL in Apiary doc.
Added checkbox styling to vocabulary/term facets.
Fixed error message on unsupported terms in learning resource.
Fixed facet checkboxes not showing in production.
Fixed course/run highlight bug.
Default checked radio button for Manage Taxonomies -> Add Vocabulary.
Fixed vertical alignment of taxonomy tabs.
Fixed error message for duplicate vocabulary.
Added docker container for javascript testing.
Added checkboxes and ability to toggle facets.
Added html coverage report for javascript.
Added shim configuration to karma test runner.
Implemented learning_resources API.
Members REST API docs.
Linked video transcripts to learning resources.
Parse static assets from LearningResource.
Removed unused patterns to limit memory use.
fix css to make list vertical align.
Installed JSXHint and configured JSCS to work with JSX files.
Included JSX files in coverage results.
Allow only usernames and not emails in the Members add input.
Added test case, tested menulay all scenarios.
Moved coverage CLI script to utils directory.
Fixed buttons alignment problem in members panel.
Fixed error message behavior for manage taxonomies tab.
Added ability to filter vocabularies by learning resource type.
git-release-notes
- https://www.npmjs.com/package/git-release-notesgit-flow
- https://github.com/nvie/gitflowThese instructions presume you’re releasing LORE version 0.10.0.
git fetch
git checkout master
git pull
git flow release start 0.10.0
vi lore/settings.py # change version number
git-release-notes v0.9.0..master util/release_notes_rst.ejs
vi RELEASE.rst # paste results of prior command at top of file, updating the release number
git add RELEASE.rst lore/settings.py
git commit -m "Release 0.10.0"
git flow release publish 0.10.0
# Verify that the Travis-ci build succeeds
git-release-notes v0.9.0..master util/release_notes.ejs
# Create PR on the ``release-candidate`` branch named ``rc/0.10.0`` (The
# PR description isn't important)
# Merge this PR immediately to kick off the build
# Create PR on the ``release`` branch named ``rc/0.10.0`` using the
# console output from git-release-notes v0.9.0..master util/release_notes.ejs
# as the PR description
# Once the ``release`` branch deploys on https://lore-rc.herokuapp.com ,
# Instruct developers to view the ``release`` branch PR description and
# validate their commits on https://lore-rc.herokuapp.com
# Wait for all checkboxes to be checked
# Check with Dev-Ops buddy whether migrations need to be run for this release
git flow release finish 0.10.0
git push --tags
# Send release notification to LORE mailing list - odl-lore-announce@mit.edu
git fetch
git checkout master
git pull
git flow release start 0.10.0
Update the VERSION
number in lore/settings.py
following Semantic
numbering practice.
vi lore/settings.py # change version number
Generate the release notes in reStructuredText format:
git-release-notes v0.9.0..master util/release_notes_rst.ejs
Update release.rst
with this version’s release notes (the console output
generated by running release_notes_rst.ejs
). Edit the new
release notes to correct the release number, make each statement in the
past tense, capitalize the first word, and end sentences with a period.
vi RELEASE.rst
Add, commit, and push the two files you have updated to publish the release.
git add RELEASE.rst lore/settings.py
git commit -m "Release 0.10.0"
git flow release publish 0.10.0
The “release publish” (push) will trigger a build on Travis CI. https://travis-ci.org/mitodl/lore/builds For each commit throughout the release, it’s good practice to check that the build succeeded before proceeding.
Developers will start their verification tests as soon as
they learn that you’ve created the PR with the checkboxes. Since
the deployment to lore-rc
can take a while, merge the
release-candidate
branch PR as soon as you create it to move
its deployment off the critical path.
Make three PRs based on the release branch you just published:
- One to the
release-candidate
branch namedrc/0.10.0
- Merge this PR immediately to kick off the build.
- One to the
release
branch namedrc/0.10.0
containing a checklist of commits- one to the
master
branch namedRelease 0.10.0
Run git-release-notes
with the other template to create the
Pull Request descriptions for the release
branch.
git-release-notes v0.9.0..master util/release_notes.ejs
and paste the Markdown output as the description of the PR to the release
branch rc/0.10.0
(This is the one with the checkboxes.)
Tell developers to test their changes on the LORE release candidate server, http://lore-rc.herokuapp.com and then bug team members until all the check boxes are checked.
When all the boxes are checked finish the release.
git flow release finish 0.10.0
git push --tags
Follow this by merging the remaining PRs and sending a release notice to LORE users.
The email address of the LORE mailing list we use for release notification is odl-lore-announce@mit.edu It is a mailman list located here: https://mailman.mit.edu:444/mailman/admin/odl-lore-announce/general The list is set to hold all emails for review by list moderators who are notified by email when an email is received.
the important piece for the dev-ops person is migrations so the devops person should actually do the merge to release so they can run migrations before they do so which means we are almost exactly following the edX release strategy, where there is a release master and “dev-ops buddy”.
git-release-notes
¶Create the release notes with a utility called git-release-notes
.
https://www.npmjs.com/package/git-release-notes Its output is written
to the console.
git-release-notes
takes two command line arguments. The first argument
takes a range of git references to determine which commits to include in
the release. In the example given it is all commits from the last release
to what is in master
.
The second command argument specifies a template to format the output. LORE has two templates.
util/release_notes_rst.ejs
formats the output as
reStructuredText for inclusion in RELEASE.rst
.util/release_notes.ejs
formats the output as Markdown so as to
precede each commit with a checkbox, and follow each with a link to
the developer’s commit history. Add these release notes as the
description for the GitHub PR to the release
branch.The LORE project uses a different branch naming scheme than is the
Git-Flow default. The easiest way to configure this alternate scheme
is to add this block to your ~/.gitconfig
file.
[gitflow "branch"]
master = release
develop = master
[gitflow "prefix"]
feature = feature/
release = rc/
hotfix = hotfix/
support = support/
versiontag = v
LORE is known to render differently in Firefox and Chrome, with Chrome producing less satisfactory results. So testing on Chrome is recommended.
Note
You may only create a repository if you have the staff
role
in Django.
Action | Result | Notes |
---|---|---|
On home page, click Create Repository link. |
The repository listing page appears. | |
On Create Repository page, enter repository title and
description. Both are required. Click Add Repo . |
The repository listing |
Note
Since many LORE issues are related to large size and “creative” content, import large and potentially problematic courses such as 8.01x ().
A large course import can be slow, anywhere from several minutes to several hours.
Action | Result | Notes |
---|---|---|
From the listing page, click Import Course . |
||
Click Browse to select a course bundled in tar.gz format.
Once selected, click Upload Course . |
Course title appears in ‘Course’ facet list with the total number of LRs following the title. |
Include occasional non-alphanumeric characters in vocabulary and term names.
Action | Result | Notes |
---|---|---|
Open Manage Taxonomy pane and click the Add Vocabulary tab. |
Enter Name and Description fields, both are required. |
Consider using unicode and punctuation in the names. |
Select item types and click Managed and leave other
choices unchecked. Click Save . |
The Vocabularies tab appears with the created vocabulary at the
bottom of the list. In addition, the vocabulary will appear in
the facet panel with the psuedo term not tagged . |
|
Repeat the initial step, this time select item types and
click Tag Style (on the fly) . Click Save . |
||
Repeat the steps to create Managed and Tag Style
vocabularies, but this time click Allow multiple terms...
and save as before. |
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Click a LR to open it’s detail pane and navigate to the
Metadata tab. |
You will see Vocabularies and Terms dropdown controls. |
You must have created a vocabulary containing at least one term to assign terms to LRs. |
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Action | Result | Notes |
---|---|---|
Click on twisty to collapse Course facet | Twisty changes direction and course facets are hidden. |
Action | Result | Notes |
---|---|---|
Enter “momentum” in the search control and click the magnifying glass icon. |
Action | Result | Notes |
---|---|---|
Open the LR pane. | A three-tabbed pane will slide out from the right side of the page. |
Action | Result | Notes |
---|---|---|
Click multiple LR ‘export’ links | The arrow on the link becomes a check and a blue button labeled “Export” appears in the upper right corner of the page. | Include one or more of each LR type and include some that have static assets. |
Click the big blue ‘Export’ button | A dialog will appear to download the export file. | |
Click “OK” to download the CSV file | What happens when the file is downloaded depends on how your computer configuration. You may see a CSV file, a directory of the file’s contents, or the file opened by a spreadsheet app | |
Verify that the contents of the file match the LRs and static assets selected for download. | The directory structure of the file contents should have directories for each LR type containing static assets in a subdirectory. |
To fully test the sort feature, the repository must contain analytics data. A script can be run to load fake analytics data if the analytics service is still unavailable.
Action | Result | Notes |
---|---|---|
Sort by ‘Number of Views (desc)’ | ||
Sort by ‘Number of Attempts (desc)’ | ||
Sort by ‘Average Grade (desc)’ | ||
Sort by ‘Title (asc)’ |
Action | Result | Notes |
---|---|---|
Note
Action | Result | Notes |
---|---|---|
Add a member as Administrator | ||
Add a member as Curator | ||
Add a member as Author | ||
Delete an Administrator | ||
Delete the last Administrator for the repository | ||
Delete a Curator | ||
Delete an Author |