views.py 13.3 KB
Newer Older
Arjun Singh committed
1
import json
2
import logging
Arjun Singh committed
3

4 5
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
6
from django.http import HttpResponse, Http404
7 8
from django.utils import simplejson
from django.core.context_processors import csrf
Rocky Duan committed
9
from django.core.urlresolvers import reverse
10
from django.contrib.auth.models import User
11 12

from mitxmako.shortcuts import render_to_response, render_to_string
13
from courseware.courses import get_course_with_access
14
from courseware.access import has_access
15

Rocky Duan committed
16
from urllib import urlencode
17
from operator import methodcaller
18
from django_comment_client.permissions import check_permissions_by_view
19
from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank, get_courseware_context
20

Rocky Duan committed
21 22
import django_comment_client.utils as utils
import comment_client as cc
23
import xml.sax.saxutils as saxutils
Rocky Duan committed
24

25
THREADS_PER_PAGE = 20
26
INLINE_THREADS_PER_PAGE = 20
Rocky Duan committed
27
PAGES_NEARBY_DELTA = 2
28
escapedict = {'"': '"'}
Arjun Singh committed
29
log = logging.getLogger("edx.discussions")
30

31
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE):
32 33 34 35
    """
    This may raise cc.utils.CommentClientError or
    cc.utils.CommentClientUnknownError if something goes wrong.
    """
Rocky Duan committed
36 37 38

    default_query_params = {
        'page': 1,
39
        'per_page': per_page,
40
        'sort_key': 'date',
Rocky Duan committed
41 42 43
        'sort_order': 'desc',
        'text': '',
        'tags': '',
44 45
        'commentable_id': discussion_id,
        'course_id': course_id,
Rocky Duan committed
46
        'user_id': request.user.id,
Rocky Duan committed
47 48
    }

49 50 51 52
    if not request.GET.get('sort_key'):
        # If the user did not select a sort key, use their last used sort key
        user = cc.User.from_django_user(request.user)
        user.retrieve()
53 54
        # TODO: After the comment service is updated this can just be user.default_sort_key because the service returns the default value
        default_query_params['sort_key'] = user.get('default_sort_key') or default_query_params['sort_key']
55 56 57 58 59 60
    else:
        # If the user clicked a sort key, update their default sort key
        user = cc.User.from_django_user(request.user)
        user.default_sort_key = request.GET.get('sort_key')
        user.save()

Rocky Duan committed
61
    query_params = merge_dict(default_query_params,
62
                              strip_none(extract(request.GET, ['page', 'sort_key', 'sort_order', 'text', 'tags', 'commentable_ids'])))
Rocky Duan committed
63

64
    threads, page, num_pages = cc.Thread.search(query_params)
65

Rocky Duan committed
66 67 68 69 70
    query_params['page'] = page
    query_params['num_pages'] = num_pages

    return threads, query_params

71
def inline_discussion(request, course_id, discussion_id):
72 73 74
    """
    Renders JSON for DiscussionModules
    """
Arjun Singh committed
75

76 77
    course = get_course_with_access(request.user, course_id, 'load')

78
    try:
Arjun Singh committed
79 80
        threads, query_params = get_threads(request, course_id, discussion_id, per_page=INLINE_THREADS_PER_PAGE)
        user_info = cc.User.from_django_user(request.user).to_dict()
81 82 83 84
    except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
        # TODO (vshnayder): since none of this code seems to be aware of the fact that
        # sometimes things go wrong, I suspect that the js client is also not
        # checking for errors on request.  Check and fix as needed.
85
        log.error("Error loading inline discussion threads.")
86 87
        raise Http404

88
    annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info)
89

90 91 92
    allow_anonymous = course.metadata.get("allow_anonymous", True)
    allow_anonymous_to_peers = course.metadata.get("allow_anonymous_to_peers", False)

Rocky Duan committed
93
    return utils.JsonResponse({
94
        'discussion_data': map(utils.safe_content, threads),
95
        'user_info': user_info,
96 97
        'annotated_content_info': annotated_content_info,
        'page': query_params['page'],
98 99
        'num_pages': query_params['num_pages'],
        'roles': utils.get_role_ids(course_id),
100 101
        'allow_anonymous_to_peers': allow_anonymous_to_peers,
        'allow_anonymous': allow_anonymous,
Rocky Duan committed
102
    })
103

104
@login_required
105
def forum_form_discussion(request, course_id):
106
    """
107
    Renders the main Discussion page, potentially filtered by a search query
108
    """
109
    course = get_course_with_access(request.user, course_id, 'load')
110
    category_map = utils.get_discussion_category_map(course)
Arjun Singh committed
111

112
    try:
113
        unsafethreads, query_params = get_threads(request, course_id)   # This might process a search query
114
        threads = [utils.safe_content(thread) for thread in unsafethreads]
115
    except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
116
        log.error("Error loading forum discussion threads: %s" % str(err))
117 118
        raise Http404

119
    user_info = cc.User.from_django_user(request.user).to_dict()
120

121
    annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info)
122

123 124 125
    for thread in threads:
        courseware_context = get_courseware_context(thread, course)
        if courseware_context:
126
            thread.update(courseware_context)
127
    if request.is_ajax():
128
        return utils.JsonResponse({
129
            'discussion_data': threads, # TODO: Standardize on 'discussion_data' vs 'threads'
130
            'annotated_content_info': annotated_content_info,
131 132
            'num_pages': query_params['num_pages'],
            'page': query_params['page'],
133
        })
134
    else:
Arjun Singh committed
135 136 137 138 139 140 141 142 143
        #recent_active_threads = cc.search_recent_active_threads(
        #    course_id,
        #    recursive=False,
        #    query_params={'follower_id': request.user.id},
        #)

        #trending_tags = cc.search_trending_tags(
        #    course_id,
        #)
144

145 146 147
        context = {
            'csrf': csrf(request)['csrf_token'],
            'course': course,
Arjun Singh committed
148 149
            #'recent_active_threads': recent_active_threads,
            #'trending_tags': trending_tags,
150
            'staff_access' : has_access(request.user, course, 'staff'),
151
            'threads': saxutils.escape(json.dumps(threads),escapedict),
Matthew Mongeau committed
152
            'thread_pages': query_params['num_pages'],
153
            'user_info': saxutils.escape(json.dumps(user_info),escapedict),
154
            'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict),
155 156
            'course_id': course.id,
            'category_map': category_map,
157
            'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
158
        }
159
        # print "start rendering.."
160
        return render_to_response('discussion/index.html', context)
161

162
@login_required
Rocky Duan committed
163
def single_thread(request, course_id, discussion_id, thread_id):
164

165 166 167
    course = get_course_with_access(request.user, course_id, 'load')
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()
Rocky Duan committed
168

Arjun Singh committed
169 170 171 172 173
    try:
        thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id)
    except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
        log.error("Error loading single thread.")
        raise Http404
Arjun Singh committed
174

Rocky Duan committed
175
    if request.is_ajax():
Arjun Singh committed
176

177
        courseware_context = get_courseware_context(thread, course)
178

179
        annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info)
180
        context = {'thread': thread.to_dict(), 'course_id': course_id}
181
        # TODO: Remove completely or switch back to server side rendering
182
        # html = render_to_string('discussion/_ajax_single_thread.html', context)
183
        content = utils.safe_content(thread.to_dict())
184 185
        if courseware_context:
            content.update(courseware_context)
186
        return utils.JsonResponse({
187
            #'html': html,
188
            'content': content,
Rocky Duan committed
189 190
            'annotated_content_info': annotated_content_info,
        })
191

Rocky Duan committed
192
    else:
Arjun Singh committed
193
        category_map = utils.get_discussion_category_map(course)
Rocky Duan committed
194

Arjun Singh committed
195 196
        try:
            threads, query_params = get_threads(request, course_id)
197
            threads.append(thread.to_dict())
Arjun Singh committed
198
        except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
199
            log.error("Error loading single thread.")
Arjun Singh committed
200
            raise Http404
Rocky Duan committed
201

202 203 204 205 206
        course = get_course_with_access(request.user, course_id, 'load')

        for thread in threads:
            courseware_context = get_courseware_context(thread, course)
            if courseware_context:
207
                thread.update(courseware_context)
208

209 210
        threads = [utils.safe_content(thread) for thread in threads]

211 212 213 214 215
        #recent_active_threads = cc.search_recent_active_threads(
        #    course_id,
        #    recursive=False,
        #    query_params={'follower_id': request.user.id},
        #)
216

217 218 219
        #trending_tags = cc.search_trending_tags(
        #    course_id,
        #)
220

Rocky Duan committed
221
        
222
        annotated_content_info = utils.get_metadata_for_threads(course_id, threads, request.user, user_info)
223

Rocky Duan committed
224
        context = {
225
            'discussion_id': discussion_id,
Rocky Duan committed
226
            'csrf': csrf(request)['csrf_token'],
227
            'init': '', #TODO: What is this?
228
            'user_info': saxutils.escape(json.dumps(user_info),escapedict),
229
            'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
Rocky Duan committed
230
            'course': course,
231 232
            #'recent_active_threads': recent_active_threads,
            #'trending_tags': trending_tags,
233
            'course_id': course.id, #TODO: Why pass both course and course.id to template?
234
            'thread_id': thread_id,
235
            'threads': saxutils.escape(json.dumps(threads), escapedict),
Arjun Singh committed
236
            'category_map': category_map,
237
            'roles': saxutils.escape(json.dumps(utils.get_role_ids(course_id)), escapedict),
238
            'thread_pages': query_params['num_pages'],
Rocky Duan committed
239 240
        }

241
        return render_to_response('discussion/single_thread.html', context)
242

243
@login_required
244
def user_profile(request, course_id, user_id):
245
    #TODO: Allow sorting?
246
    course = get_course_with_access(request.user, course_id, 'load')
247 248
    try:
        profiled_user = cc.User(id=user_id, course_id=course_id)
249

250 251
        query_params = {
            'page': request.GET.get('page', 1),
252 253 254
            'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities
            }

255
        threads, page, num_pages = profiled_user.active_threads(query_params)
256 257
        query_params['page'] = page
        query_params['num_pages'] = num_pages
258 259 260
        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)
261 262 263 264

        if request.is_ajax():
            return utils.JsonResponse({
                'discussion_data': map(utils.safe_content, threads),
265 266 267
                'page': query_params['page'],
                'num_pages': query_params['num_pages'],
                'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict),
268 269
            })
        else:
270 271


272 273 274 275 276
            context = {
                'course': course,
                'user': request.user,
                'django_user': User.objects.get(id=user_id),
                'profiled_user': profiled_user.to_dict(),
277 278 279 280
                '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,
281 282 283 284 285
            }

            return render_to_response('discussion/user_profile.html', context)
    except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
        raise Http404
286 287


288
def followed_threads(request, course_id, user_id):
289 290 291 292 293 294 295
    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
296 297
            'sort_key': request.GET.get('sort_key', 'date'),
            'sort_order': request.GET.get('sort_order', 'desc'),
298
        }
299

300 301 302 303 304 305 306 307 308 309
        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),
310 311
                'page': query_params['page'],
                'num_pages': query_params['num_pages'],
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
                })
        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