Commit 282bddd4 by Alan Boudreault Committed by Jonathan Piacenti

XBlock Discussion changes

Conflicts:
	cms/djangoapps/contentstore/views/helpers.py
	common/static/coffee/src/discussion/content.coffee
	common/static/coffee/src/discussion/discussion_filter.coffee
	common/static/coffee/src/discussion/discussion_module_view.coffee
	common/static/coffee/src/discussion/discussion_router.coffee
	common/static/coffee/src/discussion/views/discussion_content_view.coffee
	common/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee
	common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee
	common/static/coffee/src/discussion/views/discussion_thread_view.coffee
	common/static/coffee/src/discussion/views/new_post_view.coffee
	common/static/coffee/src/discussion/views/response_comment_show_view.coffee
	common/static/coffee/src/discussion/views/thread_response_show_view.coffee
	common/static/coffee/src/discussion/views/thread_response_view.coffee
	lms/djangoapps/django_comment_client/forum/views.py
	lms/static/sass/discussion/_discussion.scss
	lms/templates/discussion/_discussion_module.html
	lms/templates/discussion/_filter_dropdown.html
	lms/templates/discussion/_js_head_dependencies.html
	lms/templates/discussion/_thread_list_template.html
	lms/templates/discussion/_underscore_templates.html
	lms/templates/discussion/index.html
	lms/templates/discussion/mustache/_inline_discussion.mustache
	lms/templates/discussion/mustache/_pagination.mustache
	lms/templates/discussion/mustache/_profile_thread.mustache
parent 650582eb
......@@ -12,6 +12,7 @@ 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 opaque_keys.edx.keys import UsageKey
from xblock.core import XBlock
......@@ -37,6 +38,14 @@ GRADER_TYPES = {
}
# Add Discussion templates
add_lookup('lms.main', 'templates', package='discussion_app')
# 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', {})
......@@ -62,6 +71,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')
......@@ -59,9 +59,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):
......
......@@ -3,6 +3,7 @@ import os
import sys
import time
import yaml
import xml.sax.saxutils as saxutils
from contracts import contract, new_contract
from functools import partial
......@@ -1556,7 +1557,119 @@ class XMLParsingSystem(DescriptorSystem):
setattr(xblock, field.name, field_value)
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 has_permission
import django_comment_client.utils as utils
from openedx.core.djangoapps.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.prepare_content(thread) for thread in unsafethreads]
flag_moderator = 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, user)
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': 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': has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': has_permission(user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': 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 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 = has_permission(user, "see_all_cohorts", course_id)
flag_moderator = 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': has_permission(user, "create_thread", course_id),
'has_permission_to_create_comment': has_permission(user, "create_comment", course_id),
'has_permission_to_create_subcomment': has_permission(user, "create_subcomment", course_id),
'has_permission_to_openclose_thread': 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,
......@@ -1646,6 +1759,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.
kwargs.setdefault('id_reader', getattr(descriptor_runtime, 'id_reader', OpaqueKeyReader()))
......
......@@ -26,6 +26,8 @@ from openedx.core.djangoapps.course_groups.cohorts import (
is_commentable_cohorted
)
from courseware.tabs import EnrolledTab
from course_groups.cohorts import (is_course_cohorted, get_cohort_id, get_cohorted_threads_privacy,
get_cohorted_commentables, get_course_cohorts, get_cohort_by_id)
from courseware.access import has_access
from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx
......@@ -285,8 +287,9 @@ def forum_form_discussion(request, course_key):
'roles': _attr_safe_json(utils.get_role_ids(course_key)),
'is_moderator': has_permission(request.user, "see_all_cohorts", course_key),
'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template
'user_cohort': user_cohort_id, # read from container in NewPostView
'is_course_cohorted': is_course_cohorted(course_key), # still needed to render _thread_list_template
'user_cohort': user_cohort_id, # read from container in NewPostView
'cohorted_commentables': (get_cohorted_commentables(course_key)),
'sort_preference': user.default_sort_key,
'category_map': course_settings["category_map"],
'course_settings': _attr_safe_json(course_settings)
......@@ -393,7 +396,12 @@ def single_thread(request, course_key, 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')
......
......@@ -127,6 +127,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
......@@ -191,7 +219,7 @@ class UserTagsService(object):
)
class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract-method
class LmsModuleSystem(LmsHandlerUrls, LmsCourse, LmsUser, ModuleSystem): # pylint: disable=abstract-method
"""
ModuleSystem specialized to the LMS
"""
......
......@@ -43,6 +43,11 @@ from xmodule.modulestore.modulestore_settings import update_module_store_setting
from xmodule.mixin import LicenseMixin
from lms.djangoapps.lms_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"
......@@ -1246,8 +1251,8 @@ dashboard_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/dashboard/**/*.js'))
)
dashboard_search_js = ['js/search/dashboard/main.js']
discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js'))
rwd_header_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/utils/rwd_header.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'))
......@@ -1389,6 +1394,10 @@ PIPELINE_CSS = {
],
'output_filename': 'css/lms-main-rtl.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',
......@@ -1839,6 +1848,7 @@ INSTALLED_APPS = (
'django_comment_common',
'discussion_api',
'notes',
'discussion_app',
'edxnotes',
......
......@@ -209,3 +209,40 @@
text-decoration: underline !important;
}
}
%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;
}
// 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, .discussion-course {
body.discussion {
// 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);
.course-tabs .right {
@include float(right);
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 {
@include blue-button;
......@@ -110,7 +132,7 @@ body.discussion {
}
.wmd-panel {
min-width: 500px;
min-width: 425px;
width: 100%;
}
......@@ -225,14 +247,28 @@ body.discussion {
}
}
.container .discussion-body {
@include clearfix();
.container .discussion-body, .discussion-module, .discussion-course {
@include clearfix;
display: block;
border: none;
background: transparent;
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;
......
......@@ -44,8 +44,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,
......
......@@ -14,12 +14,10 @@ from django.core.urlresolvers import reverse
<%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>
......@@ -47,3 +45,5 @@ from django.core.urlresolvers import reverse
<%include file="_underscore_templates.html" />
<%include file="_thread_list_template.html" />
<%include file="/discussion/_discussion_course.html" />
......@@ -11,12 +11,10 @@ from django.template.defaultfilters import escapejs
<%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>
......@@ -28,7 +26,7 @@ from django.template.defaultfilters import escapejs
<nav aria-label="${_('User Profile')}">
<article class="sidebar-module discussion-sidebar">
<%include file="_user_profile.html" />
<%include file="/discussion/_user_profile.html" />
</article>
</nav>
......@@ -38,4 +36,4 @@ from django.template.defaultfilters import escapejs
</div>
</section>
<%include file="_underscore_templates.html" />
<%include file="/discussion/_underscore_templates.html" />
......@@ -63,6 +63,10 @@ from branding import api as branding_api
<%static:css group='style-vendor'/>
<%static:css group='style-main'/>
<%static:css group='style-app'/>
<%static:css group='style-app-extend1'/>
<%static:css group='style-app-extend2'/>
<%static:css group='style-discussion-app'/>
% if disable_courseware_js:
<%static:js group='base_vendor'/>
......@@ -157,6 +161,17 @@ from branding import api as branding_api
% endif
% if not disable_window_wrap:
<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}" />
<div class="content-wrapper" id="content">
${self.body()}
<%block name="bodyextra"/>
</div>
% endif
......@@ -167,8 +182,6 @@ from branding import api as branding_api
<%block name="js_extra"/>
<script type="text/javascript" src="${static.url('js/vendor/noreferrer.js')}" charset="utf-8"></script>
</body>
</html>
<%def name="login_query()">${
u"?next={0}".format(urlquote_plus(login_redirect_url)) if login_redirect_url else ""
......@@ -203,3 +216,5 @@ from branding import api as branding_api
}());
</script>
% endif
</body>
</html>
\ No newline at end of file
......@@ -14,6 +14,7 @@
{% compressed_css 'style-main' %}
{% compressed_css 'style-course-vendor' %}
{% compressed_css 'style-course' %}
{% compressed_css 'style-discussion-app' %}
{% block main_vendor_js %}
{% compressed_js 'main_vendor' %}
......@@ -52,6 +53,7 @@
{% compressed_js 'application' %}
{% compressed_js 'module-js' %}
{% include "mathjax_include.html" %}
{% render_block "js" %}
</body>
......
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