Merge pull request #3738 from mlkwaqas/waqas/for545-user-last-thread-not-remembered
Waqas/for545 user last thread not remembered
This commit is contained in:
@@ -6,7 +6,6 @@ import re
|
||||
import urlparse
|
||||
from .http import StubHttpRequestHandler, StubHttpService
|
||||
|
||||
|
||||
class StubCommentsServiceHandler(StubHttpRequestHandler):
|
||||
|
||||
@property
|
||||
@@ -23,26 +22,41 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
|
||||
"/api/v1/comments/(?P<comment_id>\\w+)$": self.do_comment,
|
||||
"/api/v1/(?P<commentable_id>\\w+)/threads$": self.do_commentable,
|
||||
}
|
||||
if self.match_pattern(pattern_handlers):
|
||||
return
|
||||
|
||||
self.send_response(404, content="404 Not Found")
|
||||
|
||||
def match_pattern(self, pattern_handlers):
|
||||
path = urlparse.urlparse(self.path).path
|
||||
for pattern in pattern_handlers:
|
||||
match = re.match(pattern, path)
|
||||
if match:
|
||||
pattern_handlers[pattern](**match.groupdict())
|
||||
return
|
||||
|
||||
self.send_response(404, content="404 Not Found")
|
||||
return True
|
||||
return None
|
||||
|
||||
def do_PUT(self):
|
||||
if self.path.startswith('/set_config'):
|
||||
return StubHttpRequestHandler.do_PUT(self)
|
||||
pattern_handlers = {
|
||||
"/api/v1/users/(?P<user_id>\\d+)$": self.do_put_user,
|
||||
}
|
||||
if self.match_pattern(pattern_handlers):
|
||||
return
|
||||
self.send_response(204, "")
|
||||
|
||||
def do_put_user(self, user_id):
|
||||
self.server.config['default_sort_key'] = self.post_dict.get("default_sort_key", "date")
|
||||
self.send_json_response({'username': self.post_dict.get("username"), 'external_id': self.post_dict.get("external_id")})
|
||||
|
||||
def do_DELETE(self):
|
||||
self.send_json_response({})
|
||||
|
||||
def do_user(self, user_id):
|
||||
response = {
|
||||
"id": user_id,
|
||||
"default_sort_key": self.server.config.get("default_sort_key", "date"),
|
||||
"upvoted_ids": [],
|
||||
"downvoted_ids": [],
|
||||
"subscribed_thread_ids": [],
|
||||
|
||||
@@ -3,6 +3,36 @@ describe "DiscussionThreadListView", ->
|
||||
beforeEach ->
|
||||
|
||||
setFixtures """
|
||||
<script type="text/template" id="thread-list-item-template">
|
||||
<a href="<%- id %>" data-id="<%- id %>">
|
||||
<span class="title"><%- title %></span>
|
||||
<span class="comments-count">
|
||||
<%
|
||||
var fmt;
|
||||
var data = {
|
||||
'span_sr_open': '<span class="sr">',
|
||||
'span_close': '</span>',
|
||||
'unread_comments_count': unread_comments_count,
|
||||
'comments_count': comments_count
|
||||
};
|
||||
if (unread_comments_count > 0) {
|
||||
fmt = '%(comments_count)s %(span_sr_open)scomments (%(unread_comments_count)s unread comments)%(span_close)s';
|
||||
} else {
|
||||
fmt = '%(comments_count)s %(span_sr_open)scomments %(span_close)s';
|
||||
}
|
||||
print(interpolate(fmt, data, true));
|
||||
%>
|
||||
</span>
|
||||
|
||||
<span class="votes-count">+<%=
|
||||
interpolate(
|
||||
'%(votes_up_count)s%(span_sr_open)s votes %(span_close)s',
|
||||
{'span_sr_open': '<span class="sr">', 'span_close': '</span>', 'votes_up_count': votes['up_count']},
|
||||
true
|
||||
)
|
||||
%></span>
|
||||
</a>
|
||||
</script>
|
||||
<script type="text/template" id="thread-list-template">
|
||||
<div class="browse-search">
|
||||
<div class="home"></div>
|
||||
@@ -14,7 +44,14 @@ describe "DiscussionThreadListView", ->
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sort-bar"></div>
|
||||
<div class="sort-bar">
|
||||
<span class="sort-label" id="sort-label">Sort by:</span>
|
||||
<ul role="radiogroup" aria-labelledby="sort-label">
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="date">date</a></li>
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="votes">votes</a></li>
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="comments">comments</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="search-alerts"></div>
|
||||
<div class="post-list-wrapper">
|
||||
<ul class="post-list"></ul>
|
||||
@@ -33,6 +70,11 @@ describe "DiscussionThreadListView", ->
|
||||
</script>
|
||||
<div class="sidebar"></div>
|
||||
"""
|
||||
@threads = [
|
||||
{id: "1", title: "Thread1", body: "dummy body", votes: {up_count: '20'}, unread_comments_count:0, comments_count:1, created_at: '2013-04-03T20:08:39Z',},
|
||||
{id: "2", title: "Thread2", body: "dummy body", votes: {up_count: '42'}, unread_comments_count:0, comments_count:2, created_at: '2013-04-03T20:07:39Z',},
|
||||
{id: "3", title: "Thread3", body: "dummy body", votes: {up_count: '12'}, unread_comments_count:0, comments_count:3, created_at: '2013-04-03T20:06:39Z',},
|
||||
]
|
||||
window.$$course_id = "TestOrg/TestCourse/TestRun"
|
||||
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
|
||||
|
||||
@@ -98,3 +140,65 @@ describe "DiscussionThreadListView", ->
|
||||
spyOn(@view, "renderThread")
|
||||
@view.collection.trigger("change", new Thread({id: 1}))
|
||||
expect(@view.clearSearchAlerts).toHaveBeenCalled()
|
||||
|
||||
makeView = (discussion) ->
|
||||
return new DiscussionThreadListView(
|
||||
el: $(".sidebar"),
|
||||
collection: discussion
|
||||
)
|
||||
|
||||
checkThreadsOrdering = (view, sort_order, type) ->
|
||||
expect(view.$el.find(".post-list .list-item").children().length).toEqual(3)
|
||||
expect(view.$el.find(".post-list .list-item:nth-child(1) .title").text()).toEqual(sort_order[0])
|
||||
expect(view.$el.find(".post-list .list-item:nth-child(2) .title").text()).toEqual(sort_order[1])
|
||||
expect(view.$el.find(".post-list .list-item:nth-child(3) .title").text()).toEqual(sort_order[2])
|
||||
expect(view.$el.find(".sort-bar a.active").text()).toEqual(type)
|
||||
|
||||
describe "thread rendering should be correct", ->
|
||||
checkRender = (threads, type, sort_order) ->
|
||||
discussion = new Discussion(threads, {pages: 1, sort: type})
|
||||
view = makeView(discussion)
|
||||
view.render()
|
||||
checkThreadsOrdering(view, sort_order, type)
|
||||
|
||||
it "with sort preference date", ->
|
||||
checkRender(@threads, "date", [ "Thread1", "Thread2", "Thread3"])
|
||||
|
||||
it "with sort preference votes", ->
|
||||
checkRender(@threads, "votes", [ "Thread2", "Thread1", "Thread3"])
|
||||
|
||||
it "with sort preference comments", ->
|
||||
checkRender(@threads, "comments", [ "Thread3", "Thread2", "Thread1"])
|
||||
|
||||
describe "Sort click should be correct", ->
|
||||
changeSorting = (threads, selected_type, new_type, sort_order) ->
|
||||
discussion = new Discussion(threads, {pages: 1, sort: selected_type})
|
||||
view = makeView(discussion)
|
||||
view.render()
|
||||
expect(view.$el.find(".sort-bar a.active").text()).toEqual(selected_type)
|
||||
sorted_threads = []
|
||||
if new_type == 'date'
|
||||
sorted_threads = [threads[0], threads[1], threads[2]]
|
||||
else if new_type == 'comments'
|
||||
sorted_threads = [threads[2], threads[1], threads[0]]
|
||||
else if new_type == 'votes'
|
||||
sorted_threads = [threads[1], threads[0], threads[2]]
|
||||
$.ajax.andCallFake((params) =>
|
||||
params.success(
|
||||
{"discussion_data":sorted_threads, page:1, num_pages:1}
|
||||
)
|
||||
{always: ->}
|
||||
)
|
||||
view.$el.find(".sort-bar a[data-sort='"+new_type+"']").click()
|
||||
expect($.ajax).toHaveBeenCalled()
|
||||
expect(view.sortBy).toEqual(new_type)
|
||||
checkThreadsOrdering(view, sort_order, new_type)
|
||||
|
||||
it "with sort preference date", ->
|
||||
changeSorting(@threads, "comments", "date", ["Thread1", "Thread2", "Thread3"])
|
||||
|
||||
it "with sort preference votes", ->
|
||||
changeSorting(@threads, "date", "votes", ["Thread2", "Thread1", "Thread3"])
|
||||
|
||||
it "with sort preference comments", ->
|
||||
changeSorting(@threads, "votes", "comments", ["Thread3", "Thread2", "Thread1"])
|
||||
|
||||
@@ -5,9 +5,10 @@ if Backbone?
|
||||
initialize: (models, options={})->
|
||||
@pages = options['pages'] || 1
|
||||
@current_page = 1
|
||||
@sort_preference = options['sort']
|
||||
@bind "add", (item) =>
|
||||
item.discussion = @
|
||||
@comparator = @sortByDateRecentFirst
|
||||
@setSortComparator(@sort_preference)
|
||||
@on "thread:remove", (thread) =>
|
||||
@remove(thread)
|
||||
|
||||
@@ -17,6 +18,12 @@ if Backbone?
|
||||
hasMorePages: ->
|
||||
@current_page < @pages
|
||||
|
||||
setSortComparator: (sortBy) ->
|
||||
switch sortBy
|
||||
when 'date' then @comparator = @sortByDateRecentFirst
|
||||
when 'votes' then @comparator = @sortByVotes
|
||||
when 'comments' then @comparator = @sortByComments
|
||||
|
||||
addThread: (thread, options) ->
|
||||
# TODO: Check for existing thread with same ID in a faster way
|
||||
if not @find(thread.id)
|
||||
|
||||
@@ -6,12 +6,13 @@ if Backbone?
|
||||
element = $(elem)
|
||||
window.$$course_id = element.data("course-id")
|
||||
user_info = element.data("user-info")
|
||||
sort_preference = element.data("sort-preference")
|
||||
threads = element.data("threads")
|
||||
thread_pages = element.data("thread-pages")
|
||||
content_info = element.data("content-info")
|
||||
window.user = new DiscussionUser(user_info)
|
||||
Content.loadContentInfos(content_info)
|
||||
discussion = new Discussion(threads, pages: thread_pages)
|
||||
discussion = new Discussion(threads, {pages: thread_pages, sort: sort_preference})
|
||||
new DiscussionRouter({discussion: discussion})
|
||||
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"})
|
||||
DiscussionProfileApp =
|
||||
|
||||
@@ -132,6 +132,9 @@ if Backbone?
|
||||
@displayedCollection.on "reset", @renderThreads
|
||||
@displayedCollection.on "thread:remove", @renderThreads
|
||||
@renderThreads()
|
||||
sort_element = @$('.sort-bar a[data-sort="' + this.collection.sort_preference + '"]')
|
||||
sort_element.attr('aria-checked',true)
|
||||
sort_element.addClass('active')
|
||||
@
|
||||
|
||||
renderThreads: =>
|
||||
@@ -413,18 +416,14 @@ if Backbone?
|
||||
@loadMorePages(event)
|
||||
|
||||
sortThreads: (event) ->
|
||||
activeSort = @$(".sort-bar a[class='active']")
|
||||
activeSort = @$(".sort-bar a.active")
|
||||
activeSort.removeClass("active")
|
||||
activeSort.attr("aria-checked", "false")
|
||||
newSort = $(event.target)
|
||||
newSort.addClass("active")
|
||||
newSort.attr("aria-checked", "true")
|
||||
@sortBy = newSort.data("sort")
|
||||
|
||||
@displayedCollection.comparator = switch @sortBy
|
||||
when 'date' then @displayedCollection.sortByDateRecentFirst
|
||||
when 'votes' then @displayedCollection.sortByVotes
|
||||
when 'comments' then @displayedCollection.sortByComments
|
||||
@displayedCollection.setSortComparator(@sortBy)
|
||||
@retrieveFirstPage(event)
|
||||
|
||||
performSearch: (event) ->
|
||||
|
||||
@@ -125,4 +125,3 @@ class SearchResultFixture(DiscussionContentFixture):
|
||||
|
||||
def get_config_data(self):
|
||||
return {"search_result": json.dumps(self.result)}
|
||||
|
||||
|
||||
@@ -167,6 +167,38 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
).fulfill()
|
||||
|
||||
|
||||
class DiscussionSortPreferencePage(CoursePage):
|
||||
"""
|
||||
Page that contain the discussion board with sorting options
|
||||
"""
|
||||
def __init__(self, browser, course_id):
|
||||
super(DiscussionSortPreferencePage, self).__init__(browser, course_id)
|
||||
self.url_path = "discussion/forum"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""
|
||||
Return true if the browser is on the right page else false.
|
||||
"""
|
||||
return self.q(css="body.discussion .sort-bar").present
|
||||
|
||||
def get_selected_sort_preference_text(self):
|
||||
"""
|
||||
Return the text of option that is selected for sorting.
|
||||
"""
|
||||
return self.q(css="body.discussion .sort-bar a.active").text[0].lower()
|
||||
|
||||
def change_sort_preference(self, sort_by):
|
||||
"""
|
||||
Change the option of sorting by clicking on new option.
|
||||
"""
|
||||
self.q(css="body.discussion .sort-bar a[data-sort='{0}']".format(sort_by)).click()
|
||||
|
||||
def refresh_page(self):
|
||||
"""
|
||||
Reload the page.
|
||||
"""
|
||||
self.browser.refresh()
|
||||
|
||||
class DiscussionTabSingleThreadPage(CoursePage):
|
||||
def __init__(self, browser, course_id, thread_id):
|
||||
super(DiscussionTabSingleThreadPage, self).__init__(browser, course_id)
|
||||
|
||||
@@ -12,7 +12,8 @@ from ..pages.lms.discussion import (
|
||||
InlineDiscussionPage,
|
||||
InlineDiscussionThreadPage,
|
||||
DiscussionUserProfilePage,
|
||||
DiscussionTabHomePage
|
||||
DiscussionTabHomePage,
|
||||
DiscussionSortPreferencePage,
|
||||
)
|
||||
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ..fixtures.discussion import (
|
||||
@@ -22,7 +23,7 @@ from ..fixtures.discussion import (
|
||||
Thread,
|
||||
Response,
|
||||
Comment,
|
||||
SearchResult
|
||||
SearchResult,
|
||||
)
|
||||
|
||||
|
||||
@@ -458,3 +459,52 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
|
||||
self.setup_corrected_text(None)
|
||||
self.page.perform_search()
|
||||
self.check_search_alert_messages([])
|
||||
|
||||
|
||||
class DiscussionSortPreferenceTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests for the discussion page displaying a single thread.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(DiscussionSortPreferenceTest, self).setUp()
|
||||
|
||||
# Create a course to register for.
|
||||
CourseFixture(**self.course_info).install()
|
||||
|
||||
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
||||
|
||||
self.sort_page = DiscussionSortPreferencePage(self.browser, self.course_id)
|
||||
self.sort_page.visit()
|
||||
|
||||
def test_default_sort_preference(self):
|
||||
"""
|
||||
Test to check the default sorting preference of user. (Default = date )
|
||||
"""
|
||||
selected_sort = self.sort_page.get_selected_sort_preference_text()
|
||||
self.assertEqual(selected_sort, "date")
|
||||
|
||||
def test_change_sort_preference(self):
|
||||
"""
|
||||
Test that if user sorting preference is changing properly.
|
||||
"""
|
||||
selected_sort = ""
|
||||
for sort_type in ["votes", "comments", "date"]:
|
||||
self.assertNotEqual(selected_sort, sort_type)
|
||||
self.sort_page.change_sort_preference(sort_type)
|
||||
selected_sort = self.sort_page.get_selected_sort_preference_text()
|
||||
self.assertEqual(selected_sort, sort_type)
|
||||
|
||||
def test_last_preference_saved(self):
|
||||
"""
|
||||
Test that user last preference is saved.
|
||||
"""
|
||||
selected_sort = ""
|
||||
for sort_type in ["votes", "comments", "date"]:
|
||||
self.assertNotEqual(selected_sort, sort_type)
|
||||
self.sort_page.change_sort_preference(sort_type)
|
||||
selected_sort = self.sort_page.get_selected_sort_preference_text()
|
||||
self.assertEqual(selected_sort, sort_type)
|
||||
self.sort_page.refresh_page()
|
||||
selected_sort = self.sort_page.get_selected_sort_preference_text()
|
||||
self.assertEqual(selected_sort, sort_type)
|
||||
|
||||
@@ -118,6 +118,7 @@ def make_mock_request_impl(text, thread_id="dummy_thread_id"):
|
||||
data = make_mock_thread_data(text, thread_id, True)
|
||||
elif "/users/" in url:
|
||||
data = {
|
||||
"default_sort_key": "date",
|
||||
"upvoted_ids": [],
|
||||
"downvoted_ids": [],
|
||||
"subscribed_thread_ids": [],
|
||||
|
||||
@@ -3,9 +3,9 @@ import logging
|
||||
import xml.sax.saxutils as saxutils
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import Http404
|
||||
from django.core.context_processors import csrf
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import Http404
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_GET
|
||||
import newrelic.agent
|
||||
@@ -225,7 +225,8 @@ def forum_form_discussion(request, course_id):
|
||||
'cohorts': cohorts,
|
||||
'user_cohort': user_cohort_id,
|
||||
'cohorted_commentables': cohorted_commentables,
|
||||
'is_course_cohorted': is_course_cohorted(course_id)
|
||||
'is_course_cohorted': is_course_cohorted(course_id),
|
||||
'sort_preference': user.default_sort_key,
|
||||
}
|
||||
# print "start rendering.."
|
||||
return render_to_response('discussion/index.html', context)
|
||||
@@ -296,7 +297,6 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
cohorts = get_course_cohorts(course_id)
|
||||
cohorted_commentables = get_cohorted_commentables(course_id)
|
||||
user_cohort = get_cohort_id(request.user, course_id)
|
||||
|
||||
context = {
|
||||
'discussion_id': discussion_id,
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
@@ -316,9 +316,9 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course),
|
||||
'cohorts': cohorts,
|
||||
'user_cohort': get_cohort_id(request.user, course_id),
|
||||
'cohorted_commentables': cohorted_commentables
|
||||
'cohorted_commentables': cohorted_commentables,
|
||||
'sort_preference': cc_user.default_sort_key,
|
||||
}
|
||||
|
||||
return render_to_response('discussion/index.html', context)
|
||||
|
||||
@require_GET
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="sort-bar">
|
||||
<span class="sort-label" id="sort-label">${_("Sort by:")}</span>
|
||||
<ul role="radiogroup" aria-labelledby="sort-label">
|
||||
<li><a href="#" role="radio" aria-checked="true" class="active" data-sort="date">${_("date")}</a></li>
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="date">${_("date")}</a></li>
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="votes">${_("votes")}</a></li>
|
||||
<li><a href="#" role="radio" aria-checked="false" data-sort="comments">${_("comments")}</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<%include file="_new_post.html" />
|
||||
|
||||
<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-flag-moderator="${flag_moderator}">
|
||||
<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}">
|
||||
<div class="discussion-body">
|
||||
<div class="sidebar"></div>
|
||||
<div class="discussion-column">
|
||||
|
||||
Reference in New Issue
Block a user