Commit 9b079037 by Xavier Antoviaque

Merge pull request #256 from open-craft/rebase-20140926-merged-xblock-discussion-changes

Rebase 20140926 merged xblock discussion changes
parents f5799fe2 3a246ec9
......@@ -10,6 +10,7 @@ from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from edxmako import add_lookup
from edxmako.shortcuts import render_to_string, render_to_response
from xblock.core import XBlock
from xmodule.modulestore.django import modulestore
......@@ -18,6 +19,10 @@ from contentstore.utils import reverse_course_url, reverse_usage_url
__all__ = ['edge', 'event', 'landing']
# Add Discussion templates
add_lookup('lms.main', 'templates', package='discussion_app')
# points to the temporary course landing page with log in and sign up
def landing(request, org, course, coursename):
return render_to_response('temp-course-landing.html', {})
......@@ -43,6 +48,22 @@ def render_from_lms(template_name, dictionary, context=None, namespace='main'):
return render_to_string(template_name, dictionary, context, namespace="lms." + namespace)
def _xmodule_recurse(item, action, ignore_exception=()):
"""
Recursively apply provided action on item and its children
ignore_exception (Exception Object): A optional argument; when passed ignores the corresponding
exception raised during xmodule recursion,
"""
for child in item.get_children():
_xmodule_recurse(child, action, ignore_exception)
try:
return action(item)
except ignore_exception:
return
def get_parent_xblock(xblock):
"""
Returns the xblock that is the parent of the specified xblock, or None if it has no parent.
......
......@@ -17,3 +17,6 @@ def run():
clear_lookups(namespace)
for directory in directories:
add_lookup(namespace, directory)
# Add Discussion templates
add_lookup('main', 'templates', package='discussion_app')
......@@ -55,9 +55,9 @@ class DiscussionModule(DiscussionFields, XModule):
'course': self.get_course(),
}
if getattr(self.system, 'is_author_mode', False):
template = 'discussion/_discussion_module_studio.html'
template = '/discussion/_discussion_inline_studio.html'
else:
template = 'discussion/_discussion_module.html'
template = '/discussion/_discussion_inline.html'
return self.system.render_template(template, context)
def get_course(self):
......
......@@ -2,6 +2,7 @@ import logging
import os
import sys
import yaml
import xml.sax.saxutils as saxutils
from functools import partial
from lxml import etree
......@@ -1223,7 +1224,119 @@ class XMLParsingSystem(DescriptorSystem):
self.process_xml = process_xml
class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method
class DiscussionService(object):
"""
This is a temporary service that provides everything needed to render the discussion template.
Used by xblock-discussion
"""
def __init__(self, runtime):
self.runtime = runtime
def get_course_template_context(self):
"""
Returns the context to render the course-level discussion templates.
"""
import json
from django.http import HttpRequest
import lms.lib.comment_client as cc
from courseware.access import has_access
from courseware.courses import get_course_with_access
from django_comment_client.forum.views import get_threads
from django_comment_client.permissions import cached_has_permission
import django_comment_client.utils as utils
from course_groups.cohorts import (
is_course_cohorted,
get_cohort_id,
get_cohorted_commentables,
get_course_cohorts
)
escapedict = {'"': '"'}
request = HttpRequest()
user = self.runtime.user
request.user = user
user_info = cc.User.from_django_user(self.runtime.user).to_dict()
course_id = self.runtime.course_id
course = get_course_with_access(self.runtime.user, 'load_forum', course_id)
user_cohort_id = get_cohort_id(user, course_id)
unsafethreads, query_params = get_threads(request, course_id)
threads = [utils.safe_content(thread) for thread in unsafethreads]
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or \
has_access(user, 'staff', course)
annotated_content_info = utils.get_metadata_for_threads(course_id, threads, user, user_info)
category_map = utils.get_discussion_category_map(course)
cohorts = get_course_cohorts(course_id)
cohorted_commentables = get_cohorted_commentables(course_id)
context = {
'course': course,
'course_id': course_id,
'staff_access': has_access(user, 'staff', course),
'threads': saxutils.escape(json.dumps(threads), escapedict),
'thread_pages': query_params['num_pages'],
'user_info': saxutils.escape(json.dumps(user_info), escapedict),
'flag_moderator': flag_moderator,
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
'category_map': category_map,
'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
'is_moderator': cached_has_permission(user, "see_all_cohorts", course_id),
'cohorts': cohorts,
'user_cohort': user_cohort_id,
'cohorted_commentables': cohorted_commentables,
'is_course_cohorted': is_course_cohorted(course_id),
'has_permission_to_create_thread': cached_has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': cached_has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': cached_has_permission(user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': cached_has_permission(user, "openclose_thread", course_id)
}
return context
def get_inline_template_context(self, discussion_id):
"""
Returns the context to render inline discussion templates.
"""
import lms.lib.comment_client as cc
from courseware.courses import get_course_with_access
from courseware.access import has_access
from django_comment_client.permissions import cached_has_permission
from django_comment_client.utils import get_discussion_category_map
course_id = self.runtime.course_id
user = self.runtime.user
course = get_course_with_access(user, 'load_forum', course_id)
category_map = get_discussion_category_map(course)
is_moderator = cached_has_permission(user, "see_all_cohorts", course_id)
flag_moderator = cached_has_permission(user, 'openclose_thread', course_id) or \
has_access(user, 'staff', course)
context = {
'course': course,
'category_map': category_map,
'is_moderator': is_moderator,
'flag_moderator': flag_moderator,
'has_permission_to_create_thread': cached_has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': cached_has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': cached_has_permission(user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': cached_has_permission(user, "openclose_thread", course_id)
}
return context
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,
......@@ -1307,6 +1420,10 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylin
modules, which have an anonymous handler, to set legitimate users' data
"""
# Add the DiscussionService for the LMS and Studio.
services = kwargs.setdefault('services', {})
services['discussion'] = DiscussionService(self)
# Usage_store is unused, and field_data is often supplanted with an
# explicit field_data during construct_xblock.
super(ModuleSystem, self).__init__(id_reader=OpaqueKeyReader(), field_data=field_data, **kwargs)
......
......@@ -35,8 +35,13 @@ from user_api.models import UserPreference
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore import Location
from django.contrib.auth.models import User
from user_api.models import UserPreference
from notification_prefs import NOTIFICATION_PREF_KEY
TEST_API_KEY = str(uuid.uuid4())
class SecureClient(Client):
""" Django test client using a "secure" connection. """
......
......@@ -28,6 +28,7 @@ from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from gradebook.models import StudentGradebook
from instructor.access import revoke_access, update_forum_role
from lang_pref import LANGUAGE_KEY
from notification_prefs.views import enable_notifications
from lms.lib.comment_client.user import User as CommentUser
from lms.lib.comment_client.utils import CommentClientRequestError
from notification_prefs.views import enable_notifications
......
......@@ -13,7 +13,7 @@ import newrelic.agent
from edxmako.shortcuts import render_to_response
from courseware.courses import get_course_with_access
from course_groups.cohorts import (is_course_cohorted, get_cohort_id, get_cohorted_threads_privacy,
get_course_cohorts, get_cohort_by_id)
get_cohorted_commentables, get_course_cohorts, get_cohort_by_id)
from courseware.access import has_access
from django_comment_client.permissions import cached_has_permission
......@@ -229,6 +229,7 @@ def forum_form_discussion(request, course_id):
'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_id),
'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template
'user_cohort': user_cohort_id, # read from container in NewPostView
'cohorted_commentables': (get_cohorted_commentables(course_id)),
'is_course_cohorted': is_course_cohorted(course_id), # still needed to render _thread_list_template
'sort_preference': user.default_sort_key,
'category_map': course_settings["category_map"],
......@@ -319,7 +320,12 @@ def single_thread(request, course_id, discussion_id, thread_id):
'user_cohort': user_cohort,
'sort_preference': cc_user.default_sort_key,
'category_map': course_settings["category_map"],
'course_settings': _attr_safe_json(course_settings)
'course_settings': _attr_safe_json(course_settings),
'cohorted_commentables': (get_cohorted_commentables(course_id)),
'has_permission_to_create_thread': cached_has_permission(request.user, "create_thread", course_id),
'has_permission_to_create_comment': cached_has_permission(request.user, "create_comment", course_id),
'has_permission_to_create_subcomment': cached_has_permission(request.user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': cached_has_permission(request.user, "openclose_thread", course_id)
}
return render_to_response('discussion/index.html', context)
......
import os
from django.conf import settings
from mako.template import Template
import os
from discussion_app.views import get_template_dir as discussion_get_template_dir
def include_mustache_templates():
mustache_dir = settings.PROJECT_ROOT / 'templates' / 'discussion' / 'mustache'
mustache_dir = discussion_get_template_dir() + '/discussion/mustache'
def is_valid_file_name(file_name):
return file_name.endswith('.mustache')
def read_file(file_name):
return open(mustache_dir / file_name, "r").read().decode('utf-8')
return open(mustache_dir + '/' + file_name, "r").read().decode('utf-8')
def template_id_from_file_name(file_name):
return file_name.rpartition('.')[0]
def process_mako(template_content):
......
......@@ -37,6 +37,11 @@ from xmodule.modulestore.modulestore_settings import update_module_store_setting
from lms.lib.xblock.mixin import LmsBlockMixin
from discussion_app.views import (
get_js_urls as discussion_get_js_urls,
get_css_urls as discussion_get_css_urls
)
################################### FEATURES ###################################
# The display name of the platform to be used in templates/emails/etc.
PLATFORM_NAME = "Your Platform Name Here"
......@@ -983,7 +988,7 @@ main_vendor_js = [
'js/vendor/URI.min.js',
]
discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js'))
discussion_js = discussion_get_js_urls()
staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js'))
open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js'))
notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js'))
......@@ -1030,6 +1035,10 @@ PIPELINE_CSS = {
],
'output_filename': 'css/lms-style-app-extend2.css',
},
'style-discussion-app': {
'source_filenames': discussion_get_css_urls(),
'output_filename': 'css/lms-style-discussion-app.css',
},
'style-course-vendor': {
'source_filenames': [
'js/vendor/CodeMirror/codemirror.css',
......@@ -1355,6 +1364,7 @@ INSTALLED_APPS = (
'django_comment_client',
'django_comment_common',
'notes',
'discussion_app',
# Splash screen
'splash',
......@@ -1459,7 +1469,9 @@ if FEATURES.get('ENABLE_CORS_HEADERS'):
'cors_csrf.middleware.CorsCSRFMiddleware',
) + MIDDLEWARE_CLASSES
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ()
CORS_ORIGIN_WHITELIST = ('devstack.local', 'apros.devstack.local')
CORS_ORIGIN_REGEX_WHITELIST = ('^http?://(\w+\.)?devstack\.local$',)
###################### Registration ##################################
......
......@@ -125,6 +125,34 @@ class LmsHandlerUrls(object):
return '//{}{}'.format(settings.SITE_NAME, path)
class LmsCourse(object):
"""
A runtime mixin that provides the course object.
This must be mixed in to a runtime that already accepts and stores
a course_id.
"""
@property
def course(self):
# TODO using 'modulestore().get_course(self._course_id)' doesn't work. return None
from courseware.courses import get_course
return get_course(self.course_id)
class LmsUser(object):
"""
A runtime mixin that provides the user object.
This must be mixed in to a runtime that already accepts and stores
a anonymous_student_id and has get_real_user method.
"""
@property
def user(self):
return self.get_real_user(self.anonymous_student_id)
class LmsPartitionService(PartitionService):
"""
Another runtime mixin that provides access to the student partitions defined on the
......@@ -185,7 +213,7 @@ class UserTagsService(object):
self.runtime.course_id, key, value)
class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract-method
class LmsModuleSystem(LmsHandlerUrls, LmsCourse, LmsUser, ModuleSystem): # pylint: disable=abstract-method
"""
ModuleSystem specialized to the LMS
"""
......
......@@ -159,12 +159,7 @@ span.edx {
}
.loading-animation {
position: absolute;
left: 50%;
width: 20px;
height: 20px;
margin-left: -10px;
background: url(../images/spinner.gif) no-repeat;
@extend %loading-animation;
}
mark {
......
......@@ -165,3 +165,40 @@
white-space: nowrap;
text-overflow: ellipsis;
}
%tooltip {
position: absolute;
top: 0;
left: 0;
z-index: 99999;
padding: 0 10px;
border-radius: 3px;
background: rgba(0, 0, 0, .85);
font-size: 11px;
font-weight: 400;
line-height: 26px;
color: #fff;
pointer-events: none;
opacity: 0;
@include transition(opacity .1s linear 0s);
&:after {
content: '▾';
display: block;
position: absolute;
bottom: -14px;
left: 50%;
margin-left: -7px;
font-size: 20px;
color: rgba(0, 0, 0, .85);
}
}
%loading-animation {
position: absolute;
left: 50%;
width: 20px;
height: 20px;
margin-left: -10px;
background: url(../images/spinner.gif) no-repeat;
}
......@@ -101,31 +101,7 @@ img {
}
.tooltip {
position: absolute;
top: 0;
left: 0;
z-index: 99999;
padding: 0 10px;
border-radius: 3px;
background: rgba(0, 0, 0, .85);
font-size: 11px;
font-weight: 400;
line-height: 26px;
color: #fff;
pointer-events: none;
opacity: 0;
@include transition(opacity .1s linear 0s);
&:after {
content: '▾';
display: block;
position: absolute;
bottom: -14px;
left: 50%;
margin-left: -7px;
font-size: 20px;
color: rgba(0, 0, 0, .85);
}
@extend %tooltip;
}
.test-class {
......
// Discussion app styles
@import 'bourbon/bourbon';
// base - utilities
@import 'base/variables';
@import 'base/mixins';
@import 'base/extends';
// base - elements
@import 'elements/typography';
@import 'shared/modal';
@import 'discussion/discussion';
// forums - main app styling
// ====================
body.discussion {
body.discussion, .discussion-course {
.course-tabs .right {
// new post creation
.new-post-form-errors {
display: none;
background: $error-red;
padding: 0;
border: 1px solid $dark-gray;
list-style: none;
color: $white;
line-height: 1.6;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, .2);
li {
padding: ($baseline/2) $baseline 12px 45px;
border-bottom: 1px solid #dc4949;
background: url(../images/white-error-icon.png) no-repeat 15px 14px;
&:last-child {
border-bottom: none;
}
}
}
.course-tabs .right, .discussion-course-new.right {
float: right;
.new-post-btn {
......@@ -109,7 +132,7 @@ body.discussion {
}
.wmd-panel {
min-width: 500px;
min-width: 425px;
width: 100%;
}
......@@ -224,7 +247,7 @@ body.discussion {
}
}
.container .discussion-body {
.container .discussion-body, .discussion-module, .discussion-course {
@include clearfix;
display: block;
border: none;
......@@ -232,6 +255,20 @@ body.discussion {
box-shadow: none;
line-height: 1.4;
.sidebar ul {
list-style: none !important;
margin: 0 !important;
padding-left: 0 !important;
}
.sr {
@extend %text-sr;
}
.wmd-panel textarea.wmd-input {
margin-bottom: 0px;
}
.bottom-post-status {
padding: 30px;
font-size: 20px;
......
......@@ -30,8 +30,6 @@ ${page_title_breadcrumbs(course_name())}
<%block name="nav_skip">${"#seq_content" if section_title else "#course-content"}</%block>
<%include file="../discussion/_js_head_dependencies.html" />
% if show_chat:
<link rel="stylesheet" href="${static.url('css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css')}" />
## It'd be better to have this in a place like lms/css/vendor/candy,
......
......@@ -12,36 +12,13 @@
<%block name="headextra">
<%static:css group='style-course-vendor'/>
<%static:css group='style-course'/>
<%include file="_js_head_dependencies.html" />
</%block>
<%block name="js_extra">
<%include file="_js_body_dependencies.html" />
<%include file="/discussion/_js_body_dependencies.html" />
<%static:js group='discussion'/>
</%block>
<%include file="_discussion_course_navigation.html" args="active_page='discussion'" />
<section class="discussion container" id="discussion-container"
data-roles="${roles}"
data-course-id="${course_id}"
data-user-info="${user_info}"
data-threads="${threads}"
data-thread-pages="${thread_pages}"
data-content-info="${annotated_content_info}"
data-sort-preference="${sort_preference}"
data-flag-moderator="${flag_moderator}"
data-user-cohort-id="${user_cohort}"
data-course-settings="${course_settings}">
<div class="discussion-body">
<div class="forum-nav"></div>
<div class="discussion-column">
<article class="new-post-article" style="display: none"></article>
<div class="forum-content"></div>
</div>
</div>
</section>
<%include file="_underscore_templates.html" />
<%include file="_thread_list_template.html" />
<%include file="/discussion/_discussion_course.html" />
......@@ -9,12 +9,10 @@
<%block name="headextra">
<%static:css group='style-course-vendor'/>
<%static:css group='style-course'/>
<%include file="_js_head_dependencies.html" />
</%block>
<%block name="js_extra">
<%include file="_js_body_dependencies.html" />
<%include file="/discussion/_js_body_dependencies.html" />
<%static:js group='discussion'/>
</%block>
......@@ -26,7 +24,7 @@
<nav>
<article class="sidebar-module discussion-sidebar">
<%include file="_user_profile.html" />
<%include file="/discussion/_user_profile.html" />
</article>
</nav>
......@@ -36,4 +34,4 @@
</div>
</section>
<%include file="_underscore_templates.html" />
<%include file="/discussion/_underscore_templates.html" />
......@@ -58,6 +58,7 @@
<%static:css group='style-app'/>
<%static:css group='style-app-extend1'/>
<%static:css group='style-app-extend2'/>
<%static:css group='style-discussion-app'/>
<%static:js group='main_vendor'/>
......@@ -119,6 +120,7 @@
<body class="<%block name='bodyclass'/> lang_${LANGUAGE_CODE}">
<a class="nav-skip" href="<%block name="nav_skip">#content</%block>">${_("Skip to this view's content")}</a>
<%include file="mathjax_include.html" />
<%include file="mathjax_accessible.html" />
<%include file="${header_file}" />
......
......@@ -13,6 +13,7 @@
{% compressed_css 'style-app-extend2' %}
{% compressed_css 'style-course-vendor' %}
{% compressed_css 'style-course' %}
{% compressed_css 'style-discussion-app' %}
{% block main_vendor_js %}
{% compressed_js 'main_vendor' %}
......@@ -50,6 +51,8 @@
{% include "footer.html" %}
{% endif %}
{% include "mathjax_include.html" %}
{% compressed_js 'application' %}
{% compressed_js 'module-js' %}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment