Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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.

FieldDescription
idString. The identifier of the course.
blackoutsList 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_urlString. The URL of the list of all threads in the course.
following_thread_list_urlString. The URL of the list of the user's followed threads in the course
topics_urlString. The URL of the topic listing for the course.
cohortsList 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.

FieldDescription
idString. The identifier string of the topic (null if this topic is only an intermediate node that cannot contain its own threads).
nameString. The display name of the topic.
thread_list_urlString. The URL of the thread list for the topic. For nodes with children, this will be a query for all threads in any subtopic.
childrenList of Topic Trees. Contains the subtrees of this topic. Can vary by user.

Course Topics

Note: This representation of topics is read only.

FieldDescription
courseware_topicsList of Topic Trees. Contains the list of topic trees defined by discussion modules in the courseware.
non_courseware_topicsList of Topic Trees. Contains the list of topic trees defined by course settings outside the courseware.

Thread

FieldDescriptionInitializable byEditable by
idString. The identifier string of the thread.NoneNone
course_idString. The identifier string of the thread's course.Any roleNone
topic_idString. The identifier of the thread's topic.Any roleAuthor or privileged role
group_idInteger. The numeric identifier of the thread's group (i.e. cohort) or null if not in a group.Privileged rolePrivileged role
group_nameString. The display name of the thread's group (i.e. cohort) or null if not in a group.NoneNone
authorString. 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.NoneNone
author_labelString. 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.NoneNone
created_atISO 8601 String. The timestamp of the thread's creation.NoneNone
updated_atISO 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.NoneNone
typeString. The type of post (either "question" or "discussion").Any roleAuthor or privileged role
titleString. The title of the thread.Any roleAuthor or privileged role
raw_bodyString. The raw content of the thread. This may contain Markdown, HTML, and MathJax markup.Any roleAuthor or privileged role
rendered_bodyString. The content of the thread, rendered in HTML (Markdown rendering and HTML stripping applied).NoneNone
has_endorsedBoolean. Whether the thread has any endorsed comments.NoneNone
pinnedBoolean. Whether the thread is pinned (to appear at the beginning of a thread list regardless of sort).Privileged rolePrivileged role
closedBoolean. Whether new edits and responses are allowed.Privileged rolePrivileged role
followingBoolean. Whether the user is following the thread. Can vary by user.Any roleAny role
abuse_flaggedBoolean. Whether the user has flagged the thread as abusive. Can vary by user.NoneAny role
votedBoolean. Whether the user has voted for the thread. Can vary by user.NoneAny role
vote_countInteger. The total number of votes for the thread.NoneNone
readBoolean. Whether the user has read the thread. Can vary by user.NoneNone
comment_countInteger. The total count of contributions (post + responses + comments) for the thread.NoneNone
unread_comment_countInteger. The total count of unread contributions (post + responses + comments) for the thread. Can vary by user.NoneNone
comment_list_urlString. The URL of the comment list for the thread. In Q4 implementation, this will be null for question threads.NoneNone
endorsed_comment_list_urlString. The URL of the endorsed comment list for the thread. In Q4 implementation, this will be null for discussion threads.NoneNone
non_endorsed_comment_list_urlString. The URL of the non-endorsed comment list for the thread. In Q4 implementation, this will be null for discussion threads.NoneNone
editable_fieldsList of Strings. The names of fields that can be edited by the user.NoneNone
response_countInteger. The total number of direct responses for the thread.NoneNone

Comment

FieldDescriptionInitializable byEditable by
idString. The identifier string of the comment.NoneNone
parent_idString. The identifier string of the comment's parent comment (null for first-level thread responses).Any roleNone
thread_idString. The identifier string of the comment's thread.Any roleNone
authorString. 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.NoneNone
author_labelString. 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.NoneNone
created_atISO 8601 String. The timestamp of the comment's creation.NoneNone
updated_atISO 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.NoneNone
raw_bodyString. The raw content of the comment. This may contain Markdown, HTML, and MathJax markup.Any roleAuthor or privileged role
rendered_bodyString. The content of the comment, rendered in HTML (Markdown rendering and HTML stripping applied).NoneNone
endorsedBoolean. 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 roleThread author (if thread is a question) or privileged role
endorsed_byString. 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).NoneNone
endorsed_by_labelString. 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.NoneNone
endorsed_atISO 8601 String. The timestamp of the endorsement or acceptance as a correct answer. Can be null if the information is unavailable.NoneNone
abuse_flaggedBoolean. Whether the user has flagged the comment as abusive. Can vary by user.NoneAny role
votedBoolean. Whether the user has voted for the comment. Can vary by user.NoneAny role
vote_countInteger. The total number of votes for the comment.NoneNone
childrenList of Comments. The children of this comment.NoneNone
editable_fieldsList of Strings. The names of fields that can be edited by the user.NoneNone

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.

Anchor
course-endpoint
course-endpoint
/api/discussion/v1/courses/{course_id}/

MethodDescriptionAccess
GETRetrieve the discussion information for a course (the Course resource above). No parameters.Any role
Code Block
languagejs
titleGET /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"}]
}

Anchor
course-topics-endpoint
course-topics-endpoint
/api/discussion/v1/course_topics/{course_id}/

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

Any role

Code Block
languagejs
titleGET /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": []
    }
  ]
}

Anchor
thread-list-endpoint
thread-list-endpoint
/api/discussion/v1/threads/

 

MethodDescriptionAccess
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

...

Code Block
languagejs
titlePOST /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
}

Anchor
thread-endpoint
thread-endpoint
/api/discussion/v1/threads/{thread_id}/

MethodDescriptionAccess
GETRetrieve the thread. No parameters.Any role, subject to group restriction
PATCHModify the thread. Parameters as per editability noted in modelAuthor or privileged role, subject to group restriction, closure, and blackouts
DELETEDelete the thread. No parameters.Author or privileged role

...

Code Block
languagejs
titlePATCH /api/discussion/v1/threads/51dfd4e38d9f4f788a0e65c0e9311a55/
// Request body
 
{"voted": true}
 
// 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": true,
  "vote_count": 43,
  "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"],
  "response_count": 2
}

Anchor
comment-list-endpoint
comment-list-endpoint
/api/discussion/v1/comments/

MethodDescriptionAccess
GET

Retrieve a list of comments, ordered by creation date ascending. thread_id must be specified.

Query Parameters:

  • thread_id (String): Retrieve comments within the given thread
  • endorsed (Boolean): Retrieve only endorsed or non-endorsed comments
  • page_size (Integer, must be positive): Number of comments per page
  • page (Integer, must be non-negative): Page number to retrievemark_as_read (Boolean): Mark the thread the comments are retrieved from as read.
Any role; comments will only be returned if thread group restriction allows
POST

Create a new comment. Parameters as per initializability noted in model.

Required Body Parameters:

  • thread_id
  • raw_body
Any role, subject to thread group restriction, thread closure, and blackouts

...

Code Block
languagejs
titlePOST /api/discussion/v1/comments/
// Request body
 
{
  "thread_id": "51dfd4e38d9f4f788a0e65c0e9311a55",
  "raw_body": "**Example Comment Body**",
}
 
// Response body

{
  "id": "5fe452161cc049dfb3e124c41732beaf",
  "parent_id": null,
  "thread_id": "51dfd4e38d9f4f788a0e65c0e9311a55",
  "author": "example-user",
  "created_at": "2015-04-09T17:31:56Z",
  "modified_at": "2015-04-09T17:31:56Z",
  "raw_body": "**Example Comment Body**",
  "rendered_body": "<b>Example Comment Body</b>",
  "endorsed": false,
  "endorsed_by": null,
  "endorsed_at": null,
  "flagged": false,
  "voted": false,
  "vote_count": 0,
  "children": [],
  "editable_fields": ["raw_body", "flagged", "voted"]
}

Anchor
comment-endpoint
comment-endpoint
/api/discussion/v1/comments/{comment_id}/

MethodDescriptionAccess
GET

Retrieve

the comment. No parameters.Any role, subject to thread group restriction

a list of child comments for a response

Query Parameters:

  • page_size (Integer, must be positive): Number of comments per page
  • page (Integer, must be non-negative): Page number to retrieve
Any role; comments will only be returned if thread group restriction allows
PATCHModify the comment. Parameters as per editability noted in model.Author or privileged role, subject to thread group restriction, thread closure, and blackouts
DELETEDelete the comment. No parameters.Author or privileged role

...

Code Block
languagejs
titleGET /api/discussion/v1/comments/5fe452161cc049dfb3e124c41732beaf/
[
	{
  		"id": "5fe452161cc049dfb3e124c41732beaf",
  		"parent_id": "6w3452161cc049dfb3e124c41732bead",
  		"thread_id": "51dfd4e38d9f4f788a0e65c0e9311a55",
  		"author": "example-user",
  		"created_at": "2015-04-09T17:31:56Z",
  		"modified_at": "2015-04-10T14:24:43Z",
  		"raw_body": "**Example Comment Body**",
  		"rendered_body": "<b>Example Comment Body</b>",
  		"endorsed": false,
  		"endorsed_by": null,
  		"endorsed_at": null,
  		"flagged": false,
  		"voted": false,
  		"vote_count": 0,
  		"children": [],
  		"editable_fields": ["flagged", "voted"]
	},
	{
  		"id": "1w33erf67yh8kf3sf45km11k0987hj2n",
  		"parent_id": null"6w3452161cc049dfb3e124c41732bead",
  		"thread_id": "51dfd4e38d9f4f788a0e65c0e9311a55",
  		"author": "example-user",
  		"created_at": "2015-04-09T17:31:56Z",
  		"modified_at": "2015-04-10T14:24:43Z",
  		"raw_body": "**Example Another Comment Body**",
  		"rendered_body": "<b>Example Another Comment Body</b>",
  		"endorsed": truefalse,
  		"endorsed_by": "example-moderator"null,
  		"endorsed_at": "2015-04-10T14:24:43Z"null,
  		"flagged": false,
  		"voted": false,
  		"vote_count": 180,
  		"children": [],
  		"editable_fields": ["flagged", "voted"]
	}
]
Code Block
languagejs
titlePATCH /api/discussion/v1/comments/5fe452161cc049dfb3e124c41732beaf/
// Request body
 
{"voted": true}
 
// Response body
 
{
  "id": "5fe452161cc049dfb3e124c41732beaf",
  "parent_id": null,
  "thread_id": "51dfd4e38d9f4f788a0e65c0e9311a55",
  "author": "example-user",
  "created_at": "2015-04-09T17:31:56Z",
  "modified_at": "2015-04-10T14:24:43Z",
  "raw_body": "**Example Comment Body**",
  "rendered_body": "<b>Example Comment Body</b>",
  "endorsed": true,
  "endorsed_by": "example-moderator",
  "endorsed_at": "2015-04-10T14:24:43Z",
  "flagged": false,
  "voted": true,
  "vote_count": 19,
  "children": [],
  "editable_fields": ["flagged", "voted"]
}

Reference Document:

Contradictions in Web and Mobile:

View file
nameDiscussionForum_Web-vs-Mobile.xlsx
height250