<commit_before>"""
Views handling read (GET) requests for the Discussion tab and inline discussions.
"""

from functools import wraps
import json
import logging
import xml.sax.saxutils as saxutils

from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.http import Http404, HttpResponseBadRequest
from django.utils.translation import ugettext_noop
from django.views.decorators.http import require_GET
import newrelic.agent

from edxmako.shortcuts import render_to_response
from courseware.courses import get_course_with_access
from courseware.tabs import EnrolledTab
from openedx.core.djangoapps.course_groups.cohorts import (
    is_course_cohorted,
    get_cohort_id,
    get_course_cohorts,
    is_commentable_cohorted,
    get_cohorted_threads_privacy,
    get_cohorted_commentables,
    get_cohort_by_id
)
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from courseware.access import has_access
from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx

from django_comment_client.permissions import has_permission
from django_comment_client.utils import (
    merge_dict,
    extract,
    strip_none,
    add_courseware_context,
    get_group_id_for_comments_service
)

from django_comment_client.utils import (merge_dict, extract, strip_none, add_courseware_context, add_thread_group_name)
import django_comment_client.utils as utils
import lms.lib.comment_client as cc

from opaque_keys.edx.keys import CourseKey

THREADS_PER_PAGE = 20
INLINE_THREADS_PER_PAGE = 20
PAGES_NEARBY_DELTA = 2
log = logging.getLogger("edx.discussions")


class DiscussionTab(EnrolledTab):
    """
    A tab for the cs_comments_service forums.
    """

    type = 'discussion'
    title = ugettext_noop('Discussion')
    priority = None
    view_name = 'django_comment_client.forum.views.forum_form_discussion'
    is_hideable = settings.FEATURES.get('ALLOW_HIDING_DISCUSSION_TAB', False)
    is_default = False

    @classmethod
    def is_enabled(cls, course, user=None):
        if not super(DiscussionTab, cls).is_enabled(course, user):
            return False

        if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
            if get_current_ccx(course.id):
                return False
        return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')


def _attr_safe_json(obj):
    """
    return a JSON string for obj which is safe to embed as the value of an attribute in a DOM node
    """
    return saxutils.escape(json.dumps(obj), {'"': '&quot;'})


@newrelic.agent.function_trace()
def make_course_settings(course, user):
    """
    Generate a JSON-serializable model for course settings, which will be used to initialize a
    DiscussionCourseSettings object on the client.
    """

    obj = {
        'is_cohorted': is_course_cohorted(course.id),
        'allow_anonymous': course.allow_anonymous,
        'allow_anonymous_to_peers': course.allow_anonymous_to_peers,
        'cohorts': [{"id": str(g.id), "name": g.name} for g in get_course_cohorts(course)],
        'category_map': utils.get_discussion_category_map(course, user)
    }

    return obj


@newrelic.agent.function_trace()
def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
    """
    This may raise an appropriate subclass of cc.utils.CommentClientError
    if something goes wrong, or ValueError if the group_id is invalid.
    """
    default_query_params = {
        'page': 1,
        'per_page': per_page,
        'sort_key': 'date',
        'sort_order': 'desc',
        'text': '',
        'course_id': unicode(course.id),
        'commentable_id': discussion_id,
        'user_id': request.user.id,
        'group_id': get_group_id_for_comments_service(request, course.id, discussion_id),  # may raise ValueError
    }

    # If provided with a discussion id, filter by discussion id in the
    # comments_service.
    if discussion_id is not None:
        default_query_params['commentable_id'] = discussion_id

    if not request.GET.get('sort_key'):
        # If the user did not select a sort key, use their last used sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.retrieve()
        # 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'] = cc_user.get('default_sort_key') or default_query_params['sort_key']
    else:
        # If the user clicked a sort key, update their default sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.default_sort_key = request.GET.get('sort_key')
        cc_user.save()

    #there are 2 dimensions to consider when executing a search with respect to group id
    #is user a moderator
    #did the user request a group

    #if the user requested a group explicitly, give them that group, otherwise, if mod, show all, else if student, use cohort

    group_id = request.GET.get('group_id')

    if group_id == "all":
        group_id = None

    if not group_id:
        if not has_permission(request.user, "see_all_cohorts", course.id):
            group_id = get_cohort_id(request.user, course.id)
            if not group_id and get_cohorted_threads_privacy(course.id) == 'cohort-only':
                default_query_params['exclude_groups'] = True

    if group_id:
        default_query_params["group_id"] = group_id

    #so by default, a moderator sees all items, and a student sees his cohort

    query_params = merge_dict(
        default_query_params,
        strip_none(
            extract(
                request.GET,
                [
                    'page',
                    'sort_key',
                    'sort_order',
                    'text',
                    'commentable_ids',
                    'flagged',
                    'unread',
                    'unanswered',
                ]
            )
        )
    )

    threads, page, num_pages, corrected_text = cc.Thread.search(query_params)
    threads = _set_group_names(course.id, threads, discussion_id=discussion_id)

    query_params['page'] = page
    query_params['num_pages'] = num_pages
    query_params['corrected_text'] = corrected_text

    return threads, query_params

def _set_group_names(course_id, threads):
    """ Adds group name if the thread has a group id"""

    for thread in threads:
        if thread.get('group_id'):
            thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name
            thread['group_string'] = "This post visible only to Group %s." % (thread['group_name'])
        else:<commit_msg>Better handling of group IDs in thread queries.<commit_after>"""
Views handling read (GET) requests for the Discussion tab and inline discussions.
"""

from functools import wraps
import json
import logging
import xml.sax.saxutils as saxutils

from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.http import Http404, HttpResponseBadRequest
from django.utils.translation import ugettext_noop
from django.views.decorators.http import require_GET
import newrelic.agent

from edxmako.shortcuts import render_to_response
from courseware.courses import get_course_with_access
from courseware.tabs import EnrolledTab
from openedx.core.djangoapps.course_groups.cohorts import (
    is_course_cohorted,
    get_cohort_id,
    get_course_cohorts,
    is_commentable_cohorted,
    get_cohorted_threads_privacy,
    get_cohorted_commentables,
    get_cohort_by_id
)
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from courseware.access import has_access
from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx

from django_comment_client.permissions import has_permission
from django_comment_client.utils import (
    merge_dict,
    extract,
    strip_none,
    add_courseware_context,
    get_group_id_for_comments_service
)

from django_comment_client.utils import (merge_dict, extract, strip_none, add_courseware_context, add_thread_group_name)
import django_comment_client.utils as utils
import lms.lib.comment_client as cc

from opaque_keys.edx.keys import CourseKey

THREADS_PER_PAGE = 20
INLINE_THREADS_PER_PAGE = 20
PAGES_NEARBY_DELTA = 2
log = logging.getLogger("edx.discussions")


class DiscussionTab(EnrolledTab):
    """
    A tab for the cs_comments_service forums.
    """

    type = 'discussion'
    title = ugettext_noop('Discussion')
    priority = None
    view_name = 'django_comment_client.forum.views.forum_form_discussion'
    is_hideable = settings.FEATURES.get('ALLOW_HIDING_DISCUSSION_TAB', False)
    is_default = False

    @classmethod
    def is_enabled(cls, course, user=None):
        if not super(DiscussionTab, cls).is_enabled(course, user):
            return False

        if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
            if get_current_ccx(course.id):
                return False
        return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE')


def _attr_safe_json(obj):
    """
    return a JSON string for obj which is safe to embed as the value of an attribute in a DOM node
    """
    return saxutils.escape(json.dumps(obj), {'"': '&quot;'})


@newrelic.agent.function_trace()
def make_course_settings(course, user):
    """
    Generate a JSON-serializable model for course settings, which will be used to initialize a
    DiscussionCourseSettings object on the client.
    """

    obj = {
        'is_cohorted': is_course_cohorted(course.id),
        'allow_anonymous': course.allow_anonymous,
        'allow_anonymous_to_peers': course.allow_anonymous_to_peers,
        'cohorts': [{"id": str(g.id), "name": g.name} for g in get_course_cohorts(course)],
        'category_map': utils.get_discussion_category_map(course, user)
    }

    return obj


@newrelic.agent.function_trace()
def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
    """
    This may raise an appropriate subclass of cc.utils.CommentClientError
    if something goes wrong. It may also raise ValueError if the group_id is invalid.
    """
    default_query_params = {
        'page': 1,
        'per_page': per_page,
        'sort_key': 'date',
        'sort_order': 'desc',
        'text': '',
        'course_id': unicode(course.id),
        'commentable_id': discussion_id,
        'user_id': request.user.id,
        'group_id': get_group_id_for_comments_service(request, course.id, discussion_id),  # may raise ValueError
    }

    # If provided with a discussion id, filter by discussion id in the
    # comments_service.
    if discussion_id is not None:
        default_query_params['commentable_id'] = discussion_id

    if not request.GET.get('sort_key'):
        # If the user did not select a sort key, use their last used sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.retrieve()
        # 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'] = cc_user.get('default_sort_key') or default_query_params['sort_key']
    else:
        # If the user clicked a sort key, update their default sort key
        cc_user = cc.User.from_django_user(request.user)
        cc_user.default_sort_key = request.GET.get('sort_key')
        cc_user.save()

    #there are 2 dimensions to consider when executing a search with respect to group id
    #is user a moderator
    #did the user request a group

    #if the user requested a group explicitly, give them that group, otherwise, if mod, show all, else if student, use cohort

    group_id = request.GET.get('group_id')
    is_cohorted = is_commentable_cohorted(course.id, discussion_id)

    if group_id in ("all", "None"):
        group_id = None


    if not has_permission(request.user, "see_all_cohorts", course.id):
        group_id = get_cohort_id(request.user, course.id)
        if not group_id and get_cohorted_threads_privacy(course.id) == 'cohort-only':
            default_query_params['exclude_groups'] = True

    if group_id:
        group_id = int(group_id)
        try:
            CourseUserGroup.objects.get(course_id=course.id, id=group_id)
        except CourseUserGroup.DoesNotExist:
            if not is_cohorted:
                group_id = None
            else:
                raise ValueError("Invalid Group ID")
        default_query_params["group_id"] = group_id

    #so by default, a moderator sees all items, and a student sees his cohort

    query_params = merge_dict(
        default_query_params,
        strip_none(
            extract(
                request.GET,
                [
                    'page',
                    'sort_key',
                    'sort_order',
                    'text',
                    'commentable_ids',
                    'flagged',
                    'unread',
                    'unanswered',
                ]
            )
        )
    )

    if not is_cohorted:
        query_params.pop('group_id', None)

    threads, page, num_pages, corrected_text = cc.Thread.search(query_params)
    threads = _set_group_names(course.id, threads)

    query_params['page'] = page
    query_params['num_pages'] = num_pages
    query_params['corrected_text'] = corrected_text

    return threads, query_params

def _set_group_names(course_id, threads):
    """ Adds group name if the thread has a group id"""

    for thread in threads:
        if thread.get('group_id'):
            thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name
            thread['group_string'] = "This post visible only to Group %s." % (thread['group_name'])
        else:
{'fraction_non_alphanumeric': 0.08267158252977878, 'fraction_numerical': 0.0014180374361883153, 'mean_word_length': 3.1904337492572785, 'pattern_counts': {'":': 5, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2269251', 'n_tokens_mistral': 4331, 'n_tokens_neox': 4134, 'n_words': 1100}