Discussion API

Discussion API

Background

The Mobile team is creating a new API to interact with discussion forums to support the addition of discussion features to the mobile apps. This API is specifically designed and scoped to support the mobile discussion implementation that we are undertaking this quarter, though our intention is for this API to be sufficiently general to support other use cases as well. In particular, we want the browser-based client to ultimately use this new API, though there is some functionality that it requires that are out of scope for this initial proposal. We plan to generally conform to the edX REST API Conventions.

Determining the course

In general, access control for discussions requires determining which course is being accessed. This can be done in three different ways: 1) the course_id as a path element (e.g. accessing a topic tree); 2) the course_id as a query parameter (e.g. accessing the thread list); or 3) in the content itself (e.g. accessing a specific thread or posting new content).

Topic definition

Unfortunately, the topic tree is not defined as a concept in itself but rather is constructed from settings of the discussion modules in the course. For the web front end, the hierarchy is formed from discussion modules by splitting the discussion_category setting ("Category" in Studio) on "/" for intermediate nodes in the tree and using the discussion_target setting ("Subcategory" in Studio) for leaf nodes. Non-courseware topics are also added alongside the topics from discussion modules; those are defined in the discussion_topics course setting ("Discussion Topic Mapping" in Studio Advanced Settings). In this API, the discussion_category setting is not split, so the hierarchy is presently limited to two levels. Also, courseware-specific topics (those defined by discussion modules) are returned separately from those defined in the discussion_topics setting. However, the data model is designed to accommodate an arbitrarily deep hierarchy to allow for future development of a more structured definition of such hierarchy.

Because the topic tree does not have a data model per se but is constructed in this way, its API is necessarily read-only. Another important consideration in the computation of the topic tree is that it respects content groups, release dates, and other access control on discussion modules.

Forum roles

At present, access control in the discussion feature is done via a role and permission framework that is designed to allow very granular granting of permissions. When a course is created in Studio or by the seed_permissions_roles django command, four roles are created: Adminstrator, Moderator, Community TA, and Student. Various appropriate permissions are assigned to those roles. There is no facility beyond manually changing database entries to modify the set of roles and permissions for a particular course. Thus, the designed granularity of the permission framework has, to our knowledge, never been needed or used. As a result, this proposal ignores permissions and grants access by role only. It should also be noted that the Moderator and Community TA roles differ only in name but have the same permissions. The Administrator role is assigned one additional permission called "manage_moderator", but forum role management is in done via the Instructor Dashboard, which is accessible to any course staff member, so the "manage_moderator" permission is never actually used. Thus, the Administrator, Moderator, and Community TA roles are interchangeable (for access control purposes) and referred to collectively below as "privileged roles."

Note that there is no direct representation of roles in this proposed API. Rather, more granular permissions are represented by listing editable fields for the thread and comment models. This is desirable because the client does not need to keep track of what each role means. (Yes, the author appreciates the irony of this statement given the above discussion of permissions on the server).

Groups

Groups are a feature that make certain threads only available to certain sets of users. Both users and threads can be assigned to groups. If a thread is assigned to a group, then only users who are also assigned to that group (and users with a privileged role) can view the thread. Users without a privileged role can only assign threads to their own groups, while users with a privileged role can assign a thread to any group.

Cohorts

Cohorts are an LMS feature that makes use of the discussion group functionality (and, currently, the only feature that makes use of this functionality). If cohorts are enabled for a course, then each user is assigned to exactly one cohort group. Each discussion topic can be cohorted or not. If a topic is cohorted, then new threads are assigned to the cohort of the author by default. A user with a privileged role can assign a new thread (or an existing thread) to any group or to be visible to everyone. If a topic is not cohorted, new threads are not assigned to a group.

Thread closure

A user with a privileged role can "close" a thread. When a thread is closed, only a user with a privileged role can modify the thread, modify any of the thread's comments, or add a new comment to the thread. Closing a thread does not affect its visibility.

Discussion blackouts

Course staff can configure blackout intervals for the course's discussions. During a blackout, all existing discussion content is accessible, but only a user with a privileged role can create or modify discussion content. This feature is typically used during an exam period to prevent students from discussing answers before the due date.

Disabling discussions

Some courses remove the discussion tab (or substitute another tab with the same name with a link to an external site). In courses where this has has been done, all endpoints in this API will be inaccessible (with all endpoints returning 404 errors). Given that it is possible for course authors to remove the discussion tab but still include discussion modules in the course, this will need to be revisited before moving the web front end to this API.

Resources

Course

Note: This representation of the course is read only.

Field

Description

Field

Description

id

String. The identifier of the course.

blackouts

List of Objects. A list of intervals during which users without a privileged role may not post. Each interval is an object containing "start" and "end" fields, where each value is an ISO 8601 String.

thread_list_url

String. The URL of the list of all threads in the course.

following_thread_list_url

String. The URL of the list of the user's followed threads in the course

topics_url

String. The URL of the topic listing for the course.

cohorts

List of Objects. A list of cohorts in the course (if the course is cohorted). Each cohort is an object containing a "group_name" string field and a "group_id" integer field.

Topic Tree

Note: This representation of topics is read only.

Field

Description

Field

Description

id

String. The identifier string of the topic (null if this topic is only an intermediate node that cannot contain its own threads).

name

String. The display name of the topic.

thread_list_url

String. The URL of the thread list for the topic. For nodes with children, this will be a query for all threads in any subtopic.

children

List of Topic Trees. Contains the subtrees of this topic. Can vary by user.

Course Topics

Note: This representation of topics is read only.

Field

Description

Field

Description

courseware_topics

List of Topic Trees. Contains the list of topic trees defined by discussion modules in the courseware.

non_courseware_topics

List of Topic Trees. Contains the list of topic trees defined by course settings outside the courseware.

Thread

Field

Description

Initializable by

Editable by

Field

Description

Initializable by

Editable by

id

String. The identifier string of the thread.

None

None

course_id

String. The identifier string of the thread's course.

Any role

None

topic_id

String. The identifier of the thread's topic.

Any role

Author or privileged role

group_id

Integer. The numeric identifier of the thread's group (i.e. cohort) or null if not in a group.

Privileged role

Privileged role

group_name

String. The display name of the thread's group (i.e. cohort) or null if not in a group.

None

None

author

String. The username of the thread's author. Null if and only if the thread is anonymous or the thread is anonymous to students and the user does not have a privileged role. Can vary by user.

None

None

author_label

String. A string value to indicate that the author has a privileged role. Currently, this will be either "Staff" for Administrators and Moderators or "Community TA" for Community TAs.

None

None

created_at

ISO 8601 String. The timestamp of the thread's creation.

None

None

updated_at

ISO 8601 String. The timestamp of the thread's last modification. Note that this can include certain operations that are not visible to the present user, such as the thread being flagged by another user.

None

None

type

String. The type of post (either "question" or "discussion").

Any role

Author or privileged role

title

String. The title of the thread.

Any role

Author or privileged role

raw_body

String. The raw content of the thread. This may contain Markdown, HTML, and MathJax markup.

Any role

Author or privileged role

rendered_body

String. The content of the thread, rendered in HTML (Markdown rendering and HTML stripping applied).

None

None

has_endorsed

Boolean. Whether the thread has any endorsed comments.

None

None

pinned

Boolean. Whether the thread is pinned (to appear at the beginning of a thread list regardless of sort).

Privileged role

Privileged role

closed

Boolean. Whether new edits and responses are allowed.

Privileged role

Privileged role

following

Boolean. Whether the user is following the thread. Can vary by user.

Any role

Any role

abuse_flagged

Boolean. Whether the user has flagged the thread as abusive. Can vary by user.

None

Any role

voted

Boolean. Whether the user has voted for the thread. Can vary by user.

None

Any role

vote_count

Integer. The total number of votes for the thread.

None

None

read

Boolean. Whether the user has read the thread. Can vary by user.

None

None

comment_count

Integer. The total count of contributions (post + responses + comments) for the thread.

None

None

unread_comment_count

Integer. The total count of unread contributions (post + responses + comments) for the thread. Can vary by user.

None

None

comment_list_url

String. The URL of the comment list for the thread. In Q4 implementation, this will be null for question threads.

None

None

endorsed_comment_list_url

String. The URL of the endorsed comment list for the thread. In Q4 implementation, this will be null for discussion threads.

None

None

non_endorsed_comment_list_url

String. The URL of the non-endorsed comment list for the thread. In Q4 implementation, this will be null for discussion threads.

None

None

editable_fields

List of Strings. The names of fields that can be edited by the user.

None

None

response_count

Integer. The total number of direct responses for the thread.

None

None

Comment

Field

Description

Initializable by

Editable by

Field

Description

Initializable by

Editable by

id

String. The identifier string of the comment.

None

None

parent_id

String. The identifier string of the comment's parent comment (null for first-level thread responses).

Any role

None

thread_id

String. The identifier string of the comment's thread.

Any role

None

author

String. The username of the thread's author. If and only if anonymous is true or anonymous_to_students is true and the user does not have a privileged role and is not the author, this will be null. Can vary by user.

None

None

author_label

String. A string value to indicate that the author has a privileged role. Currently, this will be either "Staff" for Administrators and Moderators or "Community TA" for Community TAs.

None

None

created_at

ISO 8601 String. The timestamp of the comment's creation.

None

None

updated_at

ISO 8601 String. The timestamp of the comment's last modification. Note that this can include certain operations that are not visible to the present user, such as the thread being flagged by another user.

None

None

raw_body

String. The raw content of the comment. This may contain Markdown, HTML, and MathJax markup.

Any role

Author or privileged role

rendered_body

String. The content of the comment, rendered in HTML (Markdown rendering and HTML stripping applied).

None

None

endorsed

Boolean. Whether the comment has been endorsed by a privileged user or accepted as a correct answer by the thread author or a privileged user.

Thread author (if thread is a question) or privileged role

Thread author (if thread is a question) or privileged role

endorsed_by

String. The username of the user who endorsed the comment. Can be null if the information is unavailable or to avoid revealing the identity of the author of an anonymous thread (i.e. when a non-staff author of an anonymous thread endorses a response).

None

None

endorsed_by_label

String. A string value to indicate that the endorser has a privileged role. Currently, this will be either "Staff" for Administrators and Moderators or "Community TA" for Community TAs.

None

None

endorsed_at

ISO 8601 String. The timestamp of the endorsement or acceptance as a correct answer. Can be null if the information is unavailable.

None

None

abuse_flagged

Boolean. Whether the user has flagged the comment as abusive. Can vary by user.

None

Any role

voted

Boolean. Whether the user has voted for the comment. Can vary by user.

None

Any role

vote_count

Integer. The total number of votes for the comment.

None

None

children

List of Comments. The children of this comment.

None

None

editable_fields

List of Strings. The names of fields that can be edited by the user.

None

None

Endpoints

Note that all endpoints are accessible only if the user has been assigned a forum role for the course (which happens automatically upon enrollment in the course) and, except for the course endpoint, discussions have not been disabled for the course.

/api/discussion/v1/courses/{course_id}/

Method

Description

Access

Method

Description

Access

GET

Retrieve the discussion information for a course (the Course resource above). No parameters.

Any role

GET /api/discussion/v1/courses/course-v1:TestX+TestCourse+TestRun/
{ "id": "course-v1:TestX+TestCourse+TestRun", "discussions_enabled": true, "blackouts": [{"start": "2015-04-15T00:00:00Z", "end": "2015-04-22T00:00:00Z"}], "thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun", "following_thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&following=1", "flagged_thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&abuse_flagged=1", "topics_url": "https://openedx.example.com/api/discussion/v1/course_topics/course-v1:TestX+TestCourse+TestRun/", "cohorts": [{"group_id": 1, "group_name": "Cohort One"}, {"group_id": 2, "group_name": "Cohort Two"}] }

/api/discussion/v1/course_topics/{course_id}/

Method

Description

Access

Method

Description

Access

GET

Retrieve the topics for the course, respecting normal access control for the user. No parameters.

Any role

GET /api/discussion/v1/course-v1:TestX+TestCourse+TestRun
{ "courseware_topics": [ { "name": "Week 1", "thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&topic_id=7bbd1b43d28b49a0bf1ae852d3e957f1&topic_id=bf960351e62e4d4aa42e851292defc5f", "children": [ { "id": "7bbd1b43d28b49a0bf1ae852d3e957f1", "name": "Lecture 1", "thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&topic_id=7bbd1b43d28b49a0bf1ae852d3e957f1", "children": [] }, { "id": "bf960351e62e4d4aa42e851292defc5f", "name": "Lecture 2", "thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&topic_id=bf960351e62e4d4aa42e851292defc5f", "children": [] } ] } ], "non_courseware_topics": [ { "id": "df3e1598d8544e04a34dfcfa22313caf", "name": "General", "thread_list_url": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&topic_id=df3e1598d8544e04a34dfcfa22313caf", "children": [] } ] }

/api/discussion/v1/threads/

 

Method

Description

Access

Method

Description

Access

GET

Retrieve a list of threads. course_id must be specified. topic_id, following, and text_search are mutually exclusive. In addition to pagination information and results, the returned object may include a text_search_rewrite field indicating that the text search query was rewritten (which happens automatically if the search has no matching results).

Query Parameters:

  • course_id (String): Retrieve threads only within the course

  • topic_id (String): Retrieve thread only within the topic

  • following (Boolean, must be true): Retrieve only threads the user is following

  • view (String, must be unread, or unanswered): Retrieve only unread threads or unanswered question threads, respectively

  • text_search (String): Retrieve only threads matching the given text or with comments matching the given text. Note that threads containing comments that match the search string will be returned even though the matching children are not included in the result payload.

  • order_by (String, must be one of "last_activity_at", "comment_count", "vote_count"): Order threads by most recent activity, most descendant comments, or most votes, respectively.

  • order_direction (String, must be either "asc" or "desc": Used to determine the order direction of the threads returned.

  • page_size (Integer, must be positive): Number of threads per page, with a maximum value TBD

  • page (Integer, must be non-negative): Page number to retrieve

Any role; flagged parameter is only respected for privileged users

POST

Create a new thread. Parameters as per initializability noted in model. If the user does not have a privileged role, and the topic identified by topic_id is cohorted, the new thread's group_id will be automatically initialized to the id of the user's cohort. If ther user has a privileged role, the specified group_id (or lack thereof) will be respected.

Required Body Parameters:

  • course_id

  • topic_id

  • type

  • title

  • raw_body

Any role, subject to blackouts

Example:

GET /api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&text_search=exampl
{ "text_search_rewrite": "example", "count": 26, "next": "https://openedx.example.com/api/discussion/v1/threads/?course_id=course-v1:TestX+TestCourse+TestRun&page=2", "previous": null, "results": [ { "id": "51dfd4e38d9f4f788a0e65c0e9311a55", "course_id": "course-v1:TestX+TestCourse+TestRun", "topic_id": "df3e1598d8544e04a34dfcfa22313caf", "group_id": null, "group_name": null, "author": "example-user", "created_at": "2015-04-09T17:31:56Z", "modified_at": "2015-04-09T17:31:56Z", "type": "discussion", "title": "Example Thread Title", "raw_body": "**Example Thread Body**", "rendered_body": "<b>Example Thread Body</b>", "has_endorsed": false, "pinned": false, "closed": false, "flagged": false, "voted": false, "vote_count": 42, "comment_count": 3, "unread_comment_count": 1, "read": true, "comment_list_url": "https://openedx.example.com/api/discussion/v1/comments/?thread_id=51dfd4e38d9f4f788a0e65c0e9311a55", "endorsed_comment_list_url": null, "non_endorsed_comment_list_url": null, "editable_fields": ["flagged", "voted"] }, // ... ] }
POST /api/discussion/v1/threads/
// Request body   { "course_id": "course-v1:TestX+TestCourse+TestRun", "topic_id": "df3e1598d8544e04a34dfcfa22313caf", "type": "discussion", "title": "Example Thread Title", "raw_body": "**Example Thread Body**", }   // Response body   { "id": "51dfd4e38d9f4f788a0e65c0e9311a55", "course_id": "course-v1:TestX+TestCourse+TestRun", "topic_id": "df3e1598d8544e04a34dfcfa22313caf", "group_id": null, "group_name": null, "author": "example-user", "created_at": "2015-04-09T17:31:56Z", "modified_at": "2015-04-09T17:31:56Z", "type": "discussion", "title": "Example Thread Title", "raw_body": "**Example Thread Body**", "rendered_body": "<b>Example Thread Body</b>", "has_endorsed": false, "pinned": false, "closed": false, "flagged": false, "voted": false, "vote_count": 0, "comment_count": 0, "unread_comment_count": 0, "read": true, "comment_list_url": "https://openedx.example.com/api/discussion/v1/comments/?thread_id=51dfd4e38d9f4f788a0e65c0e9311a55", "endorsed_comment_list_url": null, "non_endorsed_comment_list_url": null, "editable_fields": ["type", "title", "raw_body", "flagged", "voted"], "response_count": 0 }

/api/discussion/v1/threads/{thread_id}/