From a444166d98d367fe8b2705fbcc2b583adcb92fdf Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Tue, 18 Sep 2012 04:08:47 -0700 Subject: [PATCH] Add a followed threads entry to the topic dropdown in the discussion sidebar. Also fixed up some random bugs with content_info, and changed pagination code a little. Needs comments service update for the subscribed threads API call. --- .../django_comment_client/forum/urls.py | 1 + .../django_comment_client/forum/views.py | 62 ++++++++++++++----- lms/djangoapps/django_comment_client/utils.py | 23 +++++-- lms/lib/comment_client/user.py | 14 ++++- .../coffee/src/discussion/discussion.coffee | 22 ++++--- lms/static/coffee/src/discussion/utils.coffee | 1 + .../views/discussion_thread_list_view.coffee | 33 +++++++--- .../discussion_thread_profile_view.coffee | 1 - .../views/discussion_user_profile_view.coffee | 3 - .../discussion/_filter_dropdown.html | 7 ++- 10 files changed, 126 insertions(+), 41 deletions(-) diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/django_comment_client/forum/urls.py index 974a0b2c7b..696e981328 100644 --- a/lms/djangoapps/django_comment_client/forum/urls.py +++ b/lms/djangoapps/django_comment_client/forum/urls.py @@ -2,6 +2,7 @@ from django.conf.urls.defaults import url, patterns import django_comment_client.forum.views urlpatterns = patterns('django_comment_client.forum.views', + url(r'users/(?P\w+)/following$', 'following_threads', name='following_threads'), url(r'users/(?P\w+)$', 'user_profile', name='user_profile'), url(r'(?P[\w\-]+)/threads/(?P\w+)$', 'single_thread', name='single_thread'), url(r'(?P[\w\-]+)/inline$', 'inline_discussion', name='inline_discussion'), diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 0abcbf5fbb..9fe812ec3a 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -83,10 +83,7 @@ def inline_discussion(request, course_id, discussion_id): # checking for errors on request. Check and fix as needed. raise Http404 - def infogetter(thread): - return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) - - annotated_content_info = reduce(merge_dict, map(infogetter, threads), {}) + annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) allow_anonymous = course.metadata.get("allow_anonymous", True) allow_anonymous_to_peers = course.metadata.get("allow_anonymous_to_peers", False) @@ -118,10 +115,8 @@ def forum_form_discussion(request, course_id): user_info = cc.User.from_django_user(request.user).to_dict() - def infogetter(thread): - return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) + annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) - annotated_content_info = reduce(merge_dict, map(infogetter, threads), {}) for thread in threads: courseware_context = get_courseware_context(thread, course) if courseware_context: @@ -218,10 +213,7 @@ def single_thread(request, course_id, discussion_id, thread_id): user_info = cc.User.from_django_user(request.user).to_dict() - def infogetter(thread): - return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) - - annotated_content_info = reduce(merge_dict, map(infogetter, threads), {}) + annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) context = { 'discussion_id': discussion_id, @@ -244,7 +236,7 @@ def single_thread(request, course_id, discussion_id, thread_id): @login_required def user_profile(request, course_id, user_id): - + #TODO: Allow sorting? course = get_course_with_access(request.user, course_id, 'load') try: profiled_user = cc.User(id=user_id, course_id=course_id) @@ -260,16 +252,13 @@ def user_profile(request, course_id, user_id): if request.is_ajax(): return utils.JsonResponse({ - 'html': content, 'discussion_data': map(utils.safe_content, threads), }) else: user_info = cc.User.from_django_user(request.user).to_dict() - def infogetter(thread): - return utils.get_annotated_content_infos(course_id, thread, request.user, user_info) + annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) - annotated_content_info = reduce(merge_dict, map(infogetter, threads), {}) context = { 'course': course, 'user': request.user, @@ -284,3 +273,44 @@ def user_profile(request, course_id, user_id): return render_to_response('discussion/user_profile.html', context) except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: raise Http404 + + +def following_threads(request, course_id, user_id): + course = get_course_with_access(request.user, course_id, 'load') + try: + profiled_user = cc.User(id=user_id, course_id=course_id) + + query_params = { + 'page': request.GET.get('page', 1), + 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities + 'sort_key': 'date',#TODO: Allow custom sorting? + 'sort_order': 'desc', + } + print user_id + threads, page, num_pages = profiled_user.subscribed_threads(query_params) + query_params['page'] = page + query_params['num_pages'] = num_pages + user_info = cc.User.from_django_user(request.user).to_dict() + + annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info) + if request.is_ajax(): + return utils.JsonResponse({ + 'annotated_content_info': annotated_content_info, + 'discussion_data': map(utils.safe_content, threads), + }) + else: + + context = { + 'course': course, + 'user': request.user, + 'django_user': User.objects.get(id=user_id), + 'profiled_user': profiled_user.to_dict(), + 'threads': saxutils.escape(json.dumps(threads), escapedict), + 'user_info': saxutils.escape(json.dumps(user_info),escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), + # 'content': content, + } + + return render_to_response('discussion/user_profile.html', context) + except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: + raise Http404 diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 305f3d0929..1dcb5522f7 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -259,7 +259,11 @@ def get_ability(course_id, content, user): 'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"), } +#TODO: RENAME def get_annotated_content_info(course_id, content, user, user_info): + """ + Get metadata for an individual content (thread or comment) + """ voted = '' if content['id'] in user_info['upvoted_ids']: voted = 'up' @@ -271,7 +275,11 @@ def get_annotated_content_info(course_id, content, user, user_info): 'ability': get_ability(course_id, content, user), } +#TODO: RENAME def get_annotated_content_infos(course_id, thread, user, user_info): + """ + Get metadata for a thread and its children + """ infos = {} def annotate(content): infos[str(content['id'])] = get_annotated_content_info(course_id, content, user, user_info) @@ -280,6 +288,13 @@ def get_annotated_content_infos(course_id, thread, user, user_info): annotate(thread) return infos +def get_metadata_for_threads(course_id, threads, user, user_info): + def infogetter(thread): + return get_annotated_content_infos(course_id, thread, user, user_info) + + metadata = reduce(merge_dict, map(infogetter, threads), {}) + return metadata + # put this method in utils.py to avoid circular import dependency between helpers and mustache_helpers def url_for_tags(course_id, tags): return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id]) + '?' + urllib.urlencode({'tags': tags}) @@ -304,7 +319,7 @@ def extend_content(content): roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])) except user.DoesNotExist: logging.error('User ID {0} in comment content {1} but not in our DB.'.format(content.get('user_id'), content.get('id'))) - + content_info = { 'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''), @@ -323,9 +338,9 @@ def get_courseware_context(content, course): location = id_map[id]["location"].url() title = id_map[id]["title"] (course_id, chapter, section, position) = path_to_location(modulestore(), course.id, location) - url = reverse('courseware_position', kwargs={"course_id":course_id, - "chapter":chapter, - "section":section, + url = reverse('courseware_position', kwargs={"course_id":course_id, + "chapter":chapter, + "section":section, "position":position}) content_info = {"courseware_url": url, "courseware_title": title} return content_info diff --git a/lms/lib/comment_client/user.py b/lms/lib/comment_client/user.py index 9813e9a199..546b27556c 100644 --- a/lms/lib/comment_client/user.py +++ b/lms/lib/comment_client/user.py @@ -8,7 +8,7 @@ class User(models.Model): accessible_fields = ['username', 'email', 'follower_ids', 'upvoted_ids', 'downvoted_ids', 'id', 'external_id', 'subscribed_user_ids', 'children', 'course_id', 'subscribed_thread_ids', 'subscribed_commentable_ids', - 'subscribed_course_ids', 'threads_count', 'comments_count', + 'subscribed_course_ids', 'threads_count', 'comments_count', 'default_sort_key' ] @@ -65,6 +65,15 @@ class User(models.Model): response = perform_request('get', url, params) return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + def subscribed_threads(self, query_params={}): + if not self.course_id: + raise CommentClientError("Must provide course_id when retrieving subscribed threads for the user") + url = _url_for_user_subscribed_threads(self.id) + params = {'course_id': self.course_id} + params = merge_dict(params, query_params) + response = perform_request('get', url, params) + return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + def _retrieve(self, *args, **kwargs): url = self.url(action='get', params=self.attributes) retrieve_params = self.default_retrieve_params @@ -84,3 +93,6 @@ def _url_for_subscription(user_id): def _url_for_user_active_threads(user_id): return "{prefix}/users/{user_id}/active_threads".format(prefix=settings.PREFIX, user_id=user_id) + +def _url_for_user_subscribed_threads(user_id): + return "{prefix}/users/{user_id}/subscribed_threads".format(prefix=settings.PREFIX, user_id=user_id) diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee index eadf65c6ae..52e8d466e2 100644 --- a/lms/static/coffee/src/discussion/discussion.coffee +++ b/lms/static/coffee/src/discussion/discussion.coffee @@ -25,17 +25,22 @@ if Backbone? @add model model - retrieveAnotherPage: (search_text="", commentable_ids="", sort_key="")-> + retrieveAnotherPage: (mode, options={}, sort_options={})-> # TODO: I really feel that this belongs in DiscussionThreadListView @current_page += 1 - url = DiscussionUtil.urlFor 'threads' data = { page: @current_page } - if search_text - data['text'] = search_text - if sort_key - data['sort_key'] = sort_key - if commentable_ids - data['commentable_ids'] = commentable_ids + switch mode + when 'search' + url = DiscussionUtil.urlFor 'search' + data['text'] = options.search_text + if options.commentable_ids + data['commentable_ids'] = options.commentable_ids + when 'all' + url = DiscussionUtil.urlFor 'threads' + when 'following' + url = DiscussionUtil.urlFor 'following_threads', options.user_id + data['sort_key'] = sort_options.sort_key || 'date' + data['sort_order'] = sort_options.sort_order || 'desc' DiscussionUtil.safeAjax $elem: @$el url: url @@ -45,6 +50,7 @@ if Backbone? models = @models new_threads = [new Thread(data) for data in response.discussion_data][0] new_collection = _.union(models, new_threads) + Content.loadContentInfos(response.annotated_content_info) @reset new_collection sortByDate: (thread) -> diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee index e76ea346ff..e1e5649edc 100644 --- a/lms/static/coffee/src/discussion/utils.coffee +++ b/lms/static/coffee/src/discussion/utils.coffee @@ -66,6 +66,7 @@ class @DiscussionUtil permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}" permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}" user_profile : "/courses/#{$$course_id}/discussion/forum/users/#{param}" + following_threads : "/courses/#{$$course_id}/discussion/forum/users/#{param}/following" threads : "/courses/#{$$course_id}/discussion/forum" }[name] diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee index d998763f50..98579b7a74 100644 --- a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee +++ b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee @@ -31,6 +31,7 @@ if Backbone? @boardName @template = _.template($("#thread-list-template").html()) @current_search = "" + @mode = 'all' reloadDisplayedCollection: (thread) => thread_id = thread.get('id') @@ -122,7 +123,14 @@ if Backbone? event.preventDefault() @$(".more-pages").html('
') @$(".more-pages").addClass("loading") - @collection.retrieveAnotherPage(@current_search, @discussionIds, @sortBy) + options = {} + switch @mode + when 'search' + options.search_text = @current_search + options.commentable_ids = @discussionIds + when 'following' + options.user_id = window.user.id + @collection.retrieveAnotherPage(@mode, options, {sort_key: @sortBy}) renderThread: (thread) => content = $(_.template($("#thread-list-item-template").html())(thread.toJSON())) @@ -146,7 +154,7 @@ if Backbone? threadSelected: (e) => thread_id = $(e.target).closest("a").data("id") @setActiveThread(thread_id) - @trigger("thread:selected", thread_id) + @trigger("thread:selected", thread_id) # This triggers a callback in the DiscussionRouter which calls the line above... false threadRemoved: (thread_id) => @@ -243,10 +251,14 @@ if Backbone? else @setTopic(event) # just sets the title for the dropdown item = $(event.target).closest('li') - if item.find("span.board-name").data("discussion_id") == "#all" + discussionId = item.find("span.board-name").data("discussion_id") + if discussionId == "#all" @discussionIds = "" @$(".post-search-field").val("") @retrieveAllThreads() + else if discussionId == "#following" + @retrieveFollowing(event) + # Retrieve following else discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id @retrieveDiscussions(discussionIds) @@ -260,7 +272,7 @@ if Backbone? @collection.current_page = response.page @collection.pages = response.num_pages @collection.reset(response.discussion_data) - Content.loadContentInfos(response.content_info) + Content.loadContentInfos(response.annotated_content_info) @displayedCollection.reset(@collection.models) if callback? callback() @@ -276,7 +288,7 @@ if Backbone? @collection.current_page = response.page @collection.pages = response.num_pages @collection.reset(response.discussion_data) - Content.loadContentInfos(response.content_info) + Content.loadContentInfos(response.annotated_content_info) @displayedCollection.reset(@collection.models) retrieveAllThreads: () -> @@ -288,7 +300,7 @@ if Backbone? @collection.current_page = response.page @collection.pages = response.num_pages @collection.reset(response.discussion_data) - Content.loadContentInfos(response.content_info) + Content.loadContentInfos(response.annotated_content_info) @displayedCollection.reset(@collection.models) sortThreads: (event) -> @@ -315,6 +327,7 @@ if Backbone? @searchFor(text) searchFor: (text, callback, value) -> + @mode = 'search' @current_search = text url = DiscussionUtil.urlFor("search") DiscussionUtil.safeAjax @@ -332,7 +345,7 @@ if Backbone? if textStatus == 'success' # TODO: Augment existing collection? @collection.reset(response.discussion_data) - Content.loadContentInfos(response.content_info) + Content.loadContentInfos(response.annotated_content_info) @collection.current_page = response.page @collection.pages = response.num_pages # TODO: Perhaps reload user info so that votes can be updated. @@ -370,3 +383,9 @@ if Backbone? scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop) scrollTarget = Math.max(scrollTop - itemFromTop - $(".browse-topic-drop-menu").height() + $(items[index]).height(), scrollTarget) $(".browse-topic-drop-menu").scrollTop(scrollTarget) + + retrieveFollowing: (event)=> + @mode = 'following' + @collection.reset() + @collection.current_page = 0 + @loadMorePages(event) diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee index fa7a8a86b7..d31a402a99 100644 --- a/lms/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee +++ b/lms/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee @@ -84,7 +84,6 @@ if Backbone? toggleFollowing: (event) -> $elem = $(event.target) url = null - console.log "follow" if not @model.get('subscribed') @model.follow() url = @model.urlFor("follow") diff --git a/lms/static/coffee/src/discussion/views/discussion_user_profile_view.coffee b/lms/static/coffee/src/discussion/views/discussion_user_profile_view.coffee index ed5645e5e5..c3415fecf9 100644 --- a/lms/static/coffee/src/discussion/views/discussion_user_profile_view.coffee +++ b/lms/static/coffee/src/discussion/views/discussion_user_profile_view.coffee @@ -6,15 +6,12 @@ if Backbone? @renderThreads @$el, @collection renderThreads: ($elem, threads) => #Content.loadContentInfos(response.annotated_content_info) - console.log threads @discussion = new Discussion() @discussion.reset(threads, {silent: false}) $discussion = $(Mustache.render $("script#_user_profile").html(), {'threads':threads}) - console.log $discussion $elem.append($discussion) @threadviews = @discussion.map (thread) -> new DiscussionThreadProfileView el: @$("article#thread_#{thread.id}"), model: thread - console.log @threadviews _.each @threadviews, (dtv) -> dtv.render() addThread: (thread, collection, options) => diff --git a/lms/templates/discussion/_filter_dropdown.html b/lms/templates/discussion/_filter_dropdown.html index d24c89a734..484ee05101 100644 --- a/lms/templates/discussion/_filter_dropdown.html +++ b/lms/templates/discussion/_filter_dropdown.html @@ -27,12 +27,17 @@ -