Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
787dac90
Commit
787dac90
authored
Dec 02, 2016
by
Brian Jacobel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
use DiscussionThreadListView for user profile discussion
parent
187783be
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
240 additions
and
325 deletions
+240
-325
common/test/acceptance/pages/lms/discussion.py
+6
-75
common/test/acceptance/tests/discussion/test_discussion.py
+22
-87
lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js
+30
-10
lms/djangoapps/discussion/static/discussion/js/spec/discussion_profile_page_factory_spec.js
+8
-4
lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js
+0
-0
lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js
+46
-56
lms/djangoapps/discussion/static/discussion/templates/user-profile.underscore
+11
-7
lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html
+53
-37
lms/djangoapps/discussion/tests/test_views.py
+2
-8
lms/djangoapps/discussion/views.py
+17
-1
lms/static/sass/discussion/_layouts.scss
+18
-16
lms/static/sass/discussion/elements/_navigation.scss
+23
-24
lms/static/sass/discussion/utilities/_variables-v1.scss
+2
-0
lms/static/sass/discussion/utilities/_variables-v2.scss
+2
-0
No files found.
common/test/acceptance/pages/lms/discussion.py
View file @
787dac90
...
...
@@ -618,9 +618,9 @@ class DiscussionUserProfilePage(CoursePage):
return
(
self
.
q
(
css
=
'.discussion-user-threads[data-course-id="{}"]'
.
format
(
self
.
course_id
))
.
present
and
self
.
q
(
css
=
'.user-
profile .learner-profile-link
'
)
.
present
self
.
q
(
css
=
'.user-
name
'
)
.
present
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
...
...
@@ -628,85 +628,16 @@ class DiscussionUserProfilePage(CoursePage):
return
self
.
browser
.
execute_script
(
"return $('html, body').offset().top"
)
==
0
def
get_shown_thread_ids
(
self
):
elems
=
self
.
q
(
css
=
"article.discussion-thread"
)
return
[
elem
.
get_attribute
(
"id"
)[
7
:]
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
()
elems
=
self
.
q
(
css
=
"li.forum-nav-thread"
)
return
[
elem
.
get_attribute
(
"data-id"
)
for
elem
in
elems
]
def
click_on_sidebar_username
(
self
):
self
.
wait_for_page
()
self
.
q
(
css
=
'.
learner-profile-link
'
)
.
first
.
click
()
self
.
q
(
css
=
'.
user-name
'
)
.
first
.
click
()
def
get_user_roles
(
self
):
"""Get user roles"""
return
self
.
q
(
css
=
'.
sidebar-
user-roles'
)
.
text
[
0
]
return
self
.
q
(
css
=
'.user-roles'
)
.
text
[
0
]
class
DiscussionTabHomePage
(
CoursePage
,
DiscussionPageMixin
):
...
...
common/test/acceptance/tests/discussion/test_discussion.py
View file @
787dac90
...
...
@@ -1192,102 +1192,25 @@ class DiscussionUserProfileTest(UniqueCourseTest):
roles_str
=
','
.
join
(
roles
)
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
):
"""
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.
And I can see a username on
left sidebar
And I can see a username on
the page
When I click on my username.
Then I will be navigated to Learner Profile page.
And I can my username on Learner Profile page
"""
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
()
learner_profile_page
.
wait_for_page
()
...
...
@@ -1305,7 +1228,13 @@ class DiscussionUserProfileTest(UniqueCourseTest):
)
# 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
()
self
.
assertEqual
(
student_roles
,
', '
.
join
(
expected_student_roles
))
...
...
@@ -1325,7 +1254,13 @@ class DiscussionUserProfileTest(UniqueCourseTest):
# Visit the user profile in course discussion page of Course-B. Make sure 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
()
self
.
assertEqual
(
page
.
get_user_roles
(),
u'Student'
)
...
...
lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js
View file @
787dac90
...
...
@@ -4,17 +4,28 @@
define
(
[
'jquery'
,
'backbone'
,
'common/js/discussion/content'
,
'common/js/discussion/discussion'
,
'common/js/discussion/utils'
,
'common/js/discussion/models/discussion_user'
,
'common/js/discussion/models/discussion_course_settings'
,
'discussion/js/views/discussion_user_profile_view'
],
function
(
$
,
DiscussionUtil
,
DiscussionUser
,
DiscussionUserProfileView
)
{
function
(
$
,
Backbone
,
Content
,
Discussion
,
DiscussionUtil
,
DiscussionUser
,
DiscussionCourseSettings
,
DiscussionUserProfileView
)
{
return
function
(
options
)
{
var
$element
=
options
.
$el
,
threads
=
options
.
threads
,
var
threads
=
options
.
threads
,
contentInfo
=
options
.
contentInfo
,
userInfo
=
options
.
userInfo
,
user
=
new
DiscussionUser
(
userInfo
),
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
DiscussionUtil
.
loadRoles
({
Moderator
:
[],
...
...
@@ -22,16 +33,25 @@
'Community TA'
:
[]
});
// TODO: remove global variable usage
DiscussionUtil
.
loadRoles
(
options
.
roles
);
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
el
:
$
element
,
collection
:
threads
,
discussionUserProfileView
=
new
DiscussionUserProfileView
({
el
:
$
(
'.discussion-user-threads'
)
,
discussion
:
discussion
,
page
:
page
,
numPages
:
numPages
numPages
:
numPages
,
courseSettings
:
courseSettings
});
discussionUserProfileView
.
render
();
};
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/djangoapps/discussion/static/discussion/js/spec/discussion_profile_page_factory_spec.js
View file @
787dac90
/* globals Discussion */
define
(
[
'underscore'
,
...
...
@@ -15,10 +17,12 @@ define(
DiscussionProfilePageFactory
(
_
.
extend
(
{
courseId
:
testCourseId
,
$el
:
$
(
'.discussion-user-threads'
),
user_info
:
DiscussionSpecHelper
.
getTestUserInfo
(),
roles
:
DiscussionSpecHelper
.
getTestRoleInfo
(),
sort_preference
:
null
,
courseSettings
:
DiscussionSpecHelper
.
createTestCourseSettings
().
attributes
,
el
:
$
(
'.discussion-user-threads'
),
discussion
:
new
Discussion
(),
userInfo
:
DiscussionSpecHelper
.
getTestUserInfo
(),
sortPreference
:
null
,
threads
:
[],
page
:
1
,
numPages
:
5
...
...
@@ -34,7 +38,7 @@ define(
it
(
'can render itself'
,
function
()
{
initializeDiscussionProfilePageFactory
();
expect
(
$
(
'.discussion-user-threads'
).
text
()).
toContain
(
'
Active Threads
'
);
expect
(
$
(
'.discussion-user-threads'
).
text
()).
toContain
(
'
Show
'
);
});
});
}
...
...
lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js
View file @
787dac90
This diff is collapsed.
Click to expand it.
lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js
View file @
787dac90
/* globals DiscussionThreadView */
(
function
(
define
)
{
'use strict'
;
...
...
@@ -13,77 +14,66 @@
'common/js/discussion/utils'
,
'common/js/discussion/views/discussion_thread_profile_view'
,
'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
,
DiscussionThreadProfileView
,
userProfileTemplate
,
paginationTemplate
)
{
DiscussionThreadProfileView
,
userProfileTemplate
,
DiscussionThreadListView
)
{
var
DiscussionUserProfileView
=
Backbone
.
View
.
extend
({
events
:
{
'click .
discussion-paginator a'
:
'changePage
'
'click .
all-posts-btn'
:
'navigateToAllThreads
'
},
initialize
:
function
(
options
)
{
Backbone
.
View
.
prototype
.
initialize
.
call
(
this
);
this
.
page
=
options
.
page
;
this
.
numPages
=
options
.
numPages
;
this
.
discussion
=
new
Discussion
();
this
.
discussion
.
on
(
'reset'
,
_
.
bind
(
this
.
render
,
this
));
this
.
discussion
.
reset
(
this
.
collection
,
{
silent
:
false
});
this
.
courseSettings
=
options
.
courseSettings
;
this
.
discussion
=
options
.
discussion
;
this
.
mode
=
'all'
;
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
},
render
:
function
()
{
var
self
=
this
,
baseUri
=
URI
(
window
.
location
).
removeSearch
(
'page'
),
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
)
HtmlUtils
.
setHtml
(
this
.
$el
,
HtmlUtils
.
template
(
userProfileTemplate
)({})
);
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
;
},
changePage
:
function
(
event
)
{
var
self
=
this
,
url
;
event
.
preventDefault
();
url
=
$
(
event
.
target
).
attr
(
'href'
);
DiscussionUtil
.
safeAjax
({
$elem
:
this
.
$el
,
$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.'
)
);
}
navigateToThread
:
function
(
threadId
)
{
var
thread
=
this
.
discussion
.
get
(
threadId
);
this
.
threadView
=
new
DiscussionThreadView
({
el
:
this
.
$
(
'.forum-content'
),
model
:
thread
,
mode
:
'tab'
,
course_settings
:
this
.
courseSettings
});
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
();
}
});
...
...
lms/djangoapps/discussion/static/discussion/templates/user-profile.underscore
View file @
787dac90
<h2><%- gettext("Active Threads") %></h2>
<section class="discussion">
<% _.each(threads, function(thread) { %>
<article class="discussion-thread" id="thread_<%- thread.id %>"/>
<% }); %>
</section>
<section class="discussion-pagination"/>
<div class="inline-threads"></div>
<div class="inline-thread is-hidden">
<div class="forum-nav-bar">
<button class="btn-link all-posts-btn">
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
<span><%- gettext("All Posts") %></span>
</button>
</div>
<div class="forum-content">
</div>
</div>
lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html
View file @
787dac90
...
...
@@ -5,74 +5,90 @@
<
%
page
expression_filter=
"h"
/>
<
%
inherit
file=
"../main.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
openedx
.
core
.
djangolib
.
js_utils
import
(
dump_js_escaped_json
,
js_escaped_string
)
from
django
.
core
.
urlresolvers
import
reverse
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=
"headextra"
>
<
%
static:css
group=
'style-inline-discussion'
/>
<
%
include
file=
"_js_head_dependencies.html"
/>
</
%
block>
<
%
block
name=
"js_extra"
>
<
%
include
file=
"_js_body_dependencies.html"
/>
<
%
static:require_module
module_name=
"discussion/js/discussion_profile_page_factory"
class_name=
"DiscussionProfilePageFactory"
>
<
%
profile_page_context = {
'
courseId
'
:
unicode
(
course
.
id
),
'
courseName
'
:
course
.
display_name_with_default
,
'
userInfo
'
:
user_info
,
'
threads
'
:
threads
,
'
page
'
:
page
,
'
numPages
'
:
num_pages
,
'courseSettings': ${course_settings | n, dump_js_escaped_json},
'courseId': '${unicode(course.id) | n, js_escaped_string}',
'courseName': '${course.display_name_with_default | n, js_escaped_string}',
'contentInfo': ${annotated_content_info | n, dump_js_escaped_json},
'userInfo': ${user_info | n, dump_js_escaped_json},
'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(
{
$el: $('.discussion-user-threads
')
el: $('.discussion-user-profile-board
')
},
${profile_page_context | n, dump_js_escaped_json}
profile_page_context
));
</
%
static:require
_module
>
</
%
block>
<
%
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"
>
<div
class=
"page-header-main"
>
<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>
</header>
<div
class=
"page-content"
>
<div
class=
"layout layout-1t2t"
>
<aside
class=
"forum-nav layout-col layout-col-a"
role=
"complementary"
aria-label=
"${_("
Discussion
thread
list
")}"
>
<nav
class=
"user-profile"
aria-label=
"${_('User Profile')}"
>
<article
class=
"sidebar-module discussion-sidebar"
>
<
%
include
file=
"_user_profile.html"
/>
</article>
</nav>
</aside>
<main
id=
"main"
aria-label=
"Content"
tabindex=
"-1"
class=
"discussion-column layout-col layout-col-b"
>
<div
class=
"course-content discussion-user-threads"
data-course-id=
"${course.id}"
data-course-name=
"${course.display_name_with_default}"
data-threads=
"${threads}"
data-user-info=
"${user_info}"
data-page=
"${page}"
data-num-pages=
"${num_pages}"
>
</div>
</main>
</div>
<main
id=
"main"
aria-label=
"Content"
tabindex=
"-1"
class=
"discussion-column"
>
<div
class=
"course-content discussion-module discussion-user-threads"
data-course-id=
"${course.id}"
data-course-name=
"${course.display_name_with_default}"
data-threads=
"${threads}"
data-user-info=
"${user_info}"
data-page=
"${page}"
data-num-pages=
"${num_pages}"
data-user-create-comment=
"${json.dumps(can_create_comment)}"
data-user-create-subcomment=
"${json.dumps(can_create_subcomment)}"
data-read-only=
"false"
data-sort-preference=
"${sort_preference}"
data-flag-moderator=
"${json.dumps(flag_moderator)}"
data-user-cohort-id=
"${user_cohort}"
>
</div>
</main>
</div>
</section>
</
%
block>
<
%
include
file=
"_underscore_templates.html"
/>
<
%
include
file=
"_thread_list_template.html"
/>
lms/djangoapps/discussion/tests/test_views.py
View file @
787dac90
...
...
@@ -1155,8 +1155,8 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
html
=
response
.
content
self
.
assertRegexpMatches
(
html
,
r'data-page="1"'
)
self
.
assertRegexpMatches
(
html
,
r'data-num-pages="1"'
)
self
.
assertRegexpMatches
(
html
,
r'<span>1</span> discussion started'
)
self
.
assertRegexpMatches
(
html
,
r'<span>2</span> comments'
)
self
.
assertRegexpMatches
(
html
,
r'<span
class="discussion-count"
>1</span> discussion started'
)
self
.
assertRegexpMatches
(
html
,
r'<span
class="discussion-count"
>2</span> comments'
)
self
.
assertRegexpMatches
(
html
,
r''id': '{}''
.
format
(
self
.
TEST_THREAD_ID
))
self
.
assertRegexpMatches
(
html
,
r''title': '{}''
.
format
(
self
.
TEST_THREAD_TEXT
))
self
.
assertRegexpMatches
(
html
,
r''body': '{}''
.
format
(
self
.
TEST_THREAD_TEXT
))
...
...
@@ -1181,15 +1181,9 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
def
test_html
(
self
,
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
):
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
,
__
):
"""
Test that when student try to visit un-enrolled students' discussion profile,
...
...
lms/djangoapps/discussion/views.py
View file @
787dac90
...
...
@@ -408,8 +408,10 @@ def user_profile(request, course_key, user_id):
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_settings
=
make_course_settings
(
course
,
request
.
user
)
try
:
# If user is not enrolled in the course, do not proceed.
...
...
@@ -454,6 +456,9 @@ def user_profile(request, course_key, user_id):
course_id
=
course
.
id
)
.
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
=
{
'course'
:
course
,
'user'
:
request
.
user
,
...
...
@@ -462,9 +467,20 @@ def user_profile(request, course_key, user_id):
'profiled_user'
:
profiled_user
.
to_dict
(),
'threads'
:
threads
,
'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
,
'page'
:
query_params
[
'page'
],
'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
}),
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
...
...
lms/static/sass/discussion/_layouts.scss
View file @
787dac90
// Layouts for discussion pages
@import
'../course/base/extends'
;
.user-profile
{
background-color
:
$sidebar-color
;
.discussion-user-profile-board
{
.user-profile
{
padding
:
$baseline
;
min-height
:
500px
;
.discussion-profile-count
{
margin-top
:
$baseline
/
4
;
}
.sidebar-username
{
font-weight
:
700
;
font-size
:
$forum-large-font-size
;
.discussion-profile-info
{
@include
margin-right
(
$baseline
);
}
.sidebar-user-roles
{
margin-top
:
$baseline
/
2
;
.user-name
{
@include
margin-right
(
$baseline
);
font-size
:
$forum-x-large-font-size
;
}
.user-roles
{
font-size
:
$forum-small-font-size
;
font-style
:
italic
;
font-size
:
$forum-base-font-size
;
}
.
sidebar-threads-count
{
margin-top
:
$baseline
/
2
;
.
discussion-post
.post-body
{
width
:
90%
;
// this page is full screen
}
.
sidebar-threads-count
span
,
.sidebar-comments-count
span
{
font-
weight
:
700
;
.
all-posts-btn
{
padding
:
0
;
font-
size
:
$forum-base-font-size
;
}
}
...
...
lms/static/sass/discussion/elements/_navigation.scss
View file @
787dac90
...
...
@@ -221,9 +221,6 @@
.forum-nav-thread-link
{
@include
border-left
(
3px
solid
transparent
);
@include
rtl
{
flex-direction
:
row-reverse
;
}
display
:
flex
;
padding
:
$baseline
/
2
;
transition
:
none
;
...
...
@@ -258,34 +255,36 @@
}
}
.discussion
:not
(
.inline-discussion
)
.forum-nav-thread
{
.forum-nav-thread-link.is-active
{
color
:
$forum-color-background
;
background-color
:
$forum-color-reading-thread
;
.forum-nav-thread-labels
>
li
{
border-color
:
$forum-color-background
;
.discussion.discussion-board
{
.forum-nav-thread
{
.forum-nav-thread-link.is-active
{
color
:
$forum-color-background
;
}
background-color
:
$forum-color-reading-thread
;
.forum-nav-thread-votes-count
{
color
:
$forum-color-background
;
}
.forum-nav-thread-labels
>
li
{
border-color
:
$forum-color-background
;
color
:
$forum-color-background
;
}
.forum-nav-thread-votes-count
{
color
:
$forum-color-background
;
}
.forum-nav-thread-comments-count
{
color
:
$base-font-color
;
.forum-nav-thread-comments-count
{
color
:
$base-font-color
;
&
:after
{
@include
border-right-color
(
$forum-color-border
);
&
:after
{
@include
border-right-color
(
$forum-color-border
);
}
}
}
span
.icon
{
color
:
$forum-color-background
;
}
span
.icon
{
color
:
$forum-color-background
;
}
.thread-preview-body
{
color
:
$forum-color-background
;
.thread-preview-body
{
color
:
$forum-color-background
;
}
}
}
}
...
...
lms/static/sass/discussion/utilities/_variables-v1.scss
View file @
787dac90
...
...
@@ -25,6 +25,8 @@ $forum-color-never-read-post: $gray-d3 !default;
$forum-color-editor-preview-label
:
$gray-d2
!
default
;
$forum-color-response-count
:
$gray-d2
!
default
;
$forum-color-navigation-bar
:
#f6f6f6
!
default
;
$forum-color-count
:
$gray-d3
!
default
;
$forum-color-background-label
:
$gray-d2
!
default
;
// post images
$post-image-dimension
:
(
$baseline
*
3
)
!
default
;
// image size + margin
...
...
lms/static/sass/discussion/utilities/_variables-v2.scss
View file @
787dac90
...
...
@@ -25,6 +25,8 @@ $forum-color-never-read-post: $forum-color-primary !default;
$forum-color-editor-preview-label
:
palette
(
grayscale
,
base
)
!
default
;
$forum-color-response-count
:
palette
(
grayscale
,
base
)
!
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-image-dimension
:
(
$baseline
*
3
)
!
default
;
// image size + margin
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment