Skip to content

Commit

Permalink
Merge pull request #1092 from learningequality/release-v0.3.x
Browse files Browse the repository at this point in the history
v0.3.0
  • Loading branch information
indirectlylit authored Mar 20, 2017
2 parents 54e0696 + 3749510 commit 940672b
Show file tree
Hide file tree
Showing 205 changed files with 23,108 additions and 1,606 deletions.
22 changes: 21 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ module.exports = {
plugins: [
'html'
],
env: {
'browser': true
},
// custom rules
'rules': {
// permit vuex state mutations
Expand All @@ -14,6 +17,23 @@ module.exports = {
'quote-props': ['error', 'consistent-as-needed'],
'quotes': ['error', 'single', { 'allowTemplateLiterals': true }],
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
// These were added during the upgrade to eslint v3 - we may want to remove
// some of these rules going forwards to clean up the code base.
'import/no-extraneous-dependencies': 0,
'import/no-unresolved': 0,
'global-require': 0,
'class-methods-use-this': [ 0, { "exceptMethods": [] }],
// These are required for buble compatibility
'arrow-parens': [ 0, 'always' ],
'comma-dangle': ["error", {
"arrays": "only-multiline",
"objects": "only-multiline",
"imports": "only-multiline",
"exports": "only-multiline",
"functions": "never"
}],
// This is just a codebase convention section
'no-underscore-dangle': 0
}
};
35 changes: 20 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 97b88d9610bcc03982ddac33caba98bb2b751f5f
hooks:
- id: trailing-whitespace
- id: flake8
- id: check-yaml
- id: check-added-large-files
- id: debug-statements
- id: end-of-file-fixer
- repo: git://github.com/FalconSocial/pre-commit-python-sorter
sha: d044ff27300a6dc8b1a56cd22552e3a810dc6f49
hooks:
- id: python-import-sorter
args:
- --silent-overwrite
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: a11d9314b22d8f8c7556443875b731ef05965464
hooks:
- id: trailing-whitespace
- id: flake8
- id: check-yaml
- id: check-added-large-files
- id: debug-statements
- id: end-of-file-fixer
- repo: git://github.com/FalconSocial/pre-commit-python-sorter
sha: 5991d2aea26858d3c3538e0b4e09255b6b99413e
hooks:
- id: python-import-sorter
args:
- --silent-overwrite
- repo: git://github.com/pre-commit/mirrors-eslint
sha: v3.14.0
hooks:
- id: eslint
additional_dependencies: [ 'eslint', 'eslint-plugin-html', 'eslint-config-airbnb', 'eslint-plugin-import', 'eslint-plugin-jsx-a11y']
10 changes: 9 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
Release Notes
=============

0.3.0
-----

- Content Renderers use explicit new API rather than event-based loading
- Add search back in
- Improve pipeline for translating plugins
- Add Spanish and Swahili translations


0.2.0
-----

Expand All @@ -25,7 +34,6 @@ Release Notes
- Add JS-based 'responsive mixin' as alternative to media queries
- Rename 'Learn/Explore' to 'Recommended/Topics'
- Temporarily remove 'search' functionality
- Refactor content renderer API interface
- Add authentication for tasks API


Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ include requirements.txt
include kolibri/VERSION
recursive-include requirements *.txt
recursive-include kolibri/locale *.mo
recursive-include kolibri/locale *.json
recursive-include kolibri/auth *
recursive-include kolibri/content *
recursive-include kolibri/core *
Expand All @@ -20,6 +21,7 @@ recursive-include kolibri/tasks *
recursive-include kolibri/*/static *.*
recursive-include kolibri/*/build/ *.json
recursive-include kolibri/plugins/*/build *.json
include kolibri/plugins/*/content_types.json

recursive-exclude kolibri/* *pyc
recursive-exclude kolibri/core/assets *
Expand Down
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
REQUIREMENTS=requirements.txt

.PHONY: help clean clean-pyc clean-build list test test-all coverage docs release sdist

help:
Expand Down Expand Up @@ -64,7 +66,7 @@ release: clean assets
python setup.py bdist_wheel upload

staticdeps: clean
pip install -t kolibri/dist -r requirements.txt
pip install -t kolibri/dist -r $(REQUIREMENTS)
rm -r kolibri/dist/*.dist-info # pip installs from PyPI will complain if we have more than one dist-info directory.

writeversion:
Expand All @@ -87,7 +89,7 @@ makemessages: assets makedocsmessages
python -m kolibri manage makemessages -- -l en --ignore 'node_modules/*' --ignore 'kolibri/dist/*' --ignore 'docs/conf.py'

compilemessages:
python -m kolibri manage compilemessages -- -l en > /dev/null
python -m kolibri manage compilemessages

syncmessages: ensurecrowdinclient uploadmessages downloadmessages distributefrontendmessages

Expand All @@ -100,9 +102,6 @@ uploadmessages:
downloadmessages:
java -jar crowdin-cli.jar download -b `git symbolic-ref HEAD | xargs basename`

distributefrontendmessages:
python ./utils/distribute_frontend_messages.py

dockerenvclean:
docker container prune -f
docker image prune -f
Expand Down
6 changes: 6 additions & 0 deletions docs/dev/frontend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ All apps should extend the ``KolibriModule`` class found in `kolibri/core/assets

The ``ready`` method will be automatically executed once the Module is loaded and registered with the Kolibri Core App. By convention, JavaScript is injected into the served HTML *after* the ``<rootvue>`` tag, meaning that this tag should be available when the ``ready`` method is called, and the root component (conventionally in *vue/index.vue*) can be mounted here.

Content Renderers
~~~~~~~~~~~~~~~~~

A special kind of Kolibri Module is dedicated to rendering particular content types. All content renderers should extend the ``ContentRendererModule`` class found in `kolibri/core/assets/src/content_renderer_module.js`. In addition, rather than subclassing the ``WebpackBundleHook`` class, content renderers should be defined in the Python code using the ``ContentRendererHook`` class defined in ``kolibri.content.hooks``. In addition to the standard options for the ``WebpackBundleHook``, the ``ContentRendererHook`` also accepts a json file defining the content types that it renders::

.. automodule:: kolibri.content.hooks

Shared Core Functionality
-------------------------
Expand Down
15 changes: 0 additions & 15 deletions frontend_build/src/read_bundle_plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,10 @@ var readBundlePlugins = function(base_dir) {
}
});

// Create name to path mapping to allow translated json files to be copied into
// the correct static directory.

var namePathMapping = {};

bundles.forEach(function (bundle) {
namePathMapping[bundle.name] = path.resolve(path.dirname(bundle.output.path));
});

var locale_dir = path.join(base_dir, 'kolibri', 'locale')

mkdirp.sync(locale_dir);

// This will output a file mapping from the bundle name to the static directory where
// the built files for this mapping are put. This is used for redistributing translated message files
// back to their plugins.

fs.writeFileSync(path.join(locale_dir, 'pathMapping.json'), JSON.stringify(namePathMapping));

// We add some custom configuration options to the bundles that webpack 2 dislikes, clean them up here.
bundles.forEach(function (bundle) {
delete bundle.core_name;
Expand Down
4 changes: 4 additions & 0 deletions kolibri/auth/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.contrib.auth import authenticate, get_user, login, logout
from django.contrib.auth.models import AnonymousUser
from kolibri.logger.models import UserSessionLog
from rest_framework import filters, permissions, status, viewsets
from rest_framework.response import Response

Expand Down Expand Up @@ -218,4 +219,7 @@ def get_session(self, request):
session.update({'facility_id': user.facility_id,
'kind': ['learner'],
'error': '200'})

UserSessionLog.update_log(user)

return session
2 changes: 2 additions & 0 deletions kolibri/content/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def title_description_filter(self, queryset, value):
return exact_match
# if no exact match, fuzzy search using the stemmed_metaphone field in ContentNode that covers the title and description
fuzzed_tokens = [fuzz(word) for word in value.split()]
if not fuzzed_tokens[0]:
return []
token_queries = [reduce(lambda x, y: x | y, [Q(stemmed_metaphone__contains=token) for token in tokens]) for tokens in fuzzed_tokens]
return queryset.filter(
Q(parent__isnull=False),
Expand Down
93 changes: 93 additions & 0 deletions kolibri/content/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Kolibri Content hooks
---------------------
Hooks for managing the display and rendering of content.
"""

from __future__ import absolute_import, print_function, unicode_literals

import json
import logging
import os

from django.conf import settings as django_settings
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from kolibri.core.webpack.hooks import WebpackBundleHook
from le_utils.constants import content_kinds

logger = logging.getLogger(__name__)

_JSON_CONTENT_TYPES_CACHE = {}

class ContentRendererHook(WebpackBundleHook):
"""
An inheritable hook that allows special behaviour for a frontend module that defines
a content renderer.
Reads a JSON file of this format:
{
"kinds": [
{
"name": "kind_name",
"extensions": [
"file_extension"
]
}
]
}
e.g.
{
"kinds": [
{
"name": "video",
"extensions": [
"mp4",
"ogg",
"wmv"
]
}
]
}
Detailing the kinds and file extensions that the renderer can handle.
"""

# Set local path to content type JSON that details the kind, extension pairs this deals with.
content_types_file = ""

class Meta:
abstract = True

@cached_property
def _content_type_json(self):
global _JSON_CONTENT_TYPES_CACHE
if not _JSON_CONTENT_TYPES_CACHE.get(self.unique_slug):
try:
file_path = os.path.join(self._module_file_path, self.content_types_file)
with open(file_path) as f:
content_types = json.load(f)
for kind_data in content_types.get('kinds', []):
if kind_data.get("name") not in dict(content_kinds.choices):
logger.debug("{kind} not found in valid content kinds for plugin {name}".format(
kind=kind_data.get("name"), name=self.unique_slug))
_JSON_CONTENT_TYPES_CACHE[self.unique_slug] = content_types
except IOError:
raise IOError("Content types file not found at {}".format(self.content_types_file))
return _JSON_CONTENT_TYPES_CACHE.get(self.unique_slug, {})

def render_to_page_load_async_html(self):
"""
Generates script tag containing Javascript to register a content renderer.
:returns: HTML of a script tag to insert into a page.
"""
urls = [chunk['url'] for chunk in self.bundle]
tags = self.frontend_message_tag() +\
['<script>{kolibri_name}.registerContentRenderer("{bundle}", ["{urls}"], {content_types});</script>'.format(
kolibri_name=django_settings.KOLIBRI_CORE_JS_NAME,
bundle=self.unique_slug,
urls='","'.join(urls),
content_types=json.dumps(self._content_type_json),
)]
return mark_safe('\n'.join(tags))
File renamed without changes.
34 changes: 34 additions & 0 deletions kolibri/content/templatetags/content_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Content template tags
=====================
To use
.. code-block:: html
{% load webpack_tags %}
<!-- Render on-demand async inclusion tag for content renderers -->
{% content_renderer_assets %}
"""
from __future__ import absolute_import, print_function, unicode_literals

from django import template
from kolibri.core.webpack.utils import webpack_asset_render

from .. import hooks

register = template.Library()

@register.simple_tag()
def content_renderer_assets():
"""
This is a script tag for all ``ContentRendererInclusionHook`` hooks that implement a
render_to_html() method - this is used in in any template to
register any content renderers with the frontend so that they can be dynamically loaded
on demand.
:return: HTML of script tags to insert into template
"""
return webpack_asset_render(hooks.ContentRendererHook, async=True)
32 changes: 15 additions & 17 deletions kolibri/core/assets/src/api-resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,24 +256,22 @@ class Collection {
this.set(response.entity);
// Mark that the fetch has completed.
this.synced = true;
} else if (typeof (response.entity || {}).results !== 'undefined') {
// If it's not, there are two possibilities - something is awry, or we have received
// paginated data! Check to see if it is paginated.
this.clearCache();
// Paginated objects have 'results' as their results object so interpret this as
// such.
this.set(response.entity.results);
this.pageCount = Math.ceil(response.entity.count / this.pageSize);
this.hasNext = Boolean(response.entity.next);
this.hasPrev = Boolean(response.entity.previous);
// Mark that the fetch has completed.
this.synced = true;
} else {
// If it's not, there are two possibilities - something is awry, or we have received
// paginated data! Check to see if it is paginated.
if (typeof (response.entity || {}).results !== 'undefined') {
this.clearCache();
// Paginated objects have 'results' as their results object so interpret this as
// such.
this.set(response.entity.results);
this.pageCount = Math.ceil(response.entity.count / this.pageSize);
this.hasNext = Boolean(response.entity.next);
this.hasPrev = Boolean(response.entity.previous);
// Mark that the fetch has completed.
this.synced = true;
} else {
// It's all gone a bit Pete Tong.
logging.debug('Data appears to be malformed', response.entity);
reject(response);
}
// It's all gone a bit Pete Tong.
logging.debug('Data appears to be malformed', response.entity);
reject(response);
}
// Return the data from the models, not the models themselves.
resolve(this.data);
Expand Down
5 changes: 1 addition & 4 deletions kolibri/core/assets/src/content_renderer_module.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
const KolibriModule = require('kolibri_module');

module.exports = class ContentRenderer extends KolibriModule {
render() {
this.emit(`component_render:${this.contentType}`, this.rendererComponent);
}
get rendererComponent() {
return null;
}
get contentType() {
get contentTypes() {
return null;
}
};
Loading

0 comments on commit 940672b

Please sign in to comment.