views.py 12.8 KB
Newer Older
1 2 3 4 5 6
import time
import random
import os
import os.path
import logging
import urlparse
Rocky Duan committed
7
import functools
8

9
import comment_client as cc
Rocky Duan committed
10
import django_comment_client.utils as utils
Arjun Singh committed
11 12
import django_comment_client.settings as cc_settings

13 14

from django.core import exceptions
15 16
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
17 18 19
from django.views.decorators import csrf
from django.core.files.storage import get_storage_class
from django.utils.translation import ugettext as _
Rocky Duan committed
20
from django.contrib.auth.models import User
21

22
from mitxmako.shortcuts import render_to_response, render_to_string
23 24
from courseware.courses import get_course_with_access

Rocky Duan committed
25
from django_comment_client.utils import JsonResponse, JsonError, extract
26

27
from django_comment_client.permissions import check_permissions_by_view
Rocky Duan committed
28
from django_comment_client.models import Role
Mike Chen committed
29

30 31 32 33 34
def permitted(fn):
    @functools.wraps(fn)
    def wrapper(request, *args, **kwargs):
        def fetch_content():
            if "thread_id" in kwargs:
35
                content = cc.Thread.find(kwargs["thread_id"]).to_dict()
36
            elif "comment_id" in kwargs:
37
                content = cc.Comment.find(kwargs["comment_id"]).to_dict()
Mike Chen committed
38
            else:
39 40
                content = None
            return content
Mike Chen committed
41

42
        if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
43 44 45 46
            return fn(request, *args, **kwargs)
        else:
            return JsonError("unauthorized")
    return wrapper
Mike Chen committed
47

48 49 50 51 52 53
def ajax_content_response(request, course_id, content, template_name):
    context = {
        'course_id': course_id,
        'content': content,
    }
    html = render_to_string(template_name, context)
54
    user_info = cc.User.from_django_user(request.user).to_dict()
55
    annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
56 57 58 59 60 61
    return JsonResponse({
        'html': html,
        'content': content,
        'annotated_content_info': annotated_content_info,
    })

62
@require_POST
63
@login_required
64
@permitted
65
def create_thread(request, course_id, commentable_id):
66
    post = request.POST
67
    thread = cc.Thread(**extract(post, ['body', 'title', 'tags']))
Rocky Duan committed
68
    thread.update_attributes(**{
Rocky Duan committed
69 70 71 72 73
        'anonymous'      : post.get('anonymous', 'false').lower() == 'true',
        'commentable_id' : commentable_id,
        'course_id'      : course_id,
        'user_id'        : request.user.id,
    })
74 75
    thread.save()
    if post.get('auto_subscribe', 'false').lower() == 'true':
76 77
        user = cc.User.from_django_user(request.user)
        user.follow(thread)
78
    if request.is_ajax():
79
        return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html')
80
    else:
81
        return JsonResponse(thread.to_dict())
82 83

@require_POST
84
@login_required
85
@permitted
86
def update_thread(request, course_id, thread_id):
87
    thread = cc.Thread.find(thread_id)
88 89
    thread.update_attributes(**extract(request.POST, ['body', 'title', 'tags']))
    thread.save()
90
    if request.is_ajax():
91
        return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html')
92
    else:
93
        return JsonResponse(thread.to_dict())
94 95 96

def _create_comment(request, course_id, thread_id=None, parent_id=None):
    post = request.POST
97
    comment = cc.Comment(**extract(post, ['body']))
Rocky Duan committed
98
    comment.update_attributes(**{
Rocky Duan committed
99 100 101 102 103 104
        'anonymous' : post.get('anonymous', 'false').lower() == 'true',
        'user_id'   : request.user.id,
        'course_id' : course_id,
        'thread_id' : thread_id,
        'parent_id' : parent_id,
    })
105 106
    comment.save()
    if post.get('auto_subscribe', 'false').lower() == 'true':
107 108
        user = cc.User.from_django_user(request.user)
        user.follow(comment.thread)
109
    if request.is_ajax():
110
        return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html')
111
    else:
112
        return JsonResponse(comment.to_dict())
113 114

@require_POST
115
@login_required
116
@permitted
117
def create_comment(request, course_id, thread_id):
Arjun Singh committed
118 119 120
    if cc_settings.MAX_COMMENT_DEPTH is not None:
        if cc_settings.MAX_COMMENT_DEPTH < 0:
            return JsonError("Comment level too deep")
121
    return _create_comment(request, course_id, thread_id=thread_id)
122 123

@require_POST
124
@login_required
125
@permitted
126
def delete_thread(request, course_id, thread_id):
127
    thread = cc.Thread.find(thread_id)
128
    thread.delete()
129
    return JsonResponse(thread.to_dict())
130 131

@require_POST
132
@login_required
133
@permitted
134
def update_comment(request, course_id, comment_id):
135
    comment = cc.Comment.find(comment_id)
136 137
    comment.update_attributes(**extract(request.POST, ['body']))
    comment.save()
138
    if request.is_ajax():
139
        return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html')
140
    else:
141
        return JsonResponse(comment.to_dict()),
142 143

@require_POST
144
@login_required
145
@permitted
146
def endorse_comment(request, course_id, comment_id):
147
    comment = cc.Comment.find(comment_id)
148 149
    comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true'
    comment.save()
150
    return JsonResponse(comment.to_dict())
151 152

@require_POST
153
@login_required
154
@permitted
Mike Chen committed
155
def openclose_thread(request, course_id, thread_id):
Rocky Duan committed
156 157 158 159
    thread = cc.Thread.find(thread_id)
    thread.closed = request.POST.get('closed', 'false').lower() == 'true'
    thread.save()
    return JsonResponse(thread.to_dict())
Mike Chen committed
160 161 162

@require_POST
@login_required
163
@permitted
164
def create_sub_comment(request, course_id, comment_id):
Arjun Singh committed
165 166 167
    if cc_settings.MAX_COMMENT_DEPTH is not None:
        if cc_settings.MAX_COMMENT_DEPTH <= cc.Comment.find(comment_id).depth:
            return JsonError("Comment level too deep")
168
    return _create_comment(request, course_id, parent_id=comment_id)
169 170

@require_POST
171
@login_required
172
@permitted
173
def delete_comment(request, course_id, comment_id):
174
    comment = cc.Comment.find(comment_id)
175
    comment.delete()
176
    return JsonResponse(comment.to_dict())
177 178

@require_POST
179
@login_required
180
@permitted
181
def vote_for_comment(request, course_id, comment_id, value):
182 183
    user = cc.User.from_django_user(request.user)
    comment = cc.Comment.find(comment_id)
184
    user.vote(comment, value)
185
    return JsonResponse(comment.to_dict())
186 187

@require_POST
188
@login_required
189
@permitted
Rocky Duan committed
190
def undo_vote_for_comment(request, course_id, comment_id):
191 192
    user = cc.User.from_django_user(request.user)
    comment = cc.Comment.find(comment_id)
193
    user.unvote(comment)
194
    return JsonResponse(comment.to_dict())
Rocky Duan committed
195 196

@require_POST
197
@login_required
198
@permitted
199
def vote_for_thread(request, course_id, thread_id, value):
200 201
    user = cc.User.from_django_user(request.user)
    thread = cc.Thread.find(thread_id)
202
    user.vote(thread, value)
203
    return JsonResponse(thread.to_dict())
204

205
@require_POST
206
@login_required
207
@permitted
Rocky Duan committed
208
def undo_vote_for_thread(request, course_id, thread_id):
209 210
    user = cc.User.from_django_user(request.user)
    thread = cc.Thread.find(thread_id)
211
    user.unvote(thread)
212
    return JsonResponse(thread.to_dict())
213
    
Rocky Duan committed
214 215

@require_POST
216
@login_required
217
@permitted
Rocky Duan committed
218
def follow_thread(request, course_id, thread_id):
219 220
    user = cc.User.from_django_user(request.user)
    thread = cc.Thread.find(thread_id)
221 222
    user.follow(thread)
    return JsonResponse({})
223 224

@require_POST
225
@login_required
226
@permitted
Rocky Duan committed
227
def follow_commentable(request, course_id, commentable_id):
228 229
    user = cc.User.from_django_user(request.user)
    commentable = cc.Commentable.find(commentable_id)
230 231
    user.follow(commentable)
    return JsonResponse({})
232 233

@require_POST
234
@login_required
235
@permitted
Rocky Duan committed
236
def follow_user(request, course_id, followed_user_id):
237 238
    user = cc.User.from_django_user(request.user)
    followed_user = cc.User.find(followed_user_id)
239 240
    user.follow(followed_user)
    return JsonResponse({})
241

242
@require_POST
243
@login_required
244
@permitted
Rocky Duan committed
245
def unfollow_thread(request, course_id, thread_id):
246 247
    user = cc.User.from_django_user(request.user)
    thread = cc.Thread.find(thread_id)
248 249
    user.unfollow(thread)
    return JsonResponse({})
250 251

@require_POST
252
@login_required
253
@permitted
Rocky Duan committed
254
def unfollow_commentable(request, course_id, commentable_id):
255 256
    user = cc.User.from_django_user(request.user)
    commentable = cc.Commentable.find(commentable_id)
257 258
    user.unfollow(commentable)
    return JsonResponse({})
259 260

@require_POST
261
@login_required
262
@permitted
Rocky Duan committed
263
def unfollow_user(request, course_id, followed_user_id):
264 265
    user = cc.User.from_django_user(request.user)
    followed_user = cc.User.find(followed_user_id)
266 267
    user.unfollow(followed_user)
    return JsonResponse({})
268

Rocky Duan committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
@require_POST
@login_required
@permitted
def update_moderator_status(request, course_id, user_id):
    is_moderator = request.POST.get('is_moderator', '').lower()
    if is_moderator not in ["true", "false"]:
        return JsonError("Must provide is_moderator as boolean value")
    is_moderator = is_moderator == "true"
    user = User.objects.get(id=user_id)
    role = Role.objects.get(course_id=course_id, name="Moderator")
    if is_moderator:
        user.roles.add(role)
    else:
        user.roles.remove(role)
    if request.is_ajax():
284
        course = get_course_with_access(request.user, course_id, 'load')
Rocky Duan committed
285 286 287
        discussion_user = cc.User(id=user_id, course_id=course_id)
        context = {
            'course': course, 
288
            'course_id': course_id,
Rocky Duan committed
289 290
            'user': request.user,
            'django_user': user,
Rocky Duan committed
291
            'profiled_user': discussion_user.to_dict(),
Rocky Duan committed
292 293 294 295 296 297 298
        }
        return JsonResponse({
            'html': render_to_string('discussion/ajax_user_profile.html', context)
        })
    else:
        return JsonResponse({})

299
@require_GET
300
def search_similar_threads(request, course_id, commentable_id):
301
    text = request.GET.get('text', None)
302
    if text:
303 304 305 306
        query_params = {
            'text': text,
            'commentable_id': commentable_id,
        }
307
        threads = cc.search_similar_threads(course_id, recursive=False, query_params=query_params)
308
    else:
309 310 311 312 313
        theads = []
    context = { 'threads': map(utils.extend_content, threads) }
    return JsonResponse({
        'html': render_to_string('discussion/_similar_posts.html', context)
    })
314

Rocky Duan committed
315 316 317 318 319
@require_GET
def tags_autocomplete(request, course_id):
    value = request.GET.get('q', None)
    results = []
    if value:
320
        results = cc.tags_autocomplete(value)
Rocky Duan committed
321 322
    return JsonResponse(results)

323
@require_POST
324 325
@login_required
@csrf.csrf_exempt
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
def upload(request, course_id):#ajax upload file to a question or answer 
    """view that handles file upload via Ajax
    """

    # check upload permission
    result = ''
    error = ''
    new_file_name = ''
    try:
        # TODO authorization
        #may raise exceptions.PermissionDenied 
        #if request.user.is_anonymous():
        #    msg = _('Sorry, anonymous users cannot upload files')
        #    raise exceptions.PermissionDenied(msg)

        #request.user.assert_can_upload_file()

        # check file type
        f = request.FILES['file-upload']
        file_extension = os.path.splitext(f.name)[1].lower()
Rocky Duan committed
346 347
        if not file_extension in cc_settings.ALLOWED_UPLOAD_FILE_TYPES:
            file_types = "', '".join(cc_settings.ALLOWED_UPLOAD_FILE_TYPES)
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            msg = _("allowed file types are '%(file_types)s'") % \
                    {'file_types': file_types}
            raise exceptions.PermissionDenied(msg)

        # generate new file name
        new_file_name = str(
                            time.time()
                        ).replace(
                            '.', 
                            str(random.randint(0,100000))
                        ) + file_extension

        file_storage = get_storage_class()()
        # use default storage to store file
        file_storage.save(new_file_name, f)
        # check file size
        # byte
        size = file_storage.size(new_file_name)
Rocky Duan committed
366
        if size > cc_settings.MAX_UPLOAD_FILE_SIZE:
367 368
            file_storage.delete(new_file_name)
            msg = _("maximum upload file size is %(file_size)sK") % \
Rocky Duan committed
369
                    {'file_size': cc_settings.MAX_UPLOAD_FILE_SIZE}
370 371 372 373 374
            raise exceptions.PermissionDenied(msg)

    except exceptions.PermissionDenied, e:
        error = unicode(e)
    except Exception, e:
Rocky Duan committed
375
        print e
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
        logging.critical(unicode(e))
        error = _('Error uploading file. Please contact the site administrator. Thank you.')

    if error == '':
        result = 'Good'
        file_url = file_storage.url(new_file_name)
        parsed_url = urlparse.urlparse(file_url)
        file_url = urlparse.urlunparse(
            urlparse.ParseResult(
                parsed_url.scheme, 
                parsed_url.netloc,
                parsed_url.path,
                '', '', ''
            )
        )
    else:
        result = ''
        file_url = ''

    return JsonResponse({
        'result': {
            'msg': result,
            'error': error,
            'file_url': file_url,
        }
    })