models.py 7.36 KB
Newer Older
1
import json
2 3
import logging

4
from config_models.models import ConfigurationModel
5
from django.conf import settings
Mike Chen committed
6
from django.contrib.auth.models import User
7
from django.db import models
8
from django.db.models.signals import post_save
9
from django.dispatch import receiver
10
from django.utils.translation import ugettext_noop
11

12
from openedx.core.djangoapps.xmodule_django.models import CourseKeyField, NoneToEmptyManager
13
from student.models import CourseEnrollment
14
from xmodule.modulestore.django import modulestore
15
from xmodule.modulestore.exceptions import ItemNotFoundError
Mike Chen committed
16

17 18
FORUM_ROLE_ADMINISTRATOR = ugettext_noop('Administrator')
FORUM_ROLE_MODERATOR = ugettext_noop('Moderator')
19
FORUM_ROLE_GROUP_MODERATOR = ugettext_noop('Group Moderator')
20 21
FORUM_ROLE_COMMUNITY_TA = ugettext_noop('Community TA')
FORUM_ROLE_STUDENT = ugettext_noop('Student')
Brian Wilson committed
22

23 24

@receiver(post_save, sender=CourseEnrollment)
25 26 27 28
def assign_default_role_on_enrollment(sender, instance, **kwargs):
    """
    Assign forum default role 'Student'
    """
29 30 31 32 33 34 35 36 37 38 39 40 41
    # The code below would remove all forum Roles from a user when they unenroll
    # from a course. Concerns were raised that it should apply only to students,
    # or that even the history of student roles is important for research
    # purposes. Since this was new functionality being added in this release,
    # I'm just going to comment it out for now and let the forums team deal with
    # implementing the right behavior.
    #
    # # We've unenrolled the student, so remove all roles for this course
    # if not instance.is_active:
    #     course_roles = list(Role.objects.filter(course_id=instance.course_id))
    #     instance.user.roles.remove(*course_roles)
    #     return

42
    # We've enrolled the student, so make sure they have the Student role
43 44 45 46 47 48 49
    assign_default_role(instance.course_id, instance.user)


def assign_default_role(course_id, user):
    """
    Assign forum default role 'Student' to user
    """
50 51 52 53 54 55 56 57
    assign_role(course_id, user, FORUM_ROLE_STUDENT)


def assign_role(course_id, user, rolename):
    """
    Assign forum role `rolename` to user
    """
    role, __ = Role.objects.get_or_create(course_id=course_id, name=rolename)
58
    user.roles.add(role)
59 60


Mike Chen committed
61
class Role(models.Model):
62 63 64

    objects = NoneToEmptyManager()

65
    name = models.CharField(max_length=30, null=False, blank=False)
Mike Chen committed
66
    users = models.ManyToManyField(User, related_name="roles")
67
    course_id = CourseKeyField(max_length=255, blank=True, db_index=True)
Mike Chen committed
68

69
    class Meta(object):
70
        # use existing table that was originally created from django_comment_client app
71 72
        db_table = 'django_comment_client_role'

Mike Chen committed
73
    def __unicode__(self):
74 75
        # pylint: disable=no-member
        return self.name + " for " + (self.course_id.to_deprecated_string() if self.course_id else "all courses")
Mike Chen committed
76

David Baumgold committed
77 78 79
    # TODO the name of this method is a little bit confusing,
    # since it's one-off and doesn't handle inheritance later
    def inherit_permissions(self, role):
80 81 82 83
        """
        Make this role inherit permissions from the given role.
        Permissions are only added, not removed. Does not handle inheritance.
        """
84
        if role.course_id and role.course_id != self.course_id:
85 86 87 88 89
            logging.warning(
                "%s cannot inherit permissions from %s due to course_id inconsistency",
                self,
                role,
            )
90 91
        for per in role.permissions.all():
            self.add_permission(per)
Mike Chen committed
92

93 94
    def add_permission(self, permission):
        self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
Mike Chen committed
95

96
    def has_permission(self, permission):
97
        """Returns True if this role has the given permission, False otherwise."""
98 99 100
        course = modulestore().get_course(self.course_id)
        if course is None:
            raise ItemNotFoundError(self.course_id)
101
        if permission_blacked_out(course, {self.name}, permission):
Brian Wilson committed
102
            return False
Calen Pennington committed
103

104
        return self.permissions.filter(name=permission).exists()
Mike Chen committed
105 106 107 108 109 110


class Permission(models.Model):
    name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
    roles = models.ManyToManyField(Role, related_name="permissions")

111
    class Meta(object):
112
        # use existing table that was originally created from django_comment_client app
113 114
        db_table = 'django_comment_client_permission'

Mike Chen committed
115 116
    def __unicode__(self):
        return self.name
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148


def permission_blacked_out(course, role_names, permission_name):
    """Returns true if a user in course with the given roles would have permission_name blacked out.

    This will return true if it is a permission that the user might have normally had for the course, but does not have
    right this moment because we are in a discussion blackout period (as defined by the settings on the course module).
    Namely, they can still view, but they can't edit, update, or create anything. This only applies to students, as
    moderators of any kind still have posting privileges during discussion blackouts.
    """
    return (
        not course.forum_posts_allowed and
        role_names == {FORUM_ROLE_STUDENT} and
        any([permission_name.startswith(prefix) for prefix in ['edit', 'update', 'create']])
    )


def all_permissions_for_user_in_course(user, course_id):  # pylint: disable=invalid-name
    """Returns all the permissions the user has in the given course."""
    course = modulestore().get_course(course_id)
    if course is None:
        raise ItemNotFoundError(course_id)

    all_roles = {role.name for role in Role.objects.filter(users=user, course_id=course_id)}

    permissions = {
        permission.name
        for permission
        in Permission.objects.filter(roles__users=user, roles__course_id=course_id)
        if not permission_blacked_out(course, all_roles, permission.name)
    }
    return permissions
149 150 151 152 153


class ForumsConfig(ConfigurationModel):
    """Config for the connection to the cs_comments_service forums backend."""

154 155 156 157 158 159 160 161 162
    connection_timeout = models.FloatField(
        default=5.0,
        help_text="Seconds to wait when trying to connect to the comment service.",
    )

    @property
    def api_key(self):
        """The API key used to authenticate to the comments service."""
        return getattr(settings, "COMMENTS_SERVICE_KEY", None)
163 164 165 166

    def __unicode__(self):
        """Simple representation so the admin screen looks less ugly."""
        return u"ForumsConfig: timeout={}".format(self.connection_timeout)
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193


class CourseDiscussionSettings(models.Model):
    course_id = CourseKeyField(
        unique=True,
        max_length=255,
        db_index=True,
        help_text="Which course are these settings associated with?",
    )
    always_divide_inline_discussions = models.BooleanField(default=False)
    _divided_discussions = models.TextField(db_column='divided_discussions', null=True, blank=True)  # JSON list

    COHORT = 'cohort'
    ENROLLMENT_TRACK = 'enrollment_track'
    NONE = 'none'
    ASSIGNMENT_TYPE_CHOICES = ((NONE, 'None'), (COHORT, 'Cohort'), (ENROLLMENT_TRACK, 'Enrollment Track'))
    division_scheme = models.CharField(max_length=20, choices=ASSIGNMENT_TYPE_CHOICES, default=NONE)

    @property
    def divided_discussions(self):
        """Jsonify the divided_discussions"""
        return json.loads(self._divided_discussions)

    @divided_discussions.setter
    def divided_discussions(self, value):
        """Un-Jsonify the divided_discussions"""
        self._divided_discussions = json.dumps(value)