Commit 787dac90 by Brian Jacobel

use DiscussionThreadListView for user profile discussion

parent 187783be
...@@ -618,9 +618,9 @@ class DiscussionUserProfilePage(CoursePage): ...@@ -618,9 +618,9 @@ class DiscussionUserProfilePage(CoursePage):
return ( return (
self.q(css='.discussion-user-threads[data-course-id="{}"]'.format(self.course_id)).present self.q(css='.discussion-user-threads[data-course-id="{}"]'.format(self.course_id)).present
and and
self.q(css='.user-profile .learner-profile-link').present self.q(css='.user-name').present
and and
self.q(css='.user-profile .learner-profile-link').text[0] == self.username self.q(css='.user-name').text[0] == self.username
) )
@wait_for_js @wait_for_js
...@@ -628,85 +628,16 @@ class DiscussionUserProfilePage(CoursePage): ...@@ -628,85 +628,16 @@ class DiscussionUserProfilePage(CoursePage):
return self.browser.execute_script("return $('html, body').offset().top") == 0 return self.browser.execute_script("return $('html, body').offset().top") == 0
def get_shown_thread_ids(self): def get_shown_thread_ids(self):
elems = self.q(css="article.discussion-thread") elems = self.q(css="li.forum-nav-thread")
return [elem.get_attribute("id")[7:] for elem in elems] return [elem.get_attribute("data-id") for elem in elems]
def get_current_page(self):
def check_func():
try:
current_page = int(self.q(css="nav.discussion-paginator li.current-page").text[0])
except:
return False, None
return True, current_page
return Promise(
check_func, 'discussion-paginator current page has text', timeout=5,
).fulfill()
def _check_pager(self, text, page_number=None):
"""
returns True if 'text' matches the text in any of the pagination elements. If
page_number is provided, only return True if the element points to that result
page.
"""
elems = self.q(css=self.PAGING_SELECTOR).filter(lambda elem: elem.text == text)
if page_number:
elems = elems.filter(lambda elem: int(elem.get_attribute('data-page-number')) == page_number)
return elems.present
def get_clickable_pages(self):
return sorted([
int(elem.get_attribute('data-page-number'))
for elem in self.q(css=self.PAGING_SELECTOR)
if str(elem.text).isdigit()
])
def is_prev_button_shown(self, page_number=None):
return self._check_pager(self.TEXT_PREV, page_number)
def is_next_button_shown(self, page_number=None):
return self._check_pager(self.TEXT_NEXT, page_number)
def _click_pager_with_text(self, text, page_number):
"""
click the first pagination element with whose text is `text` and ensure
the resulting page number matches `page_number`.
"""
targets = [elem for elem in self.q(css=self.PAGING_SELECTOR) if elem.text == text]
targets[0].click()
EmptyPromise(
lambda: self.get_current_page() == page_number,
"navigated to desired page"
).fulfill()
def click_prev_page(self):
self._click_pager_with_text(self.TEXT_PREV, self.get_current_page() - 1)
EmptyPromise(
self.is_window_on_top,
"Window is on top"
).fulfill()
def click_next_page(self):
self._click_pager_with_text(self.TEXT_NEXT, self.get_current_page() + 1)
EmptyPromise(
self.is_window_on_top,
"Window is on top"
).fulfill()
def click_on_page(self, page_number):
self._click_pager_with_text(unicode(page_number), page_number)
EmptyPromise(
self.is_window_on_top,
"Window is on top"
).fulfill()
def click_on_sidebar_username(self): def click_on_sidebar_username(self):
self.wait_for_page() self.wait_for_page()
self.q(css='.learner-profile-link').first.click() self.q(css='.user-name').first.click()
def get_user_roles(self): def get_user_roles(self):
"""Get user roles""" """Get user roles"""
return self.q(css='.sidebar-user-roles').text[0] return self.q(css='.user-roles').text[0]
class DiscussionTabHomePage(CoursePage, DiscussionPageMixin): class DiscussionTabHomePage(CoursePage, DiscussionPageMixin):
......
...@@ -1192,102 +1192,25 @@ class DiscussionUserProfileTest(UniqueCourseTest): ...@@ -1192,102 +1192,25 @@ class DiscussionUserProfileTest(UniqueCourseTest):
roles_str = ','.join(roles) roles_str = ','.join(roles)
return AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str, **user_info).visit().get_user_id() return AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str, **user_info).visit().get_user_id()
def check_pages(self, num_threads):
# set up the stub server to return the desired amount of thread results
threads = [Thread(id=uuid4().hex) for _ in range(num_threads)]
UserProfileViewFixture(threads).push()
# navigate to default view (page 1)
page = DiscussionUserProfilePage(
self.browser,
self.course_id,
self.profiled_user_id,
self.PROFILED_USERNAME
)
page.visit()
current_page = 1
total_pages = max(num_threads - 1, 1) / self.PAGE_SIZE + 1
all_pages = range(1, total_pages + 1)
return page
def _check_page():
# ensure the page being displayed as "current" is the expected one
self.assertEqual(page.get_current_page(), current_page)
# ensure the expected threads are being shown in the right order
threads_expected = threads[(current_page - 1) * self.PAGE_SIZE:current_page * self.PAGE_SIZE]
self.assertEqual(page.get_shown_thread_ids(), [t["id"] for t in threads_expected])
# ensure the clickable page numbers are the expected ones
self.assertEqual(page.get_clickable_pages(), [
p for p in all_pages
if p != current_page
and p - 2 <= current_page <= p + 2
or (current_page > 2 and p == 1)
or (current_page < total_pages and p == total_pages)
])
# ensure the previous button is shown, but only if it should be.
# when it is shown, make sure it works.
if current_page > 1:
self.assertTrue(page.is_prev_button_shown(current_page - 1))
page.click_prev_page()
self.assertEqual(page.get_current_page(), current_page - 1)
page.click_next_page()
self.assertEqual(page.get_current_page(), current_page)
else:
self.assertFalse(page.is_prev_button_shown())
# ensure the next button is shown, but only if it should be.
if current_page < total_pages:
self.assertTrue(page.is_next_button_shown(current_page + 1))
else:
self.assertFalse(page.is_next_button_shown())
# click all the way up through each page
for __ in range(current_page, total_pages):
_check_page()
if current_page < total_pages:
page.click_on_page(current_page + 1)
current_page += 1
# click all the way back down
for __ in range(current_page, 0, -1):
_check_page()
if current_page > 1:
page.click_on_page(current_page - 1)
current_page -= 1
def test_0_threads(self):
self.check_pages(0)
def test_1_thread(self):
self.check_pages(1)
def test_20_threads(self):
self.check_pages(20)
def test_21_threads(self):
self.check_pages(21)
def test_151_threads(self):
self.check_pages(151)
def test_pagination_window_reposition(self):
page = self.check_pages(50)
page.click_next_page()
page.wait_for_ajax()
self.assertTrue(page.is_window_on_top())
def test_redirects_to_learner_profile(self): def test_redirects_to_learner_profile(self):
""" """
Scenario: Verify that learner-profile link is present on forum discussions page and we can navigate to it. Scenario: Verify that learner-profile link is present on forum discussions page and we can navigate to it.
Given that I am on discussion forum user's profile page. Given that I am on discussion forum user's profile page.
And I can see a username on left sidebar And I can see a username on the page
When I click on my username. When I click on my username.
Then I will be navigated to Learner Profile page. Then I will be navigated to Learner Profile page.
And I can my username on Learner Profile page And I can my username on Learner Profile page
""" """
learner_profile_page = LearnerProfilePage(self.browser, self.PROFILED_USERNAME) learner_profile_page = LearnerProfilePage(self.browser, self.PROFILED_USERNAME)
page = self.check_pages(1) page = DiscussionUserProfilePage(
self.browser,
self.course_id,
self.profiled_user_id,
self.PROFILED_USERNAME
)
page.visit()
page.click_on_sidebar_username() page.click_on_sidebar_username()
learner_profile_page.wait_for_page() learner_profile_page.wait_for_page()
...@@ -1305,7 +1228,13 @@ class DiscussionUserProfileTest(UniqueCourseTest): ...@@ -1305,7 +1228,13 @@ class DiscussionUserProfileTest(UniqueCourseTest):
) )
# Visit the page and verify the roles are listed correctly. # Visit the page and verify the roles are listed correctly.
page = self.check_pages(1) page = DiscussionUserProfilePage(
self.browser,
self.course_id,
self.profiled_user_id,
self.PROFILED_USERNAME
)
page.visit()
student_roles = page.get_user_roles() student_roles = page.get_user_roles()
self.assertEqual(student_roles, ', '.join(expected_student_roles)) self.assertEqual(student_roles, ', '.join(expected_student_roles))
...@@ -1325,7 +1254,13 @@ class DiscussionUserProfileTest(UniqueCourseTest): ...@@ -1325,7 +1254,13 @@ class DiscussionUserProfileTest(UniqueCourseTest):
# Visit the user profile in course discussion page of Course-B. Make sure the # Visit the user profile in course discussion page of Course-B. Make sure the
# roles are listed correctly. # roles are listed correctly.
page = self.check_pages(1) page = DiscussionUserProfilePage(
self.browser,
self.course_id,
self.profiled_user_id,
self.PROFILED_USERNAME
)
page.visit()
self.assertEqual(page.get_user_roles(), u'Student') self.assertEqual(page.get_user_roles(), u'Student')
......
...@@ -4,17 +4,28 @@ ...@@ -4,17 +4,28 @@
define( define(
[ [
'jquery', 'jquery',
'backbone',
'common/js/discussion/content',
'common/js/discussion/discussion',
'common/js/discussion/utils', 'common/js/discussion/utils',
'common/js/discussion/models/discussion_user', 'common/js/discussion/models/discussion_user',
'common/js/discussion/models/discussion_course_settings',
'discussion/js/views/discussion_user_profile_view' 'discussion/js/views/discussion_user_profile_view'
], ],
function($, DiscussionUtil, DiscussionUser, DiscussionUserProfileView) { function($, Backbone, Content, Discussion, DiscussionUtil, DiscussionUser, DiscussionCourseSettings,
DiscussionUserProfileView) {
return function(options) { return function(options) {
var $element = options.$el, var threads = options.threads,
threads = options.threads, contentInfo = options.contentInfo,
userInfo = options.userInfo, userInfo = options.userInfo,
user = new DiscussionUser(userInfo),
page = options.page, page = options.page,
numPages = options.numPages; numPages = options.numPages,
sortPreference = options.sortPreference,
discussionUserProfileView,
discussion,
courseSettings;
// Roles are not included in user profile page, but they are not used for anything // Roles are not included in user profile page, but they are not used for anything
DiscussionUtil.loadRoles({ DiscussionUtil.loadRoles({
Moderator: [], Moderator: [],
...@@ -22,16 +33,25 @@ ...@@ -22,16 +33,25 @@
'Community TA': [] 'Community TA': []
}); });
// TODO: remove global variable usage DiscussionUtil.loadRoles(options.roles);
window.$$course_id = options.courseId; window.$$course_id = options.courseId;
window.user = new DiscussionUser(userInfo); window.courseName = options.course_name;
DiscussionUtil.setUser(user);
window.user = user;
Content.loadContentInfos(contentInfo);
// Create a discussion model
discussion = new Discussion(threads, {pages: numPages, sort: sortPreference});
courseSettings = new DiscussionCourseSettings(options.courseSettings);
new DiscussionUserProfileView({ // eslint-disable-line no-new discussionUserProfileView = new DiscussionUserProfileView({
el: $element, el: $('.discussion-user-threads'),
collection: threads, discussion: discussion,
page: page, page: page,
numPages: numPages numPages: numPages,
courseSettings: courseSettings
}); });
discussionUserProfileView.render();
}; };
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
/* globals Discussion */
define( define(
[ [
'underscore', 'underscore',
...@@ -15,10 +17,12 @@ define( ...@@ -15,10 +17,12 @@ define(
DiscussionProfilePageFactory(_.extend( DiscussionProfilePageFactory(_.extend(
{ {
courseId: testCourseId, courseId: testCourseId,
$el: $('.discussion-user-threads'),
user_info: DiscussionSpecHelper.getTestUserInfo(),
roles: DiscussionSpecHelper.getTestRoleInfo(), roles: DiscussionSpecHelper.getTestRoleInfo(),
sort_preference: null, courseSettings: DiscussionSpecHelper.createTestCourseSettings().attributes,
el: $('.discussion-user-threads'),
discussion: new Discussion(),
userInfo: DiscussionSpecHelper.getTestUserInfo(),
sortPreference: null,
threads: [], threads: [],
page: 1, page: 1,
numPages: 5 numPages: 5
...@@ -34,7 +38,7 @@ define( ...@@ -34,7 +38,7 @@ define(
it('can render itself', function() { it('can render itself', function() {
initializeDiscussionProfilePageFactory(); initializeDiscussionProfilePageFactory();
expect($('.discussion-user-threads').text()).toContain('Active Threads'); expect($('.discussion-user-threads').text()).toContain('Show');
}); });
}); });
} }
......
/* globals DiscussionThreadView */
(function(define) { (function(define) {
'use strict'; 'use strict';
...@@ -13,77 +14,66 @@ ...@@ -13,77 +14,66 @@
'common/js/discussion/utils', 'common/js/discussion/utils',
'common/js/discussion/views/discussion_thread_profile_view', 'common/js/discussion/views/discussion_thread_profile_view',
'text!discussion/templates/user-profile.underscore', 'text!discussion/templates/user-profile.underscore',
'text!common/templates/discussion/pagination.underscore' 'common/js/discussion/views/discussion_thread_list_view'
], ],
function(_, $, Backbone, gettext, URI, HtmlUtils, ViewUtils, Discussion, DiscussionUtil, function(_, $, Backbone, gettext, URI, HtmlUtils, ViewUtils, Discussion, DiscussionUtil,
DiscussionThreadProfileView, userProfileTemplate, paginationTemplate) { DiscussionThreadProfileView, userProfileTemplate, DiscussionThreadListView) {
var DiscussionUserProfileView = Backbone.View.extend({ var DiscussionUserProfileView = Backbone.View.extend({
events: { events: {
'click .discussion-paginator a': 'changePage' 'click .all-posts-btn': 'navigateToAllThreads'
}, },
initialize: function(options) { initialize: function(options) {
Backbone.View.prototype.initialize.call(this); this.courseSettings = options.courseSettings;
this.page = options.page; this.discussion = options.discussion;
this.numPages = options.numPages; this.mode = 'all';
this.discussion = new Discussion(); this.listenTo(this.model, 'change', this.render);
this.discussion.on('reset', _.bind(this.render, this));
this.discussion.reset(this.collection, {silent: false});
}, },
render: function() { render: function() {
var self = this, HtmlUtils.setHtml(this.$el,
baseUri = URI(window.location).removeSearch('page'), HtmlUtils.template(userProfileTemplate)({})
pageUrlFunc,
paginationParams;
HtmlUtils.setHtml(this.$el, HtmlUtils.template(userProfileTemplate)({
threads: self.discussion.models
}));
this.discussion.map(function(thread) {
var view = new DiscussionThreadProfileView({
el: self.$('article#thread_' + thread.id),
model: thread
});
view.render();
return view;
});
pageUrlFunc = function(page) {
return baseUri.clone().addSearch('page', page).toString();
};
paginationParams = DiscussionUtil.getPaginationParams(this.page, this.numPages, pageUrlFunc);
HtmlUtils.setHtml(
this.$el.find('.discussion-pagination'),
HtmlUtils.template(paginationTemplate)(paginationParams)
); );
this.discussionThreadListView = new DiscussionThreadListView({
collection: this.discussion,
el: this.$('.inline-threads'),
courseSettings: this.courseSettings
}).render();
this.discussionThreadListView.on('thread:selected', _.bind(this.navigateToThread, this));
return this; return this;
}, },
changePage: function(event) { navigateToThread: function(threadId) {
var self = this, var thread = this.discussion.get(threadId);
url; this.threadView = new DiscussionThreadView({
event.preventDefault(); el: this.$('.forum-content'),
url = $(event.target).attr('href'); model: thread,
DiscussionUtil.safeAjax({ mode: 'tab',
$elem: this.$el, course_settings: this.courseSettings
$loading: $(event.target),
takeFocus: true,
url: url,
type: 'GET',
dataType: 'json',
success: function(response) {
self.page = response.page;
self.numPages = response.num_pages;
self.discussion.reset(response.discussion_data, {silent: false});
history.pushState({}, '', url);
ViewUtils.setScrollTop(0);
},
error: function() {
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble loading the page you requested. Please try again.')
);
}
}); });
this.threadView.render();
this.listenTo(this.threadView.showView, 'thread:_delete', this.navigateToAllThreads);
this.discussionThreadListView.$el.addClass('is-hidden');
this.$('.inline-thread').removeClass('is-hidden');
},
navigateToAllThreads: function() {
// Hide the inline thread section
this.$('.inline-thread').addClass('is-hidden');
// Delete the thread view
this.threadView.$el.empty().off();
this.threadView.stopListening();
this.threadView = null;
// Show the thread list view
this.discussionThreadListView.$el.removeClass('is-hidden');
// Set focus to thread list item that was saved as active
this.discussionThreadListView.$('.is-active').focus();
} }
}); });
......
<h2><%- gettext("Active Threads") %></h2> <div class="inline-threads"></div>
<section class="discussion"> <div class="inline-thread is-hidden">
<% _.each(threads, function(thread) { %> <div class="forum-nav-bar">
<article class="discussion-thread" id="thread_<%- thread.id %>"/> <button class="btn-link all-posts-btn">
<% }); %> <span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
</section> <span><%- gettext("All Posts") %></span>
<section class="discussion-pagination"/> </button>
</div>
<div class="forum-content">
</div>
</div>
...@@ -5,74 +5,90 @@ ...@@ -5,74 +5,90 @@
<%page expression_filter="h"/> <%page expression_filter="h"/>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%def name="online_help_token()"><% return "discussions" %></%def>
<%! <%!
from django.utils.translation import ugettext as _ import json
from django.utils.translation import ugettext as _, ungettext
from django.template.defaultfilters import escapejs from django.template.defaultfilters import escapejs
from openedx.core.djangolib.js_utils import ( from django.core.urlresolvers import reverse
dump_js_escaped_json, js_escaped_string
) from django_comment_client.permissions import has_permission
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
%> %>
<%block name="bodyclass">discussion-user-profile</%block> <%block name="bodyclass">discussion discussion-user-profile</%block>
<%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}</%block> <%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}</%block>
<%block name="headextra"> <%block name="headextra">
<%static:css group='style-inline-discussion'/>
<%include file="_js_head_dependencies.html" /> <%include file="_js_head_dependencies.html" />
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<%include file="_js_body_dependencies.html" /> <%include file="_js_body_dependencies.html" />
<%static:require_module module_name="discussion/js/discussion_profile_page_factory" class_name="DiscussionProfilePageFactory"> <%static:require_module module_name="discussion/js/discussion_profile_page_factory" class_name="DiscussionProfilePageFactory">
<%
profile_page_context = { profile_page_context = {
'courseId': unicode(course.id), 'courseSettings': ${course_settings | n, dump_js_escaped_json},
'courseName': course.display_name_with_default, 'courseId': '${unicode(course.id) | n, js_escaped_string}',
'userInfo': user_info, 'courseName': '${course.display_name_with_default | n, js_escaped_string}',
'threads': threads, 'contentInfo': ${annotated_content_info | n, dump_js_escaped_json},
'page': page, 'userInfo': ${user_info | n, dump_js_escaped_json},
'numPages': num_pages, 'roles': ${roles | n, dump_js_escaped_json},
'threads': ${threads | n, dump_js_escaped_json},
'page': ${page | n, dump_js_escaped_json},
'sortPreference': '${sort_preference | n, js_escaped_string}',
'numPages': ${num_pages | n, dump_js_escaped_json}
} }
%>
DiscussionProfilePageFactory(_.extend( DiscussionProfilePageFactory(_.extend(
{ {
$el: $('.discussion-user-threads') el: $('.discussion-user-profile-board')
}, },
${profile_page_context | n, dump_js_escaped_json} profile_page_context
)); ));
</%static:require_module> </%static:require_module>
</%block> </%block>
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" /> <%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
<section class="container"> <%block name="content">
<section class="discussion inline-discussion discussion-user-profile-board container">
<header class="page-header"> <header class="page-header">
<div class="page-header-main"> <div class="page-header-main">
<div class="sr-is-focusable" tabindex="-1"></div> <div class="sr-is-focusable" tabindex="-1"></div>
<h2 class="hd hd-2 page-title">${_("Discussion")}</h2> <div>
<%def name="span(num)"><span class="discussion-count">${num}</span></%def>
<span class="user-name"><a href="${learner_profile_page_url}">${django_user.username}</a></span>
<span class="discussion-profile-info">
<span class="user-roles">${", ".join(_(role_name) for role_name in django_user_roles)}</span>
</span>
<div class="discussion-profile-count">
<span class="discussion-profile-info threads-count">${ungettext('%s discussion started', '%s discussions started', profiled_user['threads_count']) % span(profiled_user['threads_count'])}</span>
<span class="discussion-profile-info comments-count">${ungettext('%s comment', '%s comments', profiled_user['comments_count']) % span(profiled_user['comments_count'])}</span>
</div>
</div>
</div> </div>
</header> </header>
<div class="page-content"> <div class="page-content">
<div class="layout layout-1t2t"> <main id="main" aria-label="Content" tabindex="-1" class="discussion-column">
<aside class="forum-nav layout-col layout-col-a" role="complementary" aria-label="${_("Discussion thread list")}"> <div class="course-content discussion-module discussion-user-threads"
<nav class="user-profile" aria-label="${_('User Profile')}"> data-course-id="${course.id}"
data-course-name="${course.display_name_with_default}"
<article class="sidebar-module discussion-sidebar"> data-threads="${threads}"
<%include file="_user_profile.html" /> data-user-info="${user_info}"
</article> data-page="${page}"
data-num-pages="${num_pages}"
</nav> data-user-create-comment="${json.dumps(can_create_comment)}"
</aside> data-user-create-subcomment="${json.dumps(can_create_subcomment)}"
data-read-only="false"
<main id="main" aria-label="Content" tabindex="-1" class="discussion-column layout-col layout-col-b"> data-sort-preference="${sort_preference}"
<div class="course-content discussion-user-threads" data-course-id="${course.id}" data-flag-moderator="${json.dumps(flag_moderator)}"
data-course-name="${course.display_name_with_default}" data-user-cohort-id="${user_cohort}">
data-threads="${threads}" data-user-info="${user_info}" </div>
data-page="${page}" data-num-pages="${num_pages}"> </main>
</div>
</main>
</div>
</div> </div>
</section> </section>
</%block>
<%include file="_underscore_templates.html" /> <%include file="_underscore_templates.html" />
<%include file="_thread_list_template.html" />
...@@ -1155,8 +1155,8 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase) ...@@ -1155,8 +1155,8 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
html = response.content html = response.content
self.assertRegexpMatches(html, r'data-page="1"') self.assertRegexpMatches(html, r'data-page="1"')
self.assertRegexpMatches(html, r'data-num-pages="1"') self.assertRegexpMatches(html, r'data-num-pages="1"')
self.assertRegexpMatches(html, r'<span>1</span> discussion started') self.assertRegexpMatches(html, r'<span class="discussion-count">1</span> discussion started')
self.assertRegexpMatches(html, r'<span>2</span> comments') self.assertRegexpMatches(html, r'<span class="discussion-count">2</span> comments')
self.assertRegexpMatches(html, r'&#39;id&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_ID)) self.assertRegexpMatches(html, r'&#39;id&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_ID))
self.assertRegexpMatches(html, r'&#39;title&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT)) self.assertRegexpMatches(html, r'&#39;title&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT))
self.assertRegexpMatches(html, r'&#39;body&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT)) self.assertRegexpMatches(html, r'&#39;body&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT))
...@@ -1181,15 +1181,9 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase) ...@@ -1181,15 +1181,9 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
def test_html(self, mock_request): def test_html(self, mock_request):
self.check_html(mock_request) self.check_html(mock_request)
def test_html_p2(self, mock_request):
self.check_html(mock_request, page="2")
def test_ajax(self, mock_request): def test_ajax(self, mock_request):
self.check_ajax(mock_request) self.check_ajax(mock_request)
def test_ajax_p2(self, mock_request):
self.check_ajax(mock_request, page="2")
def test_404_non_enrolled_user(self, __): def test_404_non_enrolled_user(self, __):
""" """
Test that when student try to visit un-enrolled students' discussion profile, Test that when student try to visit un-enrolled students' discussion profile,
......
...@@ -408,8 +408,10 @@ def user_profile(request, course_key, user_id): ...@@ -408,8 +408,10 @@ def user_profile(request, course_key, user_id):
nr_transaction = newrelic.agent.current_transaction() nr_transaction = newrelic.agent.current_transaction()
#TODO: Allow sorting? user = cc.User.from_django_user(request.user)
user_info = user.to_dict()
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
course_settings = make_course_settings(course, request.user)
try: try:
# If user is not enrolled in the course, do not proceed. # If user is not enrolled in the course, do not proceed.
...@@ -454,6 +456,9 @@ def user_profile(request, course_key, user_id): ...@@ -454,6 +456,9 @@ def user_profile(request, course_key, user_id):
course_id=course.id course_id=course.id
).order_by("name").values_list("name", flat=True).distinct() ).order_by("name").values_list("name", flat=True).distinct()
with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
user_cohort_id = get_cohort_id(request.user, course_key)
context = { context = {
'course': course, 'course': course,
'user': request.user, 'user': request.user,
...@@ -462,9 +467,20 @@ def user_profile(request, course_key, user_id): ...@@ -462,9 +467,20 @@ def user_profile(request, course_key, user_id):
'profiled_user': profiled_user.to_dict(), 'profiled_user': profiled_user.to_dict(),
'threads': threads, 'threads': threads,
'user_info': user_info, 'user_info': user_info,
'roles': utils.get_role_ids(course_key),
'can_create_comment': has_permission(request.user, "create_comment", course.id),
'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id),
'can_create_thread': has_permission(request.user, "create_thread", course.id),
'flag_moderator': bool(
has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course)
),
'user_cohort': user_cohort_id,
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
'page': query_params['page'], 'page': query_params['page'],
'num_pages': query_params['num_pages'], 'num_pages': query_params['num_pages'],
'sort_preference': user.default_sort_key,
'course_settings': course_settings,
'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}), 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}),
'disable_courseware_js': True, 'disable_courseware_js': True,
'uses_pattern_library': True, 'uses_pattern_library': True,
......
// Layouts for discussion pages // Layouts for discussion pages
@import '../course/base/extends';
.user-profile { .discussion-user-profile-board {
background-color: $sidebar-color;
.user-profile { .discussion-profile-count {
padding: $baseline; margin-top: $baseline / 4;
min-height: 500px;
} }
.sidebar-username { .discussion-profile-info {
font-weight: 700; @include margin-right($baseline);
font-size: $forum-large-font-size;
} }
.sidebar-user-roles { .user-name {
margin-top: $baseline/2; @include margin-right($baseline);
font-size: $forum-x-large-font-size;
}
.user-roles {
font-size: $forum-small-font-size;
font-style: italic; font-style: italic;
font-size: $forum-base-font-size;
} }
.sidebar-threads-count { .discussion-post .post-body {
margin-top: $baseline/2; width: 90%; // this page is full screen
} }
.sidebar-threads-count span, .all-posts-btn {
.sidebar-comments-count span { padding: 0;
font-weight: 700; font-size: $forum-base-font-size;
} }
} }
......
...@@ -221,9 +221,6 @@ ...@@ -221,9 +221,6 @@
.forum-nav-thread-link { .forum-nav-thread-link {
@include border-left(3px solid transparent); @include border-left(3px solid transparent);
@include rtl {
flex-direction: row-reverse;
}
display: flex; display: flex;
padding: $baseline / 2; padding: $baseline / 2;
transition: none; transition: none;
...@@ -258,34 +255,36 @@ ...@@ -258,34 +255,36 @@
} }
} }
.discussion:not(.inline-discussion) .forum-nav-thread { .discussion.discussion-board {
.forum-nav-thread-link.is-active { .forum-nav-thread {
color: $forum-color-background; .forum-nav-thread-link.is-active {
background-color: $forum-color-reading-thread;
.forum-nav-thread-labels > li {
border-color: $forum-color-background;
color: $forum-color-background; color: $forum-color-background;
} background-color: $forum-color-reading-thread;
.forum-nav-thread-votes-count { .forum-nav-thread-labels > li {
color: $forum-color-background; border-color: $forum-color-background;
} color: $forum-color-background;
}
.forum-nav-thread-votes-count {
color: $forum-color-background;
}
.forum-nav-thread-comments-count { .forum-nav-thread-comments-count {
color: $base-font-color; color: $base-font-color;
&:after { &:after {
@include border-right-color($forum-color-border); @include border-right-color($forum-color-border);
}
} }
}
span.icon { span.icon {
color: $forum-color-background; color: $forum-color-background;
} }
.thread-preview-body { .thread-preview-body {
color: $forum-color-background; color: $forum-color-background;
}
} }
} }
} }
......
...@@ -25,6 +25,8 @@ $forum-color-never-read-post: $gray-d3 !default; ...@@ -25,6 +25,8 @@ $forum-color-never-read-post: $gray-d3 !default;
$forum-color-editor-preview-label: $gray-d2 !default; $forum-color-editor-preview-label: $gray-d2 !default;
$forum-color-response-count: $gray-d2 !default; $forum-color-response-count: $gray-d2 !default;
$forum-color-navigation-bar: #f6f6f6 !default; $forum-color-navigation-bar: #f6f6f6 !default;
$forum-color-count: $gray-d3 !default;
$forum-color-background-label: $gray-d2 !default;
// post images // post images
$post-image-dimension: ($baseline*3) !default; // image size + margin $post-image-dimension: ($baseline*3) !default; // image size + margin
......
...@@ -25,6 +25,8 @@ $forum-color-never-read-post: $forum-color-primary !default; ...@@ -25,6 +25,8 @@ $forum-color-never-read-post: $forum-color-primary !default;
$forum-color-editor-preview-label: palette(grayscale, base) !default; $forum-color-editor-preview-label: palette(grayscale, base) !default;
$forum-color-response-count: palette(grayscale, base) !default; $forum-color-response-count: palette(grayscale, base) !default;
$forum-color-navigation-bar: palette(grayscale, x-back) !default; $forum-color-navigation-bar: palette(grayscale, x-back) !default;
$forum-color-count: palette(grayscale, base) !default;
$forum-color-background-label: palette(grayscale, base) !default;
// post images // post images
$post-image-dimension: ($baseline*3) !default; // image size + margin $post-image-dimension: ($baseline*3) !default; // image size + margin
......
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