* Python code cleanup by the cleanup-python-code Jenkins job. This pull request was generated by the cleanup-python-code Jenkins job, which ran ``` cd lms/djangoapps/dashboard; find . -type f -name '*.py' | while read fname; do sed -i 's/ # lint-amnesty, pylint: disable=super-with-arguments//; s/ # lint-amnesty, pylint: disable=import-error, wrong-import-order//; s/ # lint-amnesty, pylint: disable=wrong-import-order//' "$fname"; done; find . -type f -name '*.py' | while read fname; do pyupgrade --exit-zero-even-if-changed --py3-plus --py36-plus --py38-plus "$fname"; done; isort --recursive . ``` The following packages were installed: `pyupgrade,isort` * feedback done Co-authored-by: Zulqarnain <muhammad.zulqarnain@arbisoft.com>
631 lines
22 KiB
Python
631 lines
22 KiB
Python
"""
|
|
Discussion API serializers
|
|
"""
|
|
|
|
|
|
from django.contrib.auth.models import User as DjangoUser # lint-amnesty, pylint: disable=imported-auth-user
|
|
from django.core.exceptions import ValidationError
|
|
from django.urls import reverse
|
|
from rest_framework import serializers
|
|
from six.moves.urllib.parse import urlencode, urlunparse
|
|
|
|
from common.djangoapps.student.models import get_user_by_username_or_email
|
|
from lms.djangoapps.discussion.django_comment_client.utils import (
|
|
course_discussion_division_enabled,
|
|
get_group_id_for_user,
|
|
get_group_name,
|
|
get_group_names_by_id,
|
|
is_comment_too_deep
|
|
)
|
|
from lms.djangoapps.discussion.rest_api.permissions import (
|
|
NON_UPDATABLE_COMMENT_FIELDS,
|
|
NON_UPDATABLE_THREAD_FIELDS,
|
|
get_editable_fields
|
|
)
|
|
from lms.djangoapps.discussion.rest_api.render import render_body
|
|
from lms.djangoapps.discussion.views import get_divided_discussions
|
|
from openedx.core.djangoapps.django_comment_common.comment_client.comment import Comment
|
|
from openedx.core.djangoapps.django_comment_common.comment_client.thread import Thread
|
|
from openedx.core.djangoapps.django_comment_common.comment_client.user import User as CommentClientUser
|
|
from openedx.core.djangoapps.django_comment_common.comment_client.utils import CommentClientRequestError
|
|
from openedx.core.djangoapps.django_comment_common.models import (
|
|
FORUM_ROLE_ADMINISTRATOR,
|
|
FORUM_ROLE_COMMUNITY_TA,
|
|
FORUM_ROLE_MODERATOR,
|
|
Role
|
|
)
|
|
from openedx.core.djangoapps.django_comment_common.utils import get_course_discussion_settings
|
|
|
|
|
|
def get_context(course, request, thread=None):
|
|
"""
|
|
Returns a context appropriate for use with ThreadSerializer or
|
|
(if thread is provided) CommentSerializer.
|
|
"""
|
|
# TODO: cache staff_user_ids and ta_user_ids if we need to improve perf
|
|
staff_user_ids = {
|
|
user.id
|
|
for role in Role.objects.filter(
|
|
name__in=[FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR],
|
|
course_id=course.id
|
|
)
|
|
for user in role.users.all()
|
|
}
|
|
ta_user_ids = {
|
|
user.id
|
|
for role in Role.objects.filter(name=FORUM_ROLE_COMMUNITY_TA, course_id=course.id)
|
|
for user in role.users.all()
|
|
}
|
|
requester = request.user
|
|
cc_requester = CommentClientUser.from_django_user(requester).retrieve()
|
|
cc_requester["course_id"] = course.id
|
|
course_discussion_settings = get_course_discussion_settings(course.id)
|
|
return {
|
|
"course": course,
|
|
"request": request,
|
|
"thread": thread,
|
|
"discussion_division_enabled": course_discussion_division_enabled(course_discussion_settings),
|
|
"group_ids_to_names": get_group_names_by_id(course_discussion_settings),
|
|
"is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids,
|
|
"staff_user_ids": staff_user_ids,
|
|
"ta_user_ids": ta_user_ids,
|
|
"cc_requester": cc_requester,
|
|
}
|
|
|
|
|
|
def validate_not_blank(value):
|
|
"""
|
|
Validate that a value is not an empty string or whitespace.
|
|
|
|
Raises: ValidationError
|
|
"""
|
|
if not value.strip():
|
|
raise ValidationError("This field may not be blank.")
|
|
|
|
|
|
class _ContentSerializer(serializers.Serializer):
|
|
# pylint: disable=abstract-method
|
|
"""
|
|
A base class for thread and comment serializers.
|
|
"""
|
|
id = serializers.CharField(read_only=True) # pylint: disable=invalid-name
|
|
author = serializers.SerializerMethodField()
|
|
author_label = serializers.SerializerMethodField()
|
|
created_at = serializers.CharField(read_only=True)
|
|
updated_at = serializers.CharField(read_only=True)
|
|
raw_body = serializers.CharField(source="body", validators=[validate_not_blank])
|
|
rendered_body = serializers.SerializerMethodField()
|
|
abuse_flagged = serializers.SerializerMethodField()
|
|
voted = serializers.SerializerMethodField()
|
|
vote_count = serializers.SerializerMethodField()
|
|
editable_fields = serializers.SerializerMethodField()
|
|
|
|
non_updatable_fields = set()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
for field in self.non_updatable_fields:
|
|
setattr(self, f"validate_{field}", self._validate_non_updatable)
|
|
|
|
def _validate_non_updatable(self, value):
|
|
"""Ensure that a field is not edited in an update operation."""
|
|
if self.instance:
|
|
raise ValidationError("This field is not allowed in an update.")
|
|
return value
|
|
|
|
def _is_user_privileged(self, user_id):
|
|
"""
|
|
Returns a boolean indicating whether the given user_id identifies a
|
|
privileged user.
|
|
"""
|
|
return user_id in self.context["staff_user_ids"] or user_id in self.context["ta_user_ids"]
|
|
|
|
def _is_anonymous(self, obj):
|
|
"""
|
|
Returns a boolean indicating whether the content should be anonymous to
|
|
the requester.
|
|
"""
|
|
return (
|
|
obj["anonymous"] or
|
|
obj["anonymous_to_peers"] and not self.context["is_requester_privileged"]
|
|
)
|
|
|
|
def get_author(self, obj):
|
|
"""
|
|
Returns the author's username, or None if the content is anonymous.
|
|
"""
|
|
return None if self._is_anonymous(obj) else obj["username"]
|
|
|
|
def _get_user_label(self, user_id):
|
|
"""
|
|
Returns the role label (i.e. "Staff" or "Community TA") for the user
|
|
with the given id.
|
|
"""
|
|
return (
|
|
"Staff" if user_id in self.context["staff_user_ids"] else
|
|
"Community TA" if user_id in self.context["ta_user_ids"] else
|
|
None
|
|
)
|
|
|
|
def get_author_label(self, obj):
|
|
"""
|
|
Returns the role label for the content author.
|
|
"""
|
|
if self._is_anonymous(obj) or obj["user_id"] is None:
|
|
return None
|
|
else:
|
|
user_id = int(obj["user_id"])
|
|
return self._get_user_label(user_id)
|
|
|
|
def get_rendered_body(self, obj):
|
|
"""
|
|
Returns the rendered body content.
|
|
"""
|
|
return render_body(obj["body"])
|
|
|
|
def get_abuse_flagged(self, obj):
|
|
"""
|
|
Returns a boolean indicating whether the requester has flagged the
|
|
content as abusive.
|
|
"""
|
|
return self.context["cc_requester"]["id"] in obj.get("abuse_flaggers", [])
|
|
|
|
def get_voted(self, obj):
|
|
"""
|
|
Returns a boolean indicating whether the requester has voted for the
|
|
content.
|
|
"""
|
|
return obj["id"] in self.context["cc_requester"]["upvoted_ids"]
|
|
|
|
def get_vote_count(self, obj):
|
|
"""
|
|
Returns the number of votes for the content.
|
|
"""
|
|
return obj.get("votes", {}).get("up_count", 0)
|
|
|
|
def get_editable_fields(self, obj):
|
|
"""
|
|
Return the list of the fields the requester can edit
|
|
"""
|
|
return sorted(get_editable_fields(obj, self.context))
|
|
|
|
|
|
class ThreadSerializer(_ContentSerializer):
|
|
"""
|
|
A serializer for thread data.
|
|
|
|
N.B. This should not be used with a comment_client Thread object that has
|
|
not had retrieve() called, because of the interaction between DRF's attempts
|
|
at introspection and Thread's __getattr__.
|
|
"""
|
|
course_id = serializers.CharField()
|
|
topic_id = serializers.CharField(source="commentable_id", validators=[validate_not_blank])
|
|
group_id = serializers.IntegerField(required=False, allow_null=True)
|
|
group_name = serializers.SerializerMethodField()
|
|
type = serializers.ChoiceField(
|
|
source="thread_type",
|
|
choices=[(val, val) for val in ["discussion", "question"]]
|
|
)
|
|
title = serializers.CharField(validators=[validate_not_blank])
|
|
pinned = serializers.SerializerMethodField(read_only=True)
|
|
closed = serializers.BooleanField(read_only=True)
|
|
following = serializers.SerializerMethodField()
|
|
comment_count = serializers.SerializerMethodField(read_only=True)
|
|
unread_comment_count = serializers.SerializerMethodField(read_only=True)
|
|
comment_list_url = serializers.SerializerMethodField()
|
|
endorsed_comment_list_url = serializers.SerializerMethodField()
|
|
non_endorsed_comment_list_url = serializers.SerializerMethodField()
|
|
read = serializers.BooleanField(required=False)
|
|
has_endorsed = serializers.BooleanField(source="endorsed", read_only=True)
|
|
response_count = serializers.IntegerField(source="resp_total", read_only=True, required=False)
|
|
|
|
non_updatable_fields = NON_UPDATABLE_THREAD_FIELDS
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Compensate for the fact that some threads in the comments service do
|
|
# not have the pinned field set
|
|
if self.instance and self.instance.get("pinned") is None:
|
|
self.instance["pinned"] = False
|
|
|
|
def get_pinned(self, obj):
|
|
"""
|
|
Compensate for the fact that some threads in the comments service do
|
|
not have the pinned field set.
|
|
"""
|
|
return bool(obj["pinned"])
|
|
|
|
def get_group_name(self, obj):
|
|
"""
|
|
Returns the name of the group identified by the thread's group_id.
|
|
"""
|
|
return self.context["group_ids_to_names"].get(obj["group_id"])
|
|
|
|
def get_following(self, obj):
|
|
"""
|
|
Returns a boolean indicating whether the requester is following the
|
|
thread.
|
|
"""
|
|
return obj["id"] in self.context["cc_requester"]["subscribed_thread_ids"]
|
|
|
|
def get_comment_list_url(self, obj, endorsed=None):
|
|
"""
|
|
Returns the URL to retrieve the thread's comments, optionally including
|
|
the endorsed query parameter.
|
|
"""
|
|
if (
|
|
(obj["thread_type"] == "question" and endorsed is None) or
|
|
(obj["thread_type"] == "discussion" and endorsed is not None)
|
|
):
|
|
return None
|
|
path = reverse("comment-list")
|
|
query_dict = {"thread_id": obj["id"]}
|
|
if endorsed is not None:
|
|
query_dict["endorsed"] = endorsed
|
|
return self.context["request"].build_absolute_uri(
|
|
urlunparse(("", "", path, "", urlencode(query_dict), ""))
|
|
)
|
|
|
|
def get_endorsed_comment_list_url(self, obj):
|
|
"""
|
|
Returns the URL to retrieve the thread's endorsed comments.
|
|
"""
|
|
return self.get_comment_list_url(obj, endorsed=True)
|
|
|
|
def get_non_endorsed_comment_list_url(self, obj):
|
|
"""
|
|
Returns the URL to retrieve the thread's non-endorsed comments.
|
|
"""
|
|
return self.get_comment_list_url(obj, endorsed=False)
|
|
|
|
def get_comment_count(self, obj):
|
|
"""
|
|
Increments comment count to include post and returns total count of
|
|
contributions (i.e. post + responses + comments) for the thread
|
|
"""
|
|
return obj["comments_count"] + 1
|
|
|
|
def get_unread_comment_count(self, obj):
|
|
"""
|
|
Returns the number of unread comments. If the thread has never been read,
|
|
this additionally includes 1 for the post itself, in addition to its responses and
|
|
comments.
|
|
"""
|
|
if not obj["read"] and obj["comments_count"] == obj["unread_comments_count"]:
|
|
return obj["unread_comments_count"] + 1
|
|
return obj["unread_comments_count"]
|
|
|
|
def create(self, validated_data):
|
|
thread = Thread(user_id=self.context["cc_requester"]["id"], **validated_data)
|
|
thread.save()
|
|
return thread
|
|
|
|
def update(self, instance, validated_data):
|
|
for key, val in validated_data.items():
|
|
instance[key] = val
|
|
instance.save()
|
|
return instance
|
|
|
|
|
|
class CommentSerializer(_ContentSerializer):
|
|
"""
|
|
A serializer for comment data.
|
|
|
|
N.B. This should not be used with a comment_client Comment object that has
|
|
not had retrieve() called, because of the interaction between DRF's attempts
|
|
at introspection and Comment's __getattr__.
|
|
"""
|
|
thread_id = serializers.CharField()
|
|
parent_id = serializers.CharField(required=False, allow_null=True)
|
|
endorsed = serializers.BooleanField(required=False)
|
|
endorsed_by = serializers.SerializerMethodField()
|
|
endorsed_by_label = serializers.SerializerMethodField()
|
|
endorsed_at = serializers.SerializerMethodField()
|
|
child_count = serializers.IntegerField(read_only=True)
|
|
children = serializers.SerializerMethodField(required=False)
|
|
|
|
non_updatable_fields = NON_UPDATABLE_COMMENT_FIELDS
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
remove_fields = kwargs.pop('remove_fields', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if remove_fields:
|
|
# for multiple fields in a list
|
|
for field_name in remove_fields:
|
|
self.fields.pop(field_name)
|
|
|
|
def get_endorsed_by(self, obj):
|
|
"""
|
|
Returns the username of the endorsing user, if the information is
|
|
available and would not identify the author of an anonymous thread.
|
|
"""
|
|
endorsement = obj.get("endorsement")
|
|
if endorsement:
|
|
endorser_id = int(endorsement["user_id"])
|
|
# Avoid revealing the identity of an anonymous non-staff question
|
|
# author who has endorsed a comment in the thread
|
|
if not (
|
|
self._is_anonymous(self.context["thread"]) and
|
|
not self._is_user_privileged(endorser_id)
|
|
):
|
|
return DjangoUser.objects.get(id=endorser_id).username
|
|
return None
|
|
|
|
def get_endorsed_by_label(self, obj):
|
|
"""
|
|
Returns the role label (i.e. "Staff" or "Community TA") for the
|
|
endorsing user
|
|
"""
|
|
endorsement = obj.get("endorsement")
|
|
if endorsement:
|
|
return self._get_user_label(int(endorsement["user_id"]))
|
|
else:
|
|
return None
|
|
|
|
def get_endorsed_at(self, obj):
|
|
"""
|
|
Returns the timestamp for the endorsement, if available.
|
|
"""
|
|
endorsement = obj.get("endorsement")
|
|
return endorsement["time"] if endorsement else None
|
|
|
|
def get_children(self, obj):
|
|
return [
|
|
CommentSerializer(child, context=self.context).data
|
|
for child in obj.get("children", [])
|
|
]
|
|
|
|
def to_representation(self, data):
|
|
# pylint: disable=arguments-differ
|
|
data = super().to_representation(data)
|
|
|
|
# Django Rest Framework v3 no longer includes None values
|
|
# in the representation. To maintain the previous behavior,
|
|
# we do this manually instead.
|
|
if 'parent_id' not in data:
|
|
data["parent_id"] = None
|
|
|
|
return data
|
|
|
|
def validate(self, attrs):
|
|
"""
|
|
Ensure that parent_id identifies a comment that is actually in the
|
|
thread identified by thread_id and does not violate the configured
|
|
maximum depth.
|
|
"""
|
|
parent = None
|
|
parent_id = attrs.get("parent_id")
|
|
if parent_id:
|
|
try:
|
|
parent = Comment(id=parent_id).retrieve()
|
|
except CommentClientRequestError:
|
|
pass
|
|
if not (parent and parent["thread_id"] == attrs["thread_id"]):
|
|
raise ValidationError(
|
|
"parent_id does not identify a comment in the thread identified by thread_id."
|
|
)
|
|
if is_comment_too_deep(parent):
|
|
raise ValidationError("Comment level is too deep.")
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
comment = Comment(
|
|
course_id=self.context["thread"]["course_id"],
|
|
user_id=self.context["cc_requester"]["id"],
|
|
**validated_data
|
|
)
|
|
comment.save()
|
|
return comment
|
|
|
|
def update(self, instance, validated_data):
|
|
for key, val in validated_data.items():
|
|
instance[key] = val
|
|
# TODO: The comments service doesn't populate the endorsement
|
|
# field on comment creation, so we only provide
|
|
# endorsement_user_id on update
|
|
if key == "endorsed":
|
|
instance["endorsement_user_id"] = self.context["cc_requester"]["id"]
|
|
|
|
instance.save()
|
|
return instance
|
|
|
|
|
|
class DiscussionTopicSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for DiscussionTopic
|
|
"""
|
|
id = serializers.CharField(read_only=True) # pylint: disable=invalid-name
|
|
name = serializers.CharField(read_only=True)
|
|
thread_list_url = serializers.CharField(read_only=True)
|
|
children = serializers.SerializerMethodField()
|
|
|
|
def get_children(self, obj):
|
|
"""
|
|
Returns a list of children of DiscussionTopicSerializer type
|
|
"""
|
|
if not obj.children:
|
|
return []
|
|
return [DiscussionTopicSerializer(child).data for child in obj.children]
|
|
|
|
def create(self, validated_data):
|
|
"""
|
|
Overriden create abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
def update(self, instance, validated_data):
|
|
"""
|
|
Overriden update abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class DiscussionSettingsSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for course discussion settings.
|
|
"""
|
|
divided_course_wide_discussions = serializers.ListField(
|
|
child=serializers.CharField(),
|
|
)
|
|
divided_inline_discussions = serializers.ListField(
|
|
child=serializers.CharField(),
|
|
)
|
|
always_divide_inline_discussions = serializers.BooleanField()
|
|
division_scheme = serializers.CharField()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.course = kwargs.pop('course')
|
|
self.discussion_settings = kwargs.pop('discussion_settings')
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def validate(self, attrs):
|
|
"""
|
|
Validate the fields in combination.
|
|
"""
|
|
if not any(field in attrs for field in self.fields):
|
|
raise ValidationError('Bad request')
|
|
|
|
settings_to_change = {}
|
|
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
|
|
self.course, self.discussion_settings
|
|
)
|
|
|
|
if any(item in attrs for item in ('divided_course_wide_discussions', 'divided_inline_discussions')):
|
|
divided_course_wide_discussions = attrs.get(
|
|
'divided_course_wide_discussions',
|
|
divided_course_wide_discussions
|
|
)
|
|
divided_inline_discussions = attrs.get('divided_inline_discussions', divided_inline_discussions)
|
|
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
|
|
|
|
for item in ('always_divide_inline_discussions', 'division_scheme'):
|
|
if item in attrs:
|
|
settings_to_change[item] = attrs[item]
|
|
attrs['settings_to_change'] = settings_to_change
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
"""
|
|
Overriden create abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
def update(self, instance, validated_data):
|
|
"""
|
|
Overriden update abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class DiscussionRolesSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for course discussion roles.
|
|
"""
|
|
|
|
ACTION_CHOICES = (
|
|
('allow', 'allow'),
|
|
('revoke', 'revoke')
|
|
)
|
|
action = serializers.ChoiceField(ACTION_CHOICES)
|
|
user_id = serializers.CharField()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.user = None
|
|
|
|
def validate_user_id(self, user_id): # lint-amnesty, pylint: disable=missing-function-docstring
|
|
try:
|
|
self.user = get_user_by_username_or_email(user_id)
|
|
return user_id
|
|
except DjangoUser.DoesNotExist:
|
|
raise ValidationError(f"'{user_id}' is not a valid student identifier") # lint-amnesty, pylint: disable=raise-missing-from
|
|
|
|
def validate(self, attrs):
|
|
"""Validate the data at an object level."""
|
|
|
|
# Store the user object to avoid fetching it again.
|
|
if hasattr(self, 'user'):
|
|
attrs['user'] = self.user
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
"""
|
|
Overriden create abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
def update(self, instance, validated_data):
|
|
"""
|
|
Overriden update abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class DiscussionRolesMemberSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for course discussion roles member data.
|
|
"""
|
|
username = serializers.CharField()
|
|
email = serializers.EmailField()
|
|
first_name = serializers.CharField()
|
|
last_name = serializers.CharField()
|
|
group_name = serializers.SerializerMethodField()
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.course_discussion_settings = self.context['course_discussion_settings']
|
|
|
|
def get_group_name(self, instance):
|
|
"""Return the group name of the user."""
|
|
group_id = get_group_id_for_user(instance, self.course_discussion_settings)
|
|
group_name = get_group_name(group_id, self.course_discussion_settings)
|
|
return group_name
|
|
|
|
def create(self, validated_data):
|
|
"""
|
|
Overriden create abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
def update(self, instance, validated_data):
|
|
"""
|
|
Overriden update abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class DiscussionRolesListSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for course discussion roles member list.
|
|
"""
|
|
course_id = serializers.CharField()
|
|
results = serializers.SerializerMethodField()
|
|
division_scheme = serializers.SerializerMethodField()
|
|
|
|
def get_results(self, obj):
|
|
"""Return the nested serializer data representing a list of member users."""
|
|
context = {
|
|
'course_id': obj['course_id'],
|
|
'course_discussion_settings': self.context['course_discussion_settings']
|
|
}
|
|
serializer = DiscussionRolesMemberSerializer(obj['users'], context=context, many=True)
|
|
return serializer.data
|
|
|
|
def get_division_scheme(self, obj): # pylint: disable=unused-argument
|
|
"""Return the division scheme for the course."""
|
|
return self.context['course_discussion_settings'].division_scheme
|
|
|
|
def create(self, validated_data):
|
|
"""
|
|
Overriden create abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
def update(self, instance, validated_data):
|
|
"""
|
|
Overriden update abstract method
|
|
"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|